feat(dashboard): polish risk and digital employee boards

This commit is contained in:
caoxiaozhu
2026-06-03 09:41:32 +08:00
parent 15006a05a7
commit 0d6327a990
11 changed files with 716 additions and 100 deletions

233
web/src/utils/riskLabels.js Normal file
View File

@@ -0,0 +1,233 @@
const RISK_SIGNAL_LABELS = {
duplicate_invoice: '重复发票',
split_billing: '拆分报销',
frequent_small_claims: '高频小额报销',
location_mismatch: '地点不一致',
amount_outlier: '金额异常',
preapproval_absent: '缺少事前申请',
travel_city_consistency: '差旅城市一致性',
travel_route_city_consistency: '差旅路线一致性',
hotel_over_limit: '住宿超标',
hotel_amount_missing: '住宿金额待补充',
travel_meal_amount_missing: '餐饮金额待补充',
travel_meal_allowance_over_limit: '餐补超标',
transport_class_over_limit: '交通舱等超标',
travel_type_uncertain: '差旅类型待确认',
invoice_missing: '票据缺失',
attachment_missing: '附件缺失',
amount_missing: '金额待补充',
policy_missing: '制度依据缺失',
budget_overrun: '预算超支',
suspicious_supplier: '供应商异常',
abnormal_frequency: '频次异常',
abnormal_amount: '金额异常',
manual_review: '人工复核',
unknown: '未知风险'
}
const EXPENSE_TYPE_LABELS = {
travel: '差旅费',
transport: '交通费',
hotel: '住宿费',
meal: '餐饮费',
office: '办公费',
entertainment: '招待费',
training: '培训费',
communication: '通讯费',
taxi_receipt: '出租车票据',
parking_toll_receipt: '停车通行票据',
transport_receipt: '交通票据',
train_ticket: '火车票',
flight_itinerary: '机票行程单',
hotel_invoice: '住宿发票',
meal_receipt: '餐饮票据',
travel_ticket: '差旅票据',
travel_allowance: '差旅补贴',
other: '其他费用',
unknown: '未知费用'
}
const RISK_SOURCE_LABELS = {
financial_risk_graph: '风险图谱',
rule_center: '规则中心',
user_feedback: '用户反馈',
manual_review: '人工复核',
system_scan: '系统扫描',
onlyoffice: '文档协同',
onlyoffice_preview: '文档预览',
unknown: '未知来源'
}
const RISK_STATUS_LABELS = {
pending_review: '待复核',
pending: '待处理',
confirmed: '已确认',
false_positive: '误报',
resolved: '已处理',
ignored: '已忽略',
closed: '已关闭',
unknown: '未知状态'
}
const RISK_LEVEL_LABELS = {
critical: '重大',
high: '高',
medium: '中',
low: '低',
none: '无',
unknown: '未知'
}
const RULE_LABELS = {
'policy.duplicate_invoice': '重复发票规则',
'policy.split_billing': '拆分报销规则',
'policy.frequent_small_claims': '高频小额规则',
'policy.location_mismatch': '地点一致性规则',
'policy.amount_outlier': '金额异常规则',
'policy.preapproval_absent': '事前申请规则',
'rule.expense.travel_risk_control_standard': '差旅风险控制规则',
'rule.expense.travel_receipt_requirements': '差旅票据要求',
'rule.expense.company_travel_expense_reimbursement': '公司差旅费报销规则'
}
const UNKNOWN_LABELS = {
department: '未归集部门',
expense_type: '未归集费用',
risk_type: '未知风险类型',
supplier: '未归集供应商',
grade: '未归集职级',
employee: '未归集员工',
rule: '未关联规则',
source: '未知来源',
status: '未知状态'
}
const LATIN_PATTERN = /[A-Za-z]/
function normalizeText(value) {
return String(value || '').trim()
}
function normalizeKey(value) {
return normalizeText(value)
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
.replace(/[\s-]+/g, '_')
.replace(/\/+/g, '.')
.toLowerCase()
}
function lastKeySegment(key) {
const parts = String(key || '').split(/[.:]/).filter(Boolean)
return parts[parts.length - 1] || key
}
function isMissingValue(text) {
return !text || ['unknown', '待补充', '待确认', '未归属部门', '未归属', 'N/A', 'n/a', '-'].includes(text)
}
function fallbackVisibleText(text, fallback) {
if (!text) {
return fallback
}
return LATIN_PATTERN.test(text) ? fallback : text
}
export function formatRiskSignalLabel(value, fallback = '未知风险') {
const text = normalizeText(value)
if (isMissingValue(text)) {
return fallback
}
const key = normalizeKey(text)
const direct = RISK_SIGNAL_LABELS[key] || RISK_SIGNAL_LABELS[lastKeySegment(key)]
if (direct) {
return direct
}
if (key.startsWith('policy.') || key.startsWith('rule.')) {
return formatRiskRuleLabel(text)
}
return fallbackVisibleText(text, fallback)
}
export function formatExpenseTypeLabel(value, fallback = '未知费用') {
const text = normalizeText(value)
if (isMissingValue(text)) {
return fallback
}
const key = normalizeKey(text)
return EXPENSE_TYPE_LABELS[key] || EXPENSE_TYPE_LABELS[lastKeySegment(key)] || fallbackVisibleText(text, fallback)
}
export function formatRiskSourceLabel(value, fallback = '未知来源') {
const text = normalizeText(value)
if (isMissingValue(text)) {
return fallback
}
const key = normalizeKey(text)
return RISK_SOURCE_LABELS[key] || RISK_SOURCE_LABELS[lastKeySegment(key)] || fallbackVisibleText(text, fallback)
}
export function formatRiskStatusLabel(value, fallback = '未知状态') {
const text = normalizeText(value)
if (isMissingValue(text)) {
return fallback
}
const key = normalizeKey(text)
return RISK_STATUS_LABELS[key] || RISK_STATUS_LABELS[lastKeySegment(key)] || fallbackVisibleText(text, fallback)
}
export function formatRiskLevelLabel(value, fallback = '未知') {
const text = normalizeText(value)
if (isMissingValue(text)) {
return fallback
}
const key = normalizeKey(text)
return RISK_LEVEL_LABELS[key] || fallbackVisibleText(text, fallback)
}
export function formatRiskRuleLabel(value, fallback = '规则指标') {
const text = normalizeText(value)
if (isMissingValue(text)) {
return '未关联规则'
}
const key = normalizeKey(text)
if (RULE_LABELS[key]) {
return RULE_LABELS[key]
}
const suffix = lastKeySegment(key)
const signalLabel = RISK_SIGNAL_LABELS[suffix]
if (signalLabel) {
return `${signalLabel}规则`
}
return fallbackVisibleText(text, fallback)
}
export function formatRiskDimensionLabel(value, kind = '') {
const text = normalizeText(value)
if (isMissingValue(text)) {
return UNKNOWN_LABELS[kind] || '未知'
}
if (kind === 'risk_type') {
return formatRiskSignalLabel(text, '未知风险类型')
}
if (kind === 'expense_type') {
return formatExpenseTypeLabel(text)
}
if (kind === 'rule') {
return formatRiskRuleLabel(text)
}
if (kind === 'source') {
return formatRiskSourceLabel(text)
}
if (kind === 'status') {
return formatRiskStatusLabel(text)
}
return text
}
export function formatRiskObservationTitle(item = {}) {
const title = normalizeText(item.title)
if (title && !LATIN_PATTERN.test(title)) {
return title
}
return formatRiskSignalLabel(item.riskSignal || item.riskType || title)
}