feat: 重新设计首页,去除挂件配置,添加待办工作、通知公告、站内提醒

This commit is contained in:
吴红兵
2026-03-04 12:14:19 +08:00
parent 501af39a14
commit fa3b379d05

View File

@@ -1,24 +1,560 @@
<template> <template>
<div> <div class="home-container">
<div v-if="pageLoading"> <!-- 顶部欢迎区 -->
<el-main> <el-row :gutter="20" class="welcome-section">
<el-card shadow="never"> <el-col :span="24">
<el-skeleton :rows="1"></el-skeleton> <el-card shadow="never" class="welcome-card">
</el-card> <div class="welcome-content">
<el-card shadow="never" style="margin-top: 15px;"> <div class="welcome-left">
<el-skeleton></el-skeleton> <h2 class="welcome-title">欢迎回来{{ userName }}</h2>
</el-card> <p class="welcome-tip">这是您今天的工作台</p>
</el-main> </div>
</div> <div class="welcome-right">
<widgets/> <div class="current-time">
</div> <div class="time-display">{{ currentTime }}</div>
<div class="date-display">{{ currentDate }}</div>
<div class="week-display">{{ currentWeek }}</div>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 主体内容区 -->
<el-row :gutter="20" class="main-content">
<!-- 待办工作 -->
<el-col :xs="24" :sm="24" :md="16" :lg="17" :xl="18">
<el-card shadow="never" class="todo-card">
<template #header>
<div class="card-header">
<span class="card-title">
<SvgIcon name="ele-Tickets" class="card-icon" />
待办工作
</span>
<span class="todo-count" v-if="todoList.length > 0">{{ todoList.length }} 项待处理</span>
</div>
</template>
<div class="todo-list" v-if="todoLoading">
<el-skeleton :rows="3" animated />
</div>
<div class="todo-list" v-else-if="todoList.length > 0">
<div
v-for="item in todoList"
:key="item.id"
class="todo-item"
@click="handleTodoClick(item)"
>
<div class="todo-item-left">
<el-tag size="small" :type="getTodoType(item.priority)">
{{ getPriorityText(item.priority) }}
</el-tag>
<span class="todo-title">{{ item.title || item.taskName }}</span>
</div>
<div class="todo-item-right">
<span class="todo-time">{{ formatTime(item.createTime) }}</span>
<el-icon class="todo-arrow"><ele-ArrowRight /></el-icon>
</div>
</div>
</div>
<el-empty v-else description="暂无待办工作" :image-size="60" />
</el-card>
</el-col>
<!-- 右侧内容区 -->
<el-col :xs="24" :sm="24" :md="8" :lg="7" :xl="6">
<!-- 通知公告 -->
<el-card shadow="never" class="notice-card">
<template #header>
<div class="card-header">
<span class="card-title">
<SvgIcon name="ele-Bell" class="card-icon" />
通知公告
</span>
</div>
</template>
<div class="notice-list" v-if="noticeLoading">
<el-skeleton :rows="3" animated />
</div>
<div class="notice-list" v-else-if="noticeList.length > 0">
<div
v-for="item in noticeList"
:key="item.id"
class="notice-item"
@click="handleNoticeClick(item)"
>
<div class="notice-title">{{ item.title }}</div>
<div class="notice-time">{{ formatTime(item.createTime) }}</div>
</div>
</div>
<el-empty v-else description="暂无通知公告" :image-size="60" />
</el-card>
<!-- 站内提醒 -->
<el-card shadow="never" class="message-card">
<template #header>
<div class="card-header">
<span class="card-title">
<SvgIcon name="ele-Message" class="card-icon" />
站内提醒
</span>
<el-badge :value="unreadCount" :hidden="unreadCount === 0" :max="99" />
</div>
</template>
<div class="message-list" v-if="messageLoading">
<el-skeleton :rows="3" animated />
</div>
<div class="message-list" v-else-if="messageList.length > 0">
<div
v-for="item in messageList"
:key="item.id"
class="message-item"
:class="{ 'is-unread': !item.readFlag }"
@click="handleMessageClick(item)"
>
<div class="message-content">
<div class="message-title">{{ item.title }}</div>
<div class="message-summary" v-if="item.content">{{ item.content }}</div>
</div>
<div class="message-time">{{ formatTime(item.createTime) }}</div>
</div>
</div>
<el-empty v-else description="暂无站内提醒" :image-size="60" />
</el-card>
</el-col>
</el-row>
</div>
</template> </template>
<script setup lang="ts" name="dashboard"> <script setup lang="ts" name="home">
const Widgets = defineAsyncComponent(() => import('./widgets/index.vue')); import { ref, onMounted, onUnmounted } from 'vue'
const pageLoading = ref(true); import { useRouter } from 'vue-router'
import { fetchList as fetchPendingWorkList } from '/@/api/stuwork/pendingwork'
import { fetchList as fetchNoticeList } from '/@/api/jsonflow/ws-notice'
import { fetchUserMessageList, readUserMessage } from '/@/api/admin/message'
const router = useRouter()
// 用户信息模拟实际应从store获取
const userName = ref('用户')
// 时间相关
const currentTime = ref('')
const currentDate = ref('')
const currentWeek = ref('')
let timeTimer: ReturnType<typeof setInterval>
// 待办工作
const todoList = ref<any[]>([])
const todoLoading = ref(true)
// 通知公告
const noticeList = ref<any[]>([])
const noticeLoading = ref(true)
// 站内提醒
const messageList = ref<any[]>([])
const messageLoading = ref(true)
const unreadCount = ref(0)
// 更新时间
const updateTime = () => {
const now = new Date()
currentTime.value = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
currentDate.value = now.toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })
const weekDays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
currentWeek.value = weekDays[now.getDay()]
}
// 格式化时间
const formatTime = (time: string) => {
if (!time) return ''
const date = new Date(time)
const now = new Date()
const diff = now.getTime() - date.getTime()
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (days === 0) {
return '今天'
} else if (days === 1) {
return '昨天'
} else if (days < 7) {
return `${days}天前`
} else {
return time.substring(0, 10)
}
}
// 获取待办工作优先级文本
const getPriorityText = (priority?: number) => {
const map: Record<number, string> = {
0: '普通',
1: '重要',
2: '紧急'
}
return map[priority || 0] || '普通'
}
// 获取待办工作优先级类型
const getTodoType = (priority?: number) => {
const map: Record<number, string> = {
0: 'info',
1: 'warning',
2: 'danger'
}
return map[priority || 0] || 'info'
}
// 加载待办工作
const loadTodoList = async () => {
try {
todoLoading.value = true
const res = await fetchPendingWorkList({ size: 10 })
if (res.code === 0 && res.data) {
todoList.value = res.data.rows || res.data || []
}
} catch (e) {
console.error('加载待办工作失败:', e)
} finally {
todoLoading.value = false
}
}
// 加载通知公告
const loadNoticeList = async () => {
try {
noticeLoading.value = true
const res = await fetchNoticeList({ size: 10 })
if (res.code === 0 && res.data) {
noticeList.value = res.data.rows || res.data || []
}
} catch (e) {
console.error('加载通知公告失败:', e)
} finally {
noticeLoading.value = false
}
}
// 加载站内提醒
const loadMessageList = async () => {
try {
messageLoading.value = true
const res = await fetchUserMessageList({ size: 10 })
if (res.code === 0 && res.data) {
const list = res.data.rows || res.data || []
messageList.value = list
unreadCount.value = list.filter((item: any) => !item.readFlag).length
}
} catch (e) {
console.error('加载站内提醒失败:', e)
} finally {
messageLoading.value = false
}
}
// 点击待办项
const handleTodoClick = (item: any) => {
// 根据待办类型跳转到对应页面
if (item.businessType) {
router.push({
path: '/stuwork/pendingwork',
query: { id: item.id }
})
}
}
// 点击通知公告
const handleNoticeClick = (item: any) => {
router.push({
path: '/jsonflow/ws-notice',
query: { id: item.id }
})
}
// 点击站内提醒
const handleMessageClick = async (item: any) => {
// 标记已读
if (!item.readFlag) {
try {
await readUserMessage({ id: item.id })
item.readFlag = true
unreadCount.value = Math.max(0, unreadCount.value - 1)
} catch (e) {
console.error('标记已读失败:', e)
}
}
}
onMounted(() => { onMounted(() => {
pageLoading.value = false; updateTime()
}); timeTimer = setInterval(updateTime, 1000)
// 加载数据
loadTodoList()
loadNoticeList()
loadMessageList()
})
onUnmounted(() => {
if (timeTimer) {
clearInterval(timeTimer)
}
})
</script> </script>
<style scoped lang="scss">
.home-container {
padding: 20px;
min-height: 100%;
box-sizing: border-box;
}
.welcome-section {
margin-bottom: 20px;
}
.welcome-card {
background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-light-3) 100%);
border: none;
:deep(.el-card__body) {
padding: 30px;
}
}
.welcome-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.welcome-left {
color: #fff;
}
.welcome-title {
font-size: 28px;
font-weight: 600;
margin: 0 0 8px 0;
}
.welcome-tip {
font-size: 14px;
opacity: 0.9;
margin: 0;
}
.welcome-right {
text-align: right;
color: #fff;
}
.current-time {
.time-display {
font-size: 36px;
font-weight: 600;
font-family: 'DIN Alternate', monospace;
}
.date-display {
font-size: 16px;
opacity: 0.9;
}
.week-display {
font-size: 14px;
opacity: 0.8;
}
}
.main-content {
.el-card {
margin-bottom: 20px;
}
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-title {
display: flex;
align-items: center;
font-size: 16px;
font-weight: 600;
}
.card-icon {
margin-right: 8px;
font-size: 18px;
color: var(--el-color-primary);
}
.todo-count {
font-size: 12px;
color: var(--el-color-danger);
font-weight: 500;
}
.todo-list {
min-height: 100px;
}
.todo-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid var(--el-border-color-lighter);
cursor: pointer;
transition: all 0.2s;
&:hover {
background-color: var(--el-fill-color-light);
padding-left: 8px;
padding-right: 8px;
margin-left: -8px;
margin-right: -8px;
}
&:last-child {
border-bottom: none;
}
}
.todo-item-left {
display: flex;
align-items: center;
gap: 12px;
}
.todo-title {
font-size: 14px;
color: var(--el-text-color-primary);
}
.todo-item-right {
display: flex;
align-items: center;
gap: 8px;
}
.todo-time {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.todo-arrow {
color: var(--el-text-color-secondary);
transition: transform 0.2s;
}
.todo-item:hover .todo-arrow {
transform: translateX(4px);
color: var(--el-color-primary);
}
.notice-list,
.message-list {
min-height: 100px;
}
.notice-item {
padding: 12px 0;
border-bottom: 1px solid var(--el-border-color-lighter);
cursor: pointer;
transition: all 0.2s;
&:hover {
color: var(--el-color-primary);
}
&:last-child {
border-bottom: none;
}
}
.notice-title {
font-size: 14px;
color: var(--el-text-color-primary);
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.notice-time {
font-size: 12px;
color: var(--el-text-color-secondary);
}
.message-item {
display: flex;
flex-direction: column;
padding: 12px 0;
border-bottom: 1px solid var(--el-border-color-lighter);
cursor: pointer;
transition: all 0.2s;
&:hover {
background-color: var(--el-fill-color-light);
padding-left: 8px;
padding-right: 8px;
margin-left: -8px;
margin-right: -8px;
}
&.is-unread {
background-color: var(--el-color-primary-light-9);
border-left: 3px solid var(--el-color-primary);
padding-left: 5px;
margin-left: -8px;
}
&:last-child {
border-bottom: none;
}
}
.message-content {
margin-bottom: 4px;
}
.message-title {
font-size: 14px;
color: var(--el-text-color-primary);
font-weight: 500;
}
.message-summary {
font-size: 12px;
color: var(--el-text-color-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 4px;
}
.message-time {
font-size: 12px;
color: var(--el-text-color-secondary);
}
:deep(.el-empty) {
padding: 20px 0;
}
@media (max-width: 768px) {
.welcome-content {
flex-direction: column;
text-align: center;
gap: 20px;
}
.welcome-right {
text-align: center;
}
}
</style>