feat: 数字员工财务报告体系与定时提醒及看板快照调度

- 新增数字员工财务报告生成、邮件投递与渲染调度器
- 引入员工画像扫描调度与定时提醒任务
- 完善财务看板快照、排行口径与部门人员占比计算
- 优化数字员工工作看板仪表盘与技能目录
- 增强前端总览页图表、工作台摘要与顶部导航栏交互
- 新增差旅申请规划推动提醒与报销创建会话状态管理
- 补充财务报告、看板调度、数字员工工作记录测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-03 09:25:23 +08:00
parent 0c74b4ab4a
commit 15006a05a7
114 changed files with 7356 additions and 650 deletions

View File

@@ -1,4 +1,4 @@
import { computed, onMounted, ref, watch } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import {
fetchDigitalEmployeeDashboard,
@@ -92,6 +92,9 @@ export function useOverviewView(options = {}) {
const riskDashboardPayload = ref(null)
const riskDashboardLoading = ref(false)
const riskDashboardError = ref(null)
const riskDashboardLastUpdatedAt = ref('')
let riskDashboardRefreshTimer = 0
let riskDashboardRequestSeq = 0
const digitalEmployeeDashboardPayload = ref(null)
const digitalEmployeeDashboardLoading = ref(false)
const digitalEmployeeDashboardError = ref(null)
@@ -178,22 +181,53 @@ export function useOverviewView(options = {}) {
}
const loadRiskDashboard = async () => {
const requestSeq = ++riskDashboardRequestSeq
riskDashboardLoading.value = true
riskDashboardError.value = null
try {
riskDashboardPayload.value = await fetchRiskObservationDashboard({
const payload = await fetchRiskObservationDashboard({
windowDays: activeRiskWindowDays.value,
limit: 500
})
if (requestSeq !== riskDashboardRequestSeq) {
return
}
riskDashboardPayload.value = payload
riskDashboardLastUpdatedAt.value = new Date().toISOString()
} catch (error) {
if (requestSeq !== riskDashboardRequestSeq) {
return
}
riskDashboardPayload.value = null
riskDashboardError.value = error
} finally {
riskDashboardLoading.value = false
if (requestSeq === riskDashboardRequestSeq) {
riskDashboardLoading.value = false
}
}
}
const startRiskDashboardRealtimeRefresh = () => {
if (riskDashboardRefreshTimer) {
window.clearInterval(riskDashboardRefreshTimer)
}
riskDashboardRefreshTimer = window.setInterval(() => {
if (document.visibilityState === 'hidden' || riskDashboardLoading.value) {
return
}
void loadRiskDashboard()
}, 30_000)
}
const stopRiskDashboardRealtimeRefresh = () => {
if (!riskDashboardRefreshTimer) {
return
}
window.clearInterval(riskDashboardRefreshTimer)
riskDashboardRefreshTimer = 0
}
const loadDigitalEmployeeDashboard = async () => {
digitalEmployeeDashboardLoading.value = true
digitalEmployeeDashboardError.value = null
@@ -222,6 +256,11 @@ export function useOverviewView(options = {}) {
void loadSystemDashboard()
void loadRiskDashboard()
void loadDigitalEmployeeDashboard()
startRiskDashboardRealtimeRefresh()
})
onBeforeUnmount(() => {
stopRiskDashboardRealtimeRefresh()
})
watch(
@@ -323,6 +362,9 @@ export function useOverviewView(options = {}) {
const financeDepartmentRanking = computed(() => (
financeDashboardPayload.value?.departmentRanking || []
))
const financeDepartmentEmployeeMix = computed(() => (
financeDashboardPayload.value?.departmentEmployeeMix || emptyFinanceDonut
))
const financeEmployeeRanking = computed(() => (
financeDashboardPayload.value?.employeeRanking || []
))
@@ -501,7 +543,11 @@ export function useOverviewView(options = {}) {
const activeTrend = computed(() => financeTrend.value)
const spendTotal = computed(() => financeSpendByCategory.value.reduce((sum, item) => sum + Number(item.value || 0), 0))
const riskTotal = computed(() => financeExceptionMix.value.reduce((sum, item) => sum + Number(item.value || 0), 0))
const departmentEmployeeTotal = computed(() => (
financeDepartmentEmployeeMix.value.reduce((sum, item) => sum + Number(item.value || item.amount || 0), 0)
))
const spendCenterValue = computed(() => formatCurrency(Math.round(spendTotal.value)))
const departmentEmployeeCenterValue = computed(() => formatCurrency(Math.round(departmentEmployeeTotal.value)))
const spendLegend = computed(() => financeSpendByCategory.value.map((item) => ({
...item,
@@ -513,6 +559,14 @@ export function useOverviewView(options = {}) {
display: `${item.value}`
})))
const departmentEmployeeLegend = computed(() => financeDepartmentEmployeeMix.value.map((item) => ({
...item,
value: Number(item.value || item.amount || 0),
display: departmentEmployeeTotal.value
? `${Math.round((Number(item.value || item.amount || 0) / departmentEmployeeTotal.value) * 100)}%`
: '0%'
})))
const systemToolTotal = computed(() =>
systemToolCallMix.reduce((sum, item) => sum + item.value, 0)
)
@@ -542,6 +596,7 @@ export function useOverviewView(options = {}) {
rank: index + 1,
shortName: item.name,
amountLabel: formatCurrency(item.amount),
meta: `${Number(item.employeeCount || 0)} 人 / ${Number(item.count || 0)}`,
width: `${Math.max((item.amount / max) * 100, 18)}%`,
color: item.color
}))
@@ -561,6 +616,7 @@ export function useOverviewView(options = {}) {
rank: index + 1,
shortName: item.name,
amountLabel: formatCurrency(item.amount),
meta: `${item.department || '未归属部门'} / ${Number(item.count || 0)}`,
width: `${Math.max((item.amount / max) * 100, 18)}%`,
color: item.color
}))
@@ -738,6 +794,8 @@ export function useOverviewView(options = {}) {
bottlenecks,
budgetMetrics,
budgetSummary,
departmentEmployeeCenterValue,
departmentEmployeeLegend,
departmentRangeOptions,
digitalEmployeeCategoryRows,
digitalEmployeeDashboard,
@@ -760,6 +818,7 @@ export function useOverviewView(options = {}) {
rankedEmployees,
riskDashboard,
riskDashboardError,
riskDashboardLastUpdatedAt,
riskDashboardLoading,
riskDailyTrendRows,
riskLegend,