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

@@ -16,7 +16,6 @@
:current-page="currentPage"
:page-size="pageSize"
:page-size-options="pageSizeOptions"
:pages="pageNumbers"
:show-page-size="true"
:summary="paginationSummary"
:total="visibleSkills.length"
@@ -326,14 +325,6 @@ const pagedSkills = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return props.visibleSkills.slice(start, start + pageSize.value)
})
const pageNumbers = computed(() => {
const total = totalPages.value
if (total <= 7) {
return Array.from({ length: total }, (_, index) => index + 1)
}
const start = Math.max(1, Math.min(currentPage.value - 3, total - 6))
return Array.from({ length: 7 }, (_, index) => start + index)
})
const paginationSummary = computed(() =>
`共 ${props.visibleSkills.length} 条,每页 ${pageSize.value} 条,当前第 ${currentPage.value} / ${totalPages.value} 页`
)

View File

@@ -10,7 +10,6 @@
:current-page="currentPage"
:page-size="pageSize"
:page-size-options="pageSizeOptions"
:pages="pageNumbers"
:show-page-size="true"
:summary="paginationSummary"
:total="visibleEmployees.length"
@@ -225,14 +224,6 @@ const pagedEmployees = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
return props.visibleEmployees.slice(start, start + pageSize.value)
})
const pageNumbers = computed(() => {
const total = totalPages.value
if (total <= 7) {
return Array.from({ length: total }, (_, index) => index + 1)
}
const start = Math.max(1, Math.min(currentPage.value - 3, total - 6))
return Array.from({ length: 7 }, (_, index) => start + index)
})
const paginationSummary = computed(() =>
`共 ${props.visibleEmployees.length} 条,每页 ${pageSize.value} 条,目前第 ${currentPage.value} / ${totalPages.value} 页`
)

View File

@@ -110,6 +110,26 @@
<p v-else class="run-product-inline-empty">本次运行没有生成新的风险观察</p>
</section>
<section v-else-if="productKind === 'finance_snapshot'" class="run-product-section">
<div class="run-product-section-head">
<h4>财务经营快照</h4>
<span>{{ summary.period || summary.month || '本期' }}</span>
</div>
<p class="run-product-copy">
本次产物已刷新财务看板缓存沉淀报销金额预算使用费用结构和高额单据等经营指标
</p>
</section>
<section v-else-if="productKind === 'reminder_scan'" class="run-product-section">
<div class="run-product-section-head">
<h4>提醒与待办沉淀</h4>
<span>{{ summary.reminder_count || summary.reminders || 0 }} </span>
</div>
<p class="run-product-copy">
本次产物已生成审批提醒预算编制提醒报销逾期提醒和差旅申请闭环提醒
</p>
</section>
<section v-else-if="productKind === 'risk_clue'" class="run-product-section">
<div class="run-product-section-head">
<h4>待复核线索</h4>
@@ -230,6 +250,12 @@ const productSubtitle = computed(() => {
if (productKind.value === 'risk_graph') {
return '展示本次巡检生成的风险观察、证据数量和图谱关系计数。'
}
if (productKind.value === 'finance_snapshot') {
return '展示本次财务经营快照沉淀的预算、费用和报销统计。'
}
if (productKind.value === 'reminder_scan') {
return '展示本次定时提醒扫描生成的待办和触达结果。'
}
if (productKind.value === 'employee_profile') {
return '展示本次画像巡检写入的员工画像快照摘要。'
}
@@ -245,6 +271,12 @@ const productBadge = computed(() => {
if (productKind.value === 'risk_graph') {
return '风险观察'
}
if (productKind.value === 'finance_snapshot') {
return '财务快照'
}
if (productKind.value === 'reminder_scan') {
return '提醒事项'
}
if (productKind.value === 'employee_profile') {
return '画像快照'
}
@@ -281,6 +313,25 @@ const metrics = computed(() => {
buildMetric('图谱关系', payload.graph_edge_count)
]
}
if (productKind.value === 'finance_snapshot') {
return [
buildMetric('报销单数', payload.claim_count ?? payload.claims ?? payload.total_claims),
buildMetric(
'报销金额',
formatMoney(payload.claim_amount ?? payload.reimbursement_amount ?? payload.total_amount)
),
buildMetric('预算使用率', formatPercent(payload.budget_usage_rate ?? payload.budget_rate)),
buildMetric('高额单据', payload.high_value_claim_count ?? payload.high_amount_claims)
]
}
if (productKind.value === 'reminder_scan') {
return [
buildMetric('提醒人数', payload.recipient_count),
buildMetric('提醒事项', payload.reminder_count),
buildMetric('待审批', payload.approval_pending_count),
buildMetric('逾期报销', payload.reimbursement_overdue_count)
]
}
if (productKind.value === 'employee_profile') {
return [
buildMetric('目标员工', payload.target_employee_count),
@@ -376,6 +427,23 @@ function formatWindowDays(value) {
return days.length ? days.map((item) => `${item}`).join(' / ') : '-'
}
function formatMoney(value) {
const amount = Number(value)
if (!Number.isFinite(amount)) {
return '-'
}
return `¥${amount.toLocaleString('zh-CN', { maximumFractionDigits: 0 })}`
}
function formatPercent(value) {
const numericValue = Number(value)
if (!Number.isFinite(numericValue)) {
return '-'
}
const percent = numericValue > 1 ? numericValue : numericValue * 100
return `${Math.round(percent)}%`
}
function observationGraphCount(item) {
return (item.graphNodeKeys || []).length + (item.graphEdgeKeys || []).length
}

View File

@@ -14,7 +14,6 @@
:show-pagination="!loading && !errorMessage && visibleRuns.length > 0"
:current-page="currentPage"
:page-size="pageSize"
:pages="pageNumbers"
:show-page-size="false"
:summary="paginationSummary"
:total="filteredRuns.length"
@@ -303,6 +302,7 @@ import {
import {
formatWorkRecordDateTime,
formatWorkRecordSummary,
compactDigitalEmployeeWorkRecords,
resolveWorkRecordModuleLabel,
resolveWorkRecordSourceLabel,
resolveWorkRecordStatusLabel,
@@ -456,14 +456,6 @@ const visibleRuns = computed(() => {
return filteredRuns.value.slice(start, start + pageSize)
})
const pageNumbers = computed(() => {
const total = totalPages.value
if (total <= 7) {
return Array.from({ length: total }, (_, index) => index + 1)
}
const start = Math.max(1, Math.min(currentPage.value - 3, total - 6))
return Array.from({ length: 7 }, (_, index) => start + index)
})
const paginationSummary = computed(() =>
`共 ${filteredRuns.value.length} 条,目前第 ${currentPage.value} / ${totalPages.value} 页`
)
@@ -523,7 +515,7 @@ async function loadWorkRecords(showToast = false) {
try {
const payload = await fetchAgentRuns({ agent: 'hermes', limit: 100 })
runs.value = Array.isArray(payload) ? payload : []
runs.value = Array.isArray(payload) ? compactDigitalEmployeeWorkRecords(payload) : []
emit('summary-change', {
total: workRecordSummary.value.total,
succeeded: workRecordSummary.value.succeeded,