|
|
@@ -32,120 +32,81 @@
|
|
|
</a-row>
|
|
|
|
|
|
<!-- 主要内容区域 -->
|
|
|
- <a-row :gutter="20" class="main-content">
|
|
|
- <!-- 左侧:今日待办 + 最近消息 -->
|
|
|
+ <a-row :gutter="[16, 16]" style="margin-top: 16px">
|
|
|
+ <!-- 今日待办 -->
|
|
|
<a-col :xs="24" :lg="12">
|
|
|
- <!-- 今日待办 -->
|
|
|
- <a-card hoverable class="todo-card">
|
|
|
- <template #title>
|
|
|
- <div class="card-header">
|
|
|
- <span><CheckSquareOutlined /> 今日待办</span>
|
|
|
- <a-button type="link" size="small" @click="goRoute('/system/todo')">查看全部</a-button>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div class="todo-list">
|
|
|
- <a-empty v-if="todoList.length === 0" description="暂无待办事项" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
|
|
- <div v-else v-for="item in todoList" :key="item.id" class="todo-item" @click="goRoute('/system/todo')">
|
|
|
- <div class="todo-checkbox">
|
|
|
- <CheckCircleOutlined v-if="item.status === '1'" class="checked" />
|
|
|
- <ClockCircleOutlined v-else class="pending" />
|
|
|
- </div>
|
|
|
- <div class="todo-content">
|
|
|
- <div class="todo-title">{{ item.title }}</div>
|
|
|
- <div class="todo-time">
|
|
|
- <CalendarOutlined />
|
|
|
- <span>{{ item.dueDate }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <a-tag :color="getPriorityColor(item.priority)" size="small">
|
|
|
- {{ getPriorityText(item.priority) }}
|
|
|
- </a-tag>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </a-card>
|
|
|
-
|
|
|
- <!-- 最近消息 -->
|
|
|
- <a-card hoverable class="message-card mt-20">
|
|
|
- <template #title>
|
|
|
- <div class="card-header">
|
|
|
- <span><MessageOutlined /> 最近消息</span>
|
|
|
- <a-button type="link" size="small" @click="goRoute('/message')">查看全部</a-button>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div class="message-list">
|
|
|
- <a-empty v-if="recentMessages.length === 0" description="暂无消息" :image="Empty.PRESENTED_IMAGE_SIMPLE" />
|
|
|
- <div v-else v-for="item in recentMessages" :key="item.id" class="message-item" @click="goRoute('/message')">
|
|
|
- <a-avatar :src="item.avatar" :size="40">{{ item.sender?.charAt(0) }}</a-avatar>
|
|
|
- <div class="message-content">
|
|
|
- <div class="message-header">
|
|
|
- <span class="message-sender">{{ item.sender }}</span>
|
|
|
- <span class="message-time">{{ item.time }}</span>
|
|
|
- </div>
|
|
|
- <div class="message-text">{{ item.content }}</div>
|
|
|
- </div>
|
|
|
- <a-badge v-if="!item.isRead" dot />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <a-card title="今日待办" :extra="h(AButton, { type: 'link', onClick: () => goRoute('/system/todo') }, () => '查看全部')">
|
|
|
+ <a-list :data-source="todoList" :locale="{ emptyText: '暂无待办事项' }">
|
|
|
+ <template #renderItem="{ item }">
|
|
|
+ <a-list-item @click="goRoute('/system/todo')" style="cursor: pointer">
|
|
|
+ <a-list-item-meta>
|
|
|
+ <template #avatar>
|
|
|
+ <CheckCircleOutlined v-if="item.status === '1'" style="color: #52c41a; font-size: 20px" />
|
|
|
+ <ClockCircleOutlined v-else style="color: #faad14; font-size: 20px" />
|
|
|
+ </template>
|
|
|
+ <template #title>{{ item.title }}</template>
|
|
|
+ <template #description>
|
|
|
+ <CalendarOutlined /> {{ item.dueDate }}
|
|
|
+ </template>
|
|
|
+ </a-list-item-meta>
|
|
|
+ <template #actions>
|
|
|
+ <a-tag :color="getPriorityColor(item.priority)">{{ getPriorityText(item.priority) }}</a-tag>
|
|
|
+ </template>
|
|
|
+ </a-list-item>
|
|
|
+ </template>
|
|
|
+ </a-list>
|
|
|
</a-card>
|
|
|
</a-col>
|
|
|
|
|
|
- <!-- 右侧:日程安排 + 快捷入口 -->
|
|
|
+ <!-- 最近消息 -->
|
|
|
<a-col :xs="24" :lg="12">
|
|
|
- <!-- 本周日程 -->
|
|
|
- <a-card hoverable class="schedule-card">
|
|
|
- <template #title>
|
|
|
- <div class="card-header">
|
|
|
- <span><CalendarOutlined /> 本周日程</span>
|
|
|
- <span class="week-range">{{ weekRange }}</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <a-timeline class="schedule-timeline">
|
|
|
- <a-timeline-item v-for="item in scheduleList" :key="item.id" :color="item.color">
|
|
|
- <div class="schedule-item">
|
|
|
- <div class="schedule-time">
|
|
|
- <ClockCircleOutlined />
|
|
|
- <span>{{ item.time }}</span>
|
|
|
- </div>
|
|
|
- <div class="schedule-title">{{ item.title }}</div>
|
|
|
- <div class="schedule-desc">{{ item.description }}</div>
|
|
|
- </div>
|
|
|
- </a-timeline-item>
|
|
|
- </a-timeline>
|
|
|
- </a-card>
|
|
|
-
|
|
|
- <!-- 快捷入口 -->
|
|
|
- <a-card hoverable class="quick-entry-card mt-20">
|
|
|
- <template #title>
|
|
|
- <div class="card-header">
|
|
|
- <span><AppstoreOutlined /> 快捷入口</span>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <div class="quick-nav-grid">
|
|
|
- <div
|
|
|
- v-for="item in quickLinks"
|
|
|
- :key="item.path"
|
|
|
- class="quick-nav-item"
|
|
|
- @click="goRoute(item.path)"
|
|
|
- >
|
|
|
- <div class="nav-icon" :style="{ background: item.bg }">
|
|
|
- <component :is="item.icon" :style="{ fontSize: '24px', color: item.color }" />
|
|
|
- </div>
|
|
|
- <span class="nav-label">{{ item.name }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <a-card title="最近消息" :extra="h(AButton, { type: 'link', onClick: () => goRoute('/message') }, () => '查看全部')">
|
|
|
+ <a-list :data-source="recentMessages" :locale="{ emptyText: '暂无消息' }">
|
|
|
+ <template #renderItem="{ item }">
|
|
|
+ <a-list-item @click="goRoute('/message')" style="cursor: pointer">
|
|
|
+ <a-list-item-meta :description="item.content">
|
|
|
+ <template #avatar>
|
|
|
+ <a-badge :dot="!item.isRead">
|
|
|
+ <a-avatar :src="item.avatar">{{ item.sender?.charAt(0) }}</a-avatar>
|
|
|
+ </a-badge>
|
|
|
+ </template>
|
|
|
+ <template #title>
|
|
|
+ <a-space>
|
|
|
+ <span>{{ item.sender }}</span>
|
|
|
+ <a-typography-text type="secondary" style="font-size: 12px">{{ item.time }}</a-typography-text>
|
|
|
+ </a-space>
|
|
|
+ </template>
|
|
|
+ </a-list-item-meta>
|
|
|
+ </a-list-item>
|
|
|
+ </template>
|
|
|
+ </a-list>
|
|
|
</a-card>
|
|
|
</a-col>
|
|
|
</a-row>
|
|
|
+
|
|
|
+ <!-- 快捷入口 -->
|
|
|
+ <a-card title="快捷入口" style="margin-top: 16px">
|
|
|
+ <a-row :gutter="[16, 16]">
|
|
|
+ <a-col :xs="12" :sm="6" v-for="item in quickLinks" :key="item.path">
|
|
|
+ <div class="quick-item" @click="goRoute(item.path)">
|
|
|
+ <a-avatar :size="56" :style="{ backgroundColor: item.bg }">
|
|
|
+ <component :is="item.icon" :style="{ fontSize: '24px', color: item.color }" />
|
|
|
+ </a-avatar>
|
|
|
+ <div style="margin-top: 8px; text-align: center">{{ item.name }}</div>
|
|
|
+ </div>
|
|
|
+ </a-col>
|
|
|
+ </a-row>
|
|
|
+ </a-card>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup name="Workbench">
|
|
|
-import { ref, onMounted, onUnmounted, computed } from 'vue'
|
|
|
+import { ref, onMounted, onUnmounted, computed, h } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
-import { Empty } from 'ant-design-vue'
|
|
|
+import { Button as AButton } from 'ant-design-vue'
|
|
|
import {
|
|
|
ClockCircleOutlined, CalendarOutlined, CheckSquareOutlined, MessageOutlined,
|
|
|
- CheckCircleOutlined, BellOutlined, FileTextOutlined, AppstoreOutlined,
|
|
|
+ CheckCircleOutlined, BellOutlined, FileTextOutlined,
|
|
|
UserOutlined, SettingOutlined, FolderOutlined, TeamOutlined
|
|
|
} from '@ant-design/icons-vue'
|
|
|
import { getMyTodo } from '@/api/system/todo'
|
|
|
@@ -202,16 +163,6 @@ const todoList = ref([])
|
|
|
// 最近消息
|
|
|
const recentMessages = ref([])
|
|
|
|
|
|
-// 本周日程
|
|
|
-const weekRange = ref('')
|
|
|
-const scheduleList = ref([
|
|
|
- { id: 1, time: '周一 09:00', title: '团队周会', description: '讨论本周工作计划', color: 'blue' },
|
|
|
- { id: 2, time: '周二 14:00', title: '项目评审', description: '新功能需求评审', color: 'green' },
|
|
|
- { id: 3, time: '周三 10:30', title: '技术分享', description: 'Vue3 最佳实践', color: 'orange' },
|
|
|
- { id: 4, time: '周四 15:00', title: '客户沟通', description: '产品演示和反馈收集', color: 'purple' },
|
|
|
- { id: 5, time: '周五 16:00', title: '周总结', description: '本周工作总结和复盘', color: 'red' }
|
|
|
-])
|
|
|
-
|
|
|
// 快捷入口
|
|
|
const quickLinks = ref([])
|
|
|
|
|
|
@@ -274,24 +225,6 @@ function updateTime() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// 获取本周日期范围
|
|
|
-function getWeekRange() {
|
|
|
- const now = new Date()
|
|
|
- const day = now.getDay()
|
|
|
- const diff = now.getDate() - day + (day === 0 ? -6 : 1)
|
|
|
- const monday = new Date(now.setDate(diff))
|
|
|
- const sunday = new Date(monday)
|
|
|
- sunday.setDate(monday.getDate() + 6)
|
|
|
-
|
|
|
- const format = (date) => {
|
|
|
- const m = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
|
- const d = date.getDate().toString().padStart(2, '0')
|
|
|
- return `${m}.${d}`
|
|
|
- }
|
|
|
-
|
|
|
- weekRange.value = `${format(monday)} - ${format(sunday)}`
|
|
|
-}
|
|
|
-
|
|
|
// 加载快捷入口(从菜单获取)
|
|
|
async function loadQuickLinks() {
|
|
|
try {
|
|
|
@@ -393,7 +326,6 @@ let timer = null
|
|
|
|
|
|
onMounted(() => {
|
|
|
updateTime()
|
|
|
- getWeekRange()
|
|
|
loadQuickLinks()
|
|
|
loadTodoList()
|
|
|
loadRecentMessages()
|
|
|
@@ -413,344 +345,19 @@ onUnmounted(() => {
|
|
|
padding: 24px;
|
|
|
background-color: #f0f2f5;
|
|
|
min-height: 100vh;
|
|
|
-
|
|
|
- .mt-20 {
|
|
|
- margin-top: 20px;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 问候区域
|
|
|
-.greeting-section {
|
|
|
- background: #fff;
|
|
|
- border-radius: 8px;
|
|
|
- padding: 32px;
|
|
|
- margin-bottom: 20px;
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
-
|
|
|
- .greeting-content {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .greeting-text {
|
|
|
- h1 {
|
|
|
- font-size: 28px;
|
|
|
- font-weight: 500;
|
|
|
- color: #303133;
|
|
|
- margin: 0 0 8px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .greeting-subtitle {
|
|
|
- font-size: 14px;
|
|
|
- color: #909399;
|
|
|
- margin: 0;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .greeting-time {
|
|
|
- .time-display {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 12px;
|
|
|
- background: #409eff;
|
|
|
- padding: 12px 20px;
|
|
|
- border-radius: 8px;
|
|
|
-
|
|
|
- .time-icon {
|
|
|
- font-size: 20px;
|
|
|
- color: #fff;
|
|
|
- }
|
|
|
-
|
|
|
- .current-time {
|
|
|
- font-size: 24px;
|
|
|
- font-weight: 500;
|
|
|
- color: #fff;
|
|
|
- font-family: 'Courier New', monospace;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 概览卡片
|
|
|
-.overview-cards {
|
|
|
- margin-bottom: 20px;
|
|
|
-
|
|
|
- .stat-card-item {
|
|
|
- border: none;
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
- transition: all 0.3s;
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
|
- }
|
|
|
-
|
|
|
- :deep(.ant-card-body) {
|
|
|
- padding: 20px;
|
|
|
- }
|
|
|
-
|
|
|
- .stat-card-content {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 16px;
|
|
|
-
|
|
|
- .stat-icon-wrapper {
|
|
|
- width: 52px;
|
|
|
- height: 52px;
|
|
|
- border-radius: 8px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- }
|
|
|
-
|
|
|
- .stat-info {
|
|
|
- flex: 1;
|
|
|
-
|
|
|
- .stat-value {
|
|
|
- font-size: 26px;
|
|
|
- font-weight: 600;
|
|
|
- color: #303133;
|
|
|
- margin-bottom: 4px;
|
|
|
- }
|
|
|
-
|
|
|
- .stat-label {
|
|
|
- font-size: 14px;
|
|
|
- color: #909399;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 主要内容区域
|
|
|
-.main-content {
|
|
|
- .card-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- font-weight: 500;
|
|
|
-
|
|
|
- span {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 待办卡片
|
|
|
-.todo-card {
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
-
|
|
|
- .todo-list {
|
|
|
- .todo-item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 12px;
|
|
|
- padding: 14px;
|
|
|
- border-radius: 6px;
|
|
|
- margin-bottom: 10px;
|
|
|
- background: #f5f7fa;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background: #ecf5ff;
|
|
|
- }
|
|
|
-
|
|
|
- &:last-child {
|
|
|
- margin-bottom: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .todo-checkbox {
|
|
|
- font-size: 18px;
|
|
|
-
|
|
|
- .checked {
|
|
|
- color: #67c23a;
|
|
|
- }
|
|
|
-
|
|
|
- .pending {
|
|
|
- color: #e6a23c;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .todo-content {
|
|
|
- flex: 1;
|
|
|
-
|
|
|
- .todo-title {
|
|
|
- font-size: 14px;
|
|
|
- color: #303133;
|
|
|
- margin-bottom: 4px;
|
|
|
- }
|
|
|
-
|
|
|
- .todo-time {
|
|
|
- font-size: 12px;
|
|
|
- color: #909399;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 4px;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 消息卡片
|
|
|
-.message-card {
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
-
|
|
|
- .message-list {
|
|
|
- .message-item {
|
|
|
- display: flex;
|
|
|
- align-items: flex-start;
|
|
|
- gap: 12px;
|
|
|
- padding: 14px;
|
|
|
- border-radius: 6px;
|
|
|
- margin-bottom: 10px;
|
|
|
- background: #f5f7fa;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background: #ecf5ff;
|
|
|
- }
|
|
|
-
|
|
|
- &:last-child {
|
|
|
- margin-bottom: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .message-content {
|
|
|
- flex: 1;
|
|
|
- min-width: 0;
|
|
|
-
|
|
|
- .message-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 4px;
|
|
|
-
|
|
|
- .message-sender {
|
|
|
- font-size: 14px;
|
|
|
- font-weight: 500;
|
|
|
- color: #303133;
|
|
|
- }
|
|
|
-
|
|
|
- .message-time {
|
|
|
- font-size: 12px;
|
|
|
- color: #909399;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .message-text {
|
|
|
- font-size: 13px;
|
|
|
- color: #606266;
|
|
|
- overflow: hidden;
|
|
|
- text-overflow: ellipsis;
|
|
|
- white-space: nowrap;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 日程卡片
|
|
|
-.schedule-card {
|
|
|
- border-radius: 8px;
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
-
|
|
|
- .card-header {
|
|
|
- .week-range {
|
|
|
- font-size: 13px;
|
|
|
- color: #909399;
|
|
|
- font-weight: normal;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .schedule-timeline {
|
|
|
- margin-top: 16px;
|
|
|
-
|
|
|
- .schedule-item {
|
|
|
- .schedule-time {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 6px;
|
|
|
- font-size: 13px;
|
|
|
- color: #606266;
|
|
|
- margin-bottom: 6px;
|
|
|
- }
|
|
|
-
|
|
|
- .schedule-title {
|
|
|
- font-size: 14px;
|
|
|
- font-weight: 500;
|
|
|
- color: #303133;
|
|
|
- margin-bottom: 4px;
|
|
|
- }
|
|
|
-
|
|
|
- .schedule-desc {
|
|
|
- font-size: 13px;
|
|
|
- color: #909399;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
-// 快捷入口
|
|
|
-.quick-entry-card {
|
|
|
+.quick-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 16px;
|
|
|
border-radius: 8px;
|
|
|
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
-
|
|
|
- .quick-nav-grid {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(4, 1fr);
|
|
|
- gap: 20px;
|
|
|
- padding: 16px;
|
|
|
-
|
|
|
- .quick-nav-item {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- cursor: pointer;
|
|
|
- padding: 20px 10px;
|
|
|
- border-radius: 6px;
|
|
|
- transition: all 0.3s;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background: #f5f7fa;
|
|
|
- }
|
|
|
-
|
|
|
- .nav-icon {
|
|
|
- width: 56px;
|
|
|
- height: 56px;
|
|
|
- border-radius: 8px;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- margin-bottom:10px;
|
|
|
- }
|
|
|
-
|
|
|
- .nav-label {
|
|
|
- font-size: 14px;
|
|
|
- color: #606266;
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-@media (max-width: 768px) {
|
|
|
- .greeting-content {
|
|
|
- flex-direction: column;
|
|
|
- gap: 20px;
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
+ transition: all 0.3s;
|
|
|
|
|
|
- .quick-nav-grid {
|
|
|
- grid-template-columns: repeat(2, 1fr) !important;
|
|
|
+ &:hover {
|
|
|
+ background: #f5f7fa;
|
|
|
}
|
|
|
}
|
|
|
</style>
|