Files
school-developer/src/views/home/index.vue

549 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="home-container">
<!-- 顶部欢迎区 -->
<el-row :gutter="20" class="welcome-section">
<el-col :span="24">
<el-card shadow="never" class="welcome-card">
<div class="welcome-content">
<div class="welcome-left">
<h2 class="welcome-title">欢迎回来{{ userName }}</h2>
<p class="welcome-tip">这是您今天的工作台</p>
</div>
<div class="welcome-right">
<div class="current-time">
<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="primary">
{{ item.flowName || '流程' }}
</el-tag>
<span class="todo-title">{{ item.jobName }}</span>
<span class="todo-code" v-if="item.code">({{ item.code }})</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>
<script setup lang="ts" name="home">
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import { fetchTodoPage } from '/@/api/jsonflow/do-job'
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 loadTodoList = async () => {
try {
todoLoading.value = true
const res = await fetchTodoPage({ current: 1, size: 10 })
if (res.code === 0 && res.data) {
todoList.value = res.data.records || 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) => {
// 跳转到流程处理页面
router.push({
path: '/jsonflow/run-job/do-job',
query: {
flowInstId: item.flowInstId,
id: item.id,
runNodeId: item.runNodeId
}
})
}
// 点击通知公告
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(() => {
updateTime()
timeTimer = setInterval(updateTime, 1000)
// 加载数据
loadTodoList()
loadNoticeList()
loadMessageList()
})
onUnmounted(() => {
if (timeTimer) {
clearInterval(timeTimer)
}
})
</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-code {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-left: 4px;
}
.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>