| | |
| | | <template> |
| | | <div class="workspace-situation"> |
| | | <div class="workspace-situation-header"> |
| | | <div class="workspace-situation-header-title"> |
| | | <span>个人工作台概况</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="workspace-container h-full"> |
| | | <!-- 左侧主要内容区 --> |
| | | <div class="flex flex-col h-full overflow-hidden" :style="{ gap: layoutGap }"> |
| | | <!-- 工作概览 --> |
| | | <div class="overview-section flex-0 bg-white border border-solid border-white rounded-lg p-[20px] h-[150px]"> |
| | | <div class="section-header mb-4"> |
| | | <h3 class="font-bold">工作概览</h3> |
| | | </div> |
| | | <div class="overview-cards"> |
| | | <!-- 管理人数 --> |
| | | <div class="overview-card bg-[#dee8ff] rounded-lg p-4 flex items-center"> |
| | | <i class="i-carbon:user-multiple text-3xl text-blue-500 mr-4"></i> |
| | | <div> |
| | | <div class="text-3xl font-bold text-[#4f85f6]">3</div> |
| | | <div class="text-gray-600">管理人数</div> |
| | | </div> |
| | | </div> |
| | | <!-- 待办事项 --> |
| | | <div class="overview-card bg-[#faeaed] rounded-lg p-4 flex items-center"> |
| | | <i class="i-carbon:task text-3xl text-red-500 mr-4"></i> |
| | | <div> |
| | | <div class="text-3xl font-bold text-red-500">3</div> |
| | | <div class="text-gray-600">待办事项</div> |
| | | </div> |
| | | </div> |
| | | <!-- 预警事项 --> |
| | | <div class="overview-card bg-[#dbf2f8] rounded-lg p-4 flex items-center"> |
| | | <i class="i-carbon:warning text-3xl text-cyan-500 mr-4"></i> |
| | | <div> |
| | | <div class="text-3xl font-bold text-cyan-500">3</div> |
| | | <div class="text-gray-600">预警事项</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="flex flex-col flex-auto" :style="{ gap: layoutGap }"> |
| | | <!-- 待办事项和我发起的表格 --> |
| | | <div class="flex h-1/2" :style="{ gap: layoutGap }"> |
| | | <!-- 待办事项 --> |
| | | <div class="bg-white rounded-lg p-4 shadow-sm flex flex-col w-1/2"> |
| | | <div class="flex justify-between items-center mb-4 flex-0"> |
| | | <div class="text-lg font-bold">待办事项</div> |
| | | <el-tabs v-model="todoType" class="todo-tabs"> |
| | | <el-tab-pane label="待办事项" name="todo" /> |
| | | <el-tab-pane label="已办事项" name="done" /> |
| | | </el-tabs> |
| | | </div> |
| | | <el-table class="flex-auto" :data="todoList" style="width: 100%" size="small"> |
| | | <el-table-column prop="name" label="事件类型" min-width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="getEventTypeClass(row.type)">{{ row.name }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="sender" label="发起人" width="100" /> |
| | | <el-table-column prop="time" label="发起时间" width="100" /> |
| | | <el-table-column prop="status" label="状态" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)" size="small"> |
| | | {{ row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="80" fixed="right"> |
| | | <template #default> |
| | | <el-button type="primary" size="small" class="custom-button">办理</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | |
| | | <!-- 我发起的 --> |
| | | <div class="bg-white rounded-lg p-4 shadow-sm flex flex-col h-full w-1/2"> |
| | | <div class="flex justify-between items-center mb-4 flex-0"> |
| | | <div class="text-lg font-bold">我发起的</div> |
| | | <el-tabs v-model="initiatedType" class="todo-tabs"> |
| | | <el-tab-pane label="我发起" name="initiated" /> |
| | | <el-tab-pane label="我收到" name="received" /> |
| | | </el-tabs> |
| | | </div> |
| | | |
| | | <el-table :data="initiatedList" class="flex-auto" style="width: 100%" size="small"> |
| | | <el-table-column prop="type" label="类型" min-width="120"> |
| | | <template #default="{ row }"> |
| | | <span :class="getEventTypeClass(row.type)">{{ row.type }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="sender" label="发起人" width="100" /> |
| | | <el-table-column prop="time" label="发起时间" width="100" /> |
| | | <el-table-column prop="status" label="状态" width="80"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.status)" size="small"> |
| | | {{ row.status }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="80" fixed="right"> |
| | | <template #default> |
| | | <el-button type="primary" size="small" class="custom-button">详情</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 预警表格 --> |
| | | <div class="bg-white rounded-lg p-4 shadow-sm h-1/2"> |
| | | <div class="text-lg font-bold mb-4">预警</div> |
| | | <el-table :data="warningList" style="width: 100%" size="small"> |
| | | <el-table-column prop="studentId" label="学号" min-width="120" /> |
| | | <el-table-column prop="name" label="姓名" width="100" /> |
| | | <el-table-column prop="type" label="学生类型" width="100" /> |
| | | <el-table-column prop="guardianId" label="护照号" min-width="150" /> |
| | | <el-table-column prop="warningType" label="预警类型" width="120"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getWarningType(row.warningType)" size="small"> |
| | | {{ row.warningType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="100" fixed="right"> |
| | | <template #default> |
| | | <el-button type="primary" size="small" class="custom-button">提交预警</el-button> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 右侧边栏 --> |
| | | <div class="sidebar h-full overflow-hidden flex flex-col" :style="{ gap: layoutGap }"> |
| | | <!-- 个人信息 --> |
| | | <div class="profile-card p-[20px] bg-white border border-solid border-white rounded-lg h-[150px] flex-0"> |
| | | <div class="section-header mb-4"> |
| | | <h3>个人信息</h3> |
| | | </div> |
| | | <div class="profile-header flex gap-4"> |
| | | <!-- <div class="avatar-placeholder"> |
| | | <i class="i-carbon:user text-3xl text-gray-400"></i> |
| | | </div> --> |
| | | <div class="profile-info flex flex-col gap-2"> |
| | | <div class="flex items-center" v-for="item in 3" :key="item"> |
| | | <span class="mr-1">用户名:</span> |
| | | <span>wjj</span> |
| | | </div> |
| | | </div> |
| | | <div class="profile-info flex flex-col gap-2"> |
| | | <div class="flex items-center" v-for="item in 3" :key="item"> |
| | | <span class="mr-1">用户名:</span> |
| | | <span>wjj</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 统计图表 --> |
| | | <div class="chart-container p-[20px] h-1/2 overflow-hidden"> |
| | | <div class="chart-header"> |
| | | <h3>统计图表</h3> |
| | | <span class="subtitle">国别</span> |
| | | </div> |
| | | <div class="chart" ref="chartRef"></div> |
| | | </div> |
| | | |
| | | <!-- 日历 --> |
| | | <div class="calendar-container p-[20px] h-1/2 overflow-hidden"> |
| | | <div class="calendar-header"> |
| | | <h3>时间</h3> |
| | | <div class="flex items-center gap-2"> |
| | | <el-date-picker v-model="currentMonth" type="month" format="YYYY年MM月" :placeholder="'选择月份'" size="small" /> |
| | | </div> |
| | | </div> |
| | | <div class="calendar-notice mb-0.5">您今日无待办</div> |
| | | <el-calendar v-model="currentDate"> |
| | | <template #dateCell="{ data }"> |
| | | <div class="custom-calendar-cell"> |
| | | <span class="date-text">{{ data.day.split('-')[2] }}</span> |
| | | <div v-if="hasEvent(data)" class="event-dot"></div> |
| | | </div> |
| | | </template> |
| | | </el-calendar> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts" name="WorkspaceSituation"> |
| | | import { ref } from 'vue'; |
| | | <script setup lang="ts"> |
| | | import * as echarts from 'echarts'; |
| | | import { computed, onMounted, ref } from 'vue'; |
| | | |
| | | const layoutGap = '10px'; |
| | | interface CalendarData { |
| | | day: string; |
| | | [key: string]: any; |
| | | } |
| | | |
| | | // 状态变量 |
| | | const todoType = ref('todo'); |
| | | const initiatedType = ref('initiated'); |
| | | const currentDate = ref(new Date('2025-04-06')); |
| | | const currentMonth = computed(() => { |
| | | const date = currentDate.value; |
| | | return date instanceof Date ? date : new Date(date); |
| | | }); |
| | | const chartRef = ref<HTMLElement>(); |
| | | |
| | | // 示例数据 - 增加更多数据 |
| | | const todoList = ref([ |
| | | { name: '实验室更新', type: 'update', sender: '王美丽', time: '2021.12.06', status: '待确认' }, |
| | | { name: '新生指导', type: 'guide', sender: '李明', time: '2021.12.06', status: '未读' }, |
| | | { name: '活动报名', type: 'activity', sender: '张三', time: '2021.12.06', status: '已读' }, |
| | | { name: '课程变更', type: 'update', sender: '李四', time: '2021.12.06', status: '待确认' }, |
| | | { name: '会议通知', type: 'notice', sender: '王五', time: '2021.12.06', status: '未读' }, |
| | | { name: '教材订购', type: 'order', sender: '赵六', time: '2021.12.06', status: '已处理' }, |
| | | { name: '成绩录入', type: 'grade', sender: '孙七', time: '2021.12.06', status: '待确认' }, |
| | | { name: '请假审批', type: 'leave', sender: '周八', time: '2021.12.06', status: '已读' }, |
| | | { name: '请假审批', type: 'leave', sender: '周八', time: '2021.12.06', status: '已读' }, |
| | | { name: '请假审批', type: 'leave', sender: '周八', time: '2021.12.06', status: '已读' }, |
| | | { name: '请假审批', type: 'leave', sender: '周八', time: '2021.12.06', status: '已读' }, |
| | | { name: '请假审批', type: 'leave', sender: '周八', time: '2021.12.06', status: '已读' }, |
| | | ]); |
| | | |
| | | const initiatedList = ref([ |
| | | { type: '活动', sender: '王美丽', time: '2021.12.06', status: '进行中' }, |
| | | { type: '报到注册', sender: '李明', time: '2021.12.06', status: '关闭' }, |
| | | { type: '课程变更', sender: '张三', time: '2021.12.06', status: '待审核' }, |
| | | { type: '会议通知', sender: '李四', time: '2021.12.06', status: '已完成' }, |
| | | { type: '教材订购', sender: '王五', time: '2021.12.06', status: '进行中' }, |
| | | { type: '成绩录入', sender: '赵六', time: '2021.12.06', status: '待审核' }, |
| | | { type: '请假审批', sender: '孙七', time: '2021.12.06', status: '已拒绝' }, |
| | | { type: '活动策划', sender: '周八', time: '2021.12.06', status: '进行中' }, |
| | | { type: '活动策划', sender: '周八', time: '2021.12.06', status: '进行中' }, |
| | | { type: '活动策划', sender: '周八', time: '2021.12.06', status: '进行中' }, |
| | | { type: '活动策划', sender: '周八', time: '2021.12.06', status: '进行中' }, |
| | | { type: '活动策划', sender: '周八', time: '2021.12.06', status: '进行中' }, |
| | | { type: '活动策划', sender: '周八', time: '2021.12.06', status: '进行中' }, |
| | | { type: '活动策划', sender: '周八', time: '2021.12.06', status: '进行中' }, |
| | | { type: '活动策划', sender: '周八', time: '2021.12.06', status: '进行中' }, |
| | | ]); |
| | | |
| | | const warningList = ref([ |
| | | { studentId: 'N20930498594', name: '王美丽', type: '高中', guardianId: '309209382903943', warningType: '迟到预警' }, |
| | | { studentId: 'N20930498595', name: '李明', type: '高中', guardianId: '309209382903944', warningType: '缺勤预警' }, |
| | | { studentId: 'N20930498596', name: '张三', type: '初中', guardianId: '309209382903945', warningType: '成绩预警' }, |
| | | { studentId: 'N20930498597', name: '李四', type: '高中', guardianId: '309209382903946', warningType: '行为预警' }, |
| | | { studentId: 'N20930498598', name: '王五', type: '初中', guardianId: '309209382903947', warningType: '迟到预警' }, |
| | | ]); |
| | | |
| | | // 工具函数 |
| | | const getEventTypeClass = (type: string) => { |
| | | const classes = { |
| | | update: 'text-green-500', |
| | | guide: 'text-blue-500', |
| | | activity: 'text-orange-500', |
| | | notice: 'text-purple-500', |
| | | order: 'text-cyan-500', |
| | | grade: 'text-pink-500', |
| | | leave: 'text-indigo-500', |
| | | }; |
| | | return classes[type] || ''; |
| | | }; |
| | | |
| | | const getStatusType = (status: string) => { |
| | | const types = { |
| | | 待确认: 'warning', |
| | | 未读: 'danger', |
| | | 已读: 'info', |
| | | 已处理: 'success', |
| | | 进行中: 'primary', |
| | | 关闭: 'info', |
| | | 待审核: 'warning', |
| | | 已完成: 'success', |
| | | 已拒绝: 'danger', |
| | | }; |
| | | return types[status] || 'default'; |
| | | }; |
| | | |
| | | const getWarningType = (type: string) => { |
| | | const types = { |
| | | 迟到预警: 'danger', |
| | | 缺勤预警: 'warning', |
| | | 成绩预警: 'info', |
| | | 行为预警: 'warning', |
| | | }; |
| | | return types[type] || 'default'; |
| | | }; |
| | | |
| | | const hasEvent = (date: CalendarData) => { |
| | | // 实现判断日期是否有事件的逻辑 |
| | | return Math.random() > 0.8; |
| | | }; |
| | | |
| | | // 图表初始化 |
| | | onMounted(() => { |
| | | if (chartRef.value) { |
| | | const chart = echarts.init(chartRef.value); |
| | | chart.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['中国', '德国', '法国', '英国', '新西兰', '美国', '瑞士'], |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | }, |
| | | series: [ |
| | | { |
| | | data: [80, 50, 100, 30, 70, 80, 60], |
| | | type: 'bar', |
| | | barWidth: '30%', |
| | | itemStyle: { |
| | | color: '#409EFF', |
| | | }, |
| | | }, |
| | | ], |
| | | }); |
| | | } |
| | | }); |
| | | </script> |
| | | <style scoped lang="scss"></style> |
| | | |
| | | <style scoped lang="scss"> |
| | | .workspace-container { |
| | | display: grid; |
| | | grid-template-columns: 1fr 300px; |
| | | gap: v-bind(layoutGap); |
| | | padding: 20px; |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | .overview-cards { |
| | | display: grid; |
| | | grid-template-columns: repeat(3, 1fr); |
| | | gap: 20px; |
| | | } |
| | | |
| | | // 自定义按钮样式 |
| | | .custom-button { |
| | | --el-button-bg-color: var(--color-btn-base); |
| | | --el-button-border-color: var(--color-btn-base); |
| | | --el-button-hover-bg-color: var(--color-btn-hover); |
| | | --el-button-hover-border-color: var(--color-btn-hover); |
| | | } |
| | | |
| | | // 自定义 tabs 样式 |
| | | :deep(.todo-tabs) { |
| | | .el-tabs__header { |
| | | margin: 0; |
| | | } |
| | | |
| | | .el-tabs__nav-wrap::after { |
| | | display: none; |
| | | } |
| | | |
| | | .el-tabs__item { |
| | | padding: 0 10px; |
| | | height: 32px; |
| | | line-height: 32px; |
| | | font-size: 14px; |
| | | |
| | | &.is-active { |
| | | color: var(--color-btn-base); |
| | | } |
| | | } |
| | | |
| | | .el-tabs__active-bar { |
| | | background-color: var(--color-btn-base); |
| | | } |
| | | } |
| | | |
| | | // 自定义日历样式 |
| | | :deep(.el-calendar) { |
| | | --el-calendar-border: none; |
| | | --el-calendar-header-border-bottom: none; |
| | | background: none; |
| | | |
| | | .el-calendar__header { |
| | | display: none; |
| | | } |
| | | |
| | | .el-calendar__body { |
| | | padding: 12px 0; |
| | | } |
| | | |
| | | .el-calendar-table { |
| | | td { |
| | | border: none; |
| | | padding: 4px; |
| | | } |
| | | |
| | | .current { |
| | | background: none; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .custom-calendar-cell { |
| | | height: 32px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | position: relative; |
| | | |
| | | .date-text { |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .event-dot { |
| | | position: absolute; |
| | | top: 2px; |
| | | right: 2px; |
| | | width: 6px; |
| | | height: 6px; |
| | | border-radius: 50%; |
| | | background-color: var(--el-color-primary); |
| | | } |
| | | } |
| | | |
| | | .calendar-notice { |
| | | background-color: #fff7e6; |
| | | color: #fa8c16; |
| | | padding: 8px 12px; |
| | | border-radius: 4px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .avatar-placeholder { |
| | | width: 64px; |
| | | height: 64px; |
| | | border-radius: 50%; |
| | | background-color: #f5f7fa; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | :deep(.el-calendar-table .el-calendar-day) { |
| | | --el-calendar-cell-width: 10px; |
| | | } |
| | | |
| | | .chart-container { |
| | | background: white; |
| | | border-radius: 8px; |
| | | |
| | | .chart { |
| | | height: 300px; |
| | | } |
| | | |
| | | .chart-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 16px; |
| | | |
| | | .subtitle { |
| | | color: var(--el-text-color-secondary); |
| | | font-size: 14px; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .calendar-container { |
| | | background: white; |
| | | border-radius: 8px; |
| | | |
| | | .calendar-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 16px; |
| | | |
| | | h3 { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | margin: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | :deep(.el-date-picker) { |
| | | --el-input-width: 120px; |
| | | } |
| | | |
| | | .section-header { |
| | | h3 { |
| | | font-size: 16px; |
| | | // font-weight: 500; |
| | | // margin: 0; |
| | | // color: var(--el-text-color-primary); |
| | | } |
| | | } |
| | | </style> |