feat: 数字员工财务报告体系与定时提醒及看板快照调度
- 新增数字员工财务报告生成、邮件投递与渲染调度器 - 引入员工画像扫描调度与定时提醒任务 - 完善财务看板快照、排行口径与部门人员占比计算 - 优化数字员工工作看板仪表盘与技能目录 - 增强前端总览页图表、工作台摘要与顶部导航栏交互 - 新增差旅申请规划推动提醒与报销创建会话状态管理 - 补充财务报告、看板调度、数字员工工作记录测试覆盖
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<header class="topbar" :class="{ 'chat-mode': isChat }">
|
||||
<header class="topbar" :class="{ 'chat-mode': isChat, 'detail-mode': isRequestDetail }">
|
||||
<div class="title-group">
|
||||
<div class="eyebrow">{{ eyebrowLabel }}</div>
|
||||
<h1>{{ currentView.title }}</h1>
|
||||
@@ -121,12 +121,73 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="isWorkbench">
|
||||
<template v-else-if="isWorkbench">
|
||||
<div class="topbar-toolset" aria-label="工作台快捷工具">
|
||||
<button class="topbar-icon-btn notification-btn" type="button" aria-label="通知">
|
||||
<i class="mdi mdi-bell-outline"></i>
|
||||
<span v-if="topbarNotificationCount" class="notification-badge">{{ topbarNotificationCount }}</span>
|
||||
</button>
|
||||
<div class="notification-wrap">
|
||||
<button
|
||||
class="topbar-icon-btn notification-btn"
|
||||
type="button"
|
||||
aria-label="通知"
|
||||
:aria-expanded="notificationOpen"
|
||||
aria-haspopup="dialog"
|
||||
@click="notificationOpen = !notificationOpen"
|
||||
>
|
||||
<i class="mdi mdi-bell-outline"></i>
|
||||
<span v-if="topbarNotificationCount" class="notification-badge">{{ topbarNotificationCount }}</span>
|
||||
</button>
|
||||
|
||||
<div v-if="notificationOpen" class="notification-popover" role="dialog" aria-label="通知中心">
|
||||
<header class="notification-head">
|
||||
<strong>通知</strong>
|
||||
<button type="button" aria-label="关闭通知" @click="notificationOpen = false">
|
||||
<i class="mdi mdi-close"></i>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div class="notification-tabs" role="tablist" aria-label="通知状态">
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="notificationTab === 'unread'"
|
||||
:class="{ active: notificationTab === 'unread' }"
|
||||
@click="notificationTab = 'unread'"
|
||||
>
|
||||
未读 {{ unreadNotifications.length }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="notificationTab === 'read'"
|
||||
:class="{ active: notificationTab === 'read' }"
|
||||
@click="notificationTab = 'read'"
|
||||
>
|
||||
已读 {{ readNotifications.length }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="activeNotifications.length" class="notification-list">
|
||||
<button
|
||||
v-for="item in activeNotifications"
|
||||
:key="item.id"
|
||||
type="button"
|
||||
class="notification-row"
|
||||
@click="openNotification(item)"
|
||||
>
|
||||
<span class="notification-dot" :class="item.tone"></span>
|
||||
<span class="notification-copy">
|
||||
<strong>{{ item.title }}</strong>
|
||||
<small>{{ item.description }}</small>
|
||||
<em>{{ item.time }}</em>
|
||||
</span>
|
||||
<i class="mdi mdi-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="notification-empty">
|
||||
<i class="mdi mdi-bell-check-outline"></i>
|
||||
<span>{{ notificationTab === 'unread' ? '暂无未读通知' : '暂无已读通知' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="topbar-icon-btn" type="button" aria-label="帮助">
|
||||
<i class="mdi mdi-help-circle-outline"></i>
|
||||
@@ -243,6 +304,10 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
workbenchSummary: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
companyName: {
|
||||
type: String,
|
||||
default: ''
|
||||
@@ -276,7 +341,8 @@ const emit = defineEmits([
|
||||
'update:overviewDashboard',
|
||||
'batchApprove',
|
||||
'openChat',
|
||||
'newApplication'
|
||||
'newApplication',
|
||||
'openDocument'
|
||||
])
|
||||
const isChat = computed(() => props.activeView === 'chat')
|
||||
const isOverview = computed(() => props.activeView === 'overview')
|
||||
@@ -294,10 +360,34 @@ const eyebrowLabel = computed(() => (
|
||||
))
|
||||
const displayCompanyName = computed(() => String(props.companyName || '远光软件股份有限公司').trim() || '远光软件股份有限公司')
|
||||
const topbarNotificationCount = computed(() => {
|
||||
const summary = props.documentSummary ?? {}
|
||||
const count = Number(summary.toProcess ?? summary.toSubmit ?? 8)
|
||||
const summary = props.workbenchSummary ?? {}
|
||||
const count = Number(summary.unreadNotificationCount ?? 0)
|
||||
return Number.isFinite(count) && count > 0 ? Math.min(count, 99) : 0
|
||||
})
|
||||
})
|
||||
const notificationOpen = ref(false)
|
||||
const notificationTab = ref('unread')
|
||||
const notificationItems = computed(() => (
|
||||
Array.isArray(props.workbenchSummary?.notifications)
|
||||
? props.workbenchSummary.notifications
|
||||
: []
|
||||
))
|
||||
const unreadNotifications = computed(() => notificationItems.value.filter((item) => item.unread))
|
||||
const readNotifications = computed(() => notificationItems.value.filter((item) => !item.unread))
|
||||
const activeNotifications = computed(() => (
|
||||
notificationTab.value === 'unread' ? unreadNotifications.value : readNotifications.value
|
||||
))
|
||||
|
||||
function openNotification(item) {
|
||||
notificationOpen.value = false
|
||||
const target = item?.target || {}
|
||||
if (target.type === 'document' && (target.id || target.claimNo)) {
|
||||
emit('openDocument', {
|
||||
claimId: target.id,
|
||||
id: target.id || target.claimNo,
|
||||
claimNo: target.claimNo
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const requestKpis = computed(() => {
|
||||
const summary = props.requestSummary ?? {}
|
||||
|
||||
Reference in New Issue
Block a user