Files
guoju0808/ry-vue3/src/views/manager/Workbench.vue
T
2026-08-16 13:42:26 +08:00

168 lines
5.9 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="page-card manager-workbench">
<div class="breadcrumb">首页 / 工作台</div>
<!-- 5 KPI -->
<div class="stats-grid">
<router-link class="stat-card" to="/manager/projects">
<div class="stat-label">项目总数量</div>
<div class="stat-value">{{ stats.totalProjects }}</div>
</router-link>
<router-link class="stat-card" to="/manager/meetings">
<div class="stat-label">已结算数量</div>
<div class="stat-value">{{ stats.settledProjects }}</div>
</router-link>
<router-link class="stat-card" to="/manager/projects">
<div class="stat-label">已结题数量</div>
<div class="stat-value">{{ stats.completedProjects }}</div>
</router-link>
<router-link class="stat-card" to="/manager/meetings">
<div class="stat-label">已执行会议</div>
<div class="stat-value">{{ stats.executedMeetings }}</div>
</router-link>
<router-link class="stat-card" to="/manager/meetings">
<div class="stat-label">未执行会议</div>
<div class="stat-value">{{ stats.pendingMeetings }}</div>
</router-link>
</div>
<p class="hint-text">*点击数字跳转到相应的列表页</p>
<!-- 消息通知 -->
<section class="section">
<h2 class="section-title">消息通知<a class="more" href="javascript:void(0)">更多 </a></h2>
<ul class="notice-list">
<li class="notice-item" v-for="n in notices" :key="n.id" :class="{ read: n.read }">
<div class="dot"></div>
<div class="body">
<div class="title">{{ n.title }}</div>
<div class="text">{{ n.text }}</div>
</div>
<span class="time">{{ n.time }}</span>
</li>
<li v-if="notices.length === 0" class="empty">暂无消息</li>
</ul>
</section>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { bizList, listMyMessages } from '@/api/public'
const stats = ref({
totalProjects: 0, settledProjects: 0, completedProjects: 0,
executedMeetings: 0, pendingMeetings: 0
})
const notices = ref([])
async function loadStats() {
try {
// 项目总数量
const ps = await bizList('project', { pageNum: 1, pageSize: 1 })
stats.value.totalProjects = ps.total || ps.data?.total || 0
// 已结题项目
const cp = await bizList('project', { pageNum: 1, pageSize: 1, isFinished: '1' })
stats.value.completedProjects = cp.total || cp.data?.total || 0
// 会议总数量
const ms = await bizList('meeting', { pageNum: 1, pageSize: 1 })
const meetingTotal = ms.total || ms.data?.total || 0
// 已执行会议
const em = await bizList('meeting', { pageNum: 1, pageSize: 1, status: '已执行' })
stats.value.executedMeetings = em.total || em.data?.total || 0
stats.value.pendingMeetings = meetingTotal - stats.value.executedMeetings
// 已结算项目
const sp = await bizList('project', { pageNum: 1, pageSize: 1, isSettled: '1' })
stats.value.settledProjects = sp.total || sp.data?.total || 0
} catch (e) {
console.warn('loadStats failed', e)
}
}
async function loadNotices() {
try {
const r = await listMyMessages({ limit: 50 }); const list = r.rows
notices.value = list.map(n => ({
id: n.id,
title: n.title,
text: n.text,
time: n.time,
read: n.read
}))
} catch (e) {
console.warn('loadNotices failed', e)
}
}
onMounted(() => { loadStats(); loadNotices() })
</script>
<style scoped>
/* 整页面板 (与 People.vue 风格一致) */
.manager-workbench { padding: 16px; }
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
/* 5 个 KPI 卡 (跟 executor/Overview 一致) */
.stats-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 12px; }
.stat-card {
background: #fff;
border: 1px solid #f0f0f0;
border-radius: 8px;
padding: 24px;
cursor: pointer;
text-decoration: none;
color: inherit;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: opacity .15s, border-color .15s;
min-height: 120px;
}
.stat-card:hover { border-color: var(--brand-primary); opacity: .9; }
.stat-label { font-size: 14px; color: #8c8c8c; margin-bottom: 16px; }
.stat-value {
font-size: 32px;
font-weight: 600;
line-height: 1.2;
background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-deep) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hint-text { font-size: 12px; color: #ff4d4f; margin: 12px 0 20px; text-align: center; }
/* 消息通知 */
.section {
background: #fff;
border-radius: 8px;
padding: 20px 24px;
border: 1px solid #f0f0f0;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 16px;
padding-left: 10px;
border-left: 3px solid var(--brand-primary);
line-height: 1;
display: flex;
justify-content: space-between;
}
.more { font-size: 12px; color: var(--brand-primary); text-decoration: none; }
.more:hover { text-decoration: underline; }
.notice-list { list-style: none; border-top: 1px solid #f0f0f0; }
.notice-item { padding: 12px 0; border-bottom: 1px solid #f0f0f0; display: flex; align-items: center; gap: 12px; }
.notice-item:last-child { border-bottom: none; }
.notice-item .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--brand-primary); flex-shrink: 0; }
.notice-item.read .dot { background: transparent; border: 1px solid #d9d9d9; }
.notice-item .body { flex: 1; min-width: 0; }
.notice-item .title { font-size: 13px; color: #1a1a1a; }
.notice-item.read .title { color: #8c8c8c; }
.notice-item .text { font-size: 12px; color: #595959; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
.notice-item .time { font-size: 12px; color: #bfbfbf; flex-shrink: 0; }
.empty { padding: 24px; text-align: center; color: #909399; }
</style>