feat: 新增员工行为画像算法与费用风险标签体系

后端新增员工行为画像算法模块,支持标签规则引擎和评分计算,
完善员工模型、银行信息、序列化和导入逻辑,优化报销审批流
和工作流常量,增强 Hermes 同步和知识同步能力,前端新增费
用画像详情弹窗、雷达图和风险卡片组件,完善登录页和工作台
样式,优化文档中心和归档中心交互,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-28 12:09:49 +08:00
parent 04cd6d0f81
commit 8a4a777be7
96 changed files with 9835 additions and 704 deletions

View File

@@ -98,14 +98,20 @@
</template>
<template v-else-if="isWorkbench">
<div class="kpi-chips">
<div v-for="kpi in workbenchKpis" :key="kpi.label" class="kpi-chip" :style="{ '--chip-color': kpi.color }">
<span class="chip-value">
{{ kpi.value }}<small v-if="kpi.unit">{{ kpi.unit }}</small>
</span>
<span class="chip-label">{{ kpi.label }}</span>
<span class="chip-delta" :class="kpi.trend">{{ kpi.meta }}</span>
</div>
<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>
<button class="topbar-icon-btn" type="button" aria-label="帮助">
<i class="mdi mdi-help-circle-outline"></i>
</button>
<button class="company-switcher" type="button" aria-label="切换公司">
<span>{{ displayCompanyName }}</span>
<i class="mdi mdi-chevron-down"></i>
</button>
</div>
</template>
@@ -206,9 +212,9 @@ const props = defineProps({
type: Object,
default: () => null
},
workbenchSummary: {
type: Object,
default: () => null
companyName: {
type: String,
default: ''
},
detailMode: {
type: Boolean,
@@ -247,48 +253,11 @@ const isLogs = computed(() => props.activeView === 'logs' && !props.logDetailMod
const isApproval = computed(() => props.activeView === 'approval')
const isPolicies = computed(() => props.activeView === 'policies')
const isEmployees = computed(() => props.activeView === 'employees')
const workbenchKpis = computed(() => {
const summary = props.workbenchSummary ?? {}
const monthlyCount = Number(summary.monthlyCount ?? 0)
const returnCount = Number(summary.returnCount ?? 0)
const highRiskCount = Number(summary.highRiskCount ?? 0)
const monthlyAmountLabel = String(summary.monthlyAmountLabel || '¥0')
return [
{
label: '本月报销笔数',
value: monthlyCount,
unit: '笔',
meta: '本月累计',
trend: monthlyCount > 0 ? 'up' : 'down',
color: 'var(--theme-primary)'
},
{
label: '本月报销总金额',
value: monthlyAmountLabel,
unit: '',
meta: '本月累计',
trend: monthlyCount > 0 ? 'up' : 'down',
color: '#3b82f6'
},
{
label: '退单次数',
value: returnCount,
unit: '次',
meta: '累计退回',
trend: returnCount > 0 ? 'down' : 'up',
color: '#f59e0b'
},
{
label: '高危风险次数',
value: highRiskCount,
unit: '次',
meta: highRiskCount > 0 ? '本月需关注' : '本月无高危',
trend: highRiskCount > 0 ? 'down' : 'up',
color: '#ef4444'
}
]
const displayCompanyName = computed(() => String(props.companyName || '远光软件股份有限公司').trim() || '远光软件股份有限公司')
const topbarNotificationCount = computed(() => {
const summary = props.documentSummary ?? {}
const count = Number(summary.toProcess ?? summary.toSubmit ?? 8)
return Number.isFinite(count) && count > 0 ? Math.min(count, 99) : 0
})
const requestKpis = computed(() => {