feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造

- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制
- 引入费用审批动态路由、平台风险分级、预审与风险阶段管理
- 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板
- 新增 Hermes 风险线索收集器、Agent 链路追踪中心
- 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估
- 完善报销申请快速预览、权限控制与前端测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-01 17:07:14 +08:00
parent 7989f3a159
commit 92444e7eae
285 changed files with 25075 additions and 2986 deletions

View File

@@ -100,6 +100,28 @@
</div>
</div>
<div
v-if="buildFieldPipelineSections(message.result).length"
class="risk-sim-field-pipeline"
>
<section
v-for="section in buildFieldPipelineSections(message.result)"
:key="section.key"
>
<header>
<span>{{ section.title }}</span>
<small>{{ section.description }}</small>
</header>
<ul>
<li v-for="field in section.rows" :key="field.key">
<strong>{{ field.label }}</strong>
<em>{{ field.source }}</em>
<b>{{ field.value }}</b>
</li>
</ul>
</section>
</div>
<div
v-if="buildRecognizedFieldRows(message.result).length"
class="risk-sim-recognized-fields"
@@ -254,6 +276,12 @@
<p>{{ boundaryDescription }}</p>
</section>
<section>
<span>测试报告</span>
<strong>{{ testReportTitle }}</strong>
<p>{{ testReportDescription }}</p>
</section>
<section>
<span>使用字段</span>
<div class="risk-sim-field-list">
@@ -311,6 +339,7 @@ import {
import {
buildDocumentBrief,
buildEvidenceItems as buildEvidenceItemsModel,
buildFieldPipelineSections as buildFieldPipelineSectionsModel,
buildRecognizedFieldRows as buildRecognizedFieldRowsModel,
buildResultFields as buildResultFieldsModel,
buildTraceItems as buildTraceItemsModel,
@@ -419,6 +448,15 @@ const lastSimulationHint = computed(() => {
? `最近一次仿真:命中${activeSimulationResult.value.severity_label}`
: '最近一次仿真:未命中风险'
})
const testReportTitle = computed(() => latestSummary.value?.test_passed ? '已确认测试通过' : '待确认测试结论')
const testReportDescription = computed(() => {
const summary = latestSummary.value
if (!summary) return '暂无测试报告。完成一次仿真后,可点击底部按钮确认测试通过。'
if (summary.report?.summary) return summary.report.summary
if (summary.sample?.summary) return `样例复核:${summary.sample.summary}`
if (summary.scenario?.summary) return `场景试运行:${summary.scenario.summary}`
return summary.test_passed ? '测试结论已保存。' : '暂无通过结论。'
})
watch(
() => props.open,
@@ -666,6 +704,10 @@ function buildRecognizedFieldRows(result) {
return buildRecognizedFieldRowsModel(result, fields.value)
}
function buildFieldPipelineSections(result) {
return buildFieldPipelineSectionsModel(result, fields.value)
}
function buildEvidenceItems(result) {
return buildEvidenceItemsModel(result, fields.value)
}

View File

@@ -32,6 +32,30 @@ export function buildRecognizedFieldRows(result, fields = []) {
}))
}
export function buildFieldPipelineSections(result, fields = []) {
const sections = [
{
key: 'ocr',
title: 'OCR 原始字段',
description: '临时附件识别得到的原始文本和结构化字段。',
rows: buildPipelineRows(result?.ocr_raw_fields, fields, { showAttachment: true })
},
{
key: 'hermes',
title: 'Hermes 规范化字段',
description: '合并测试意图和附件后,映射到规则字段本体。',
rows: buildPipelineRows(result?.hermes_normalized_fields, fields)
},
{
key: 'executor',
title: '执行器实际输入',
description: '最终交给规则执行器参与判断的字段值。',
rows: buildPipelineRows(result?.executor_input_fields, fields, { showRequired: true })
}
]
return sections.filter((section) => section.rows.length)
}
export function buildEvidenceItems(result, fields = []) {
const evidence = result?.evidence && typeof result.evidence === 'object'
? result.evidence
@@ -111,6 +135,26 @@ function formatRecognitionSource(source) {
}[String(source || '').trim()] || '未标注来源'
}
function buildPipelineRows(rows, fields, options = {}) {
return (Array.isArray(rows) ? rows : []).slice(0, 16).map((field, index) => {
const key = String(field?.key || `field-${index}`).trim()
const sourceLabel = String(field?.source_label || '').trim() || formatRecognitionSource(field?.source)
const suffixes = [
options.showAttachment ? String(field?.attachment_name || '').trim() : '',
options.showRequired && field?.required ? '判断必需' : ''
].filter(Boolean)
return {
key: `${key}-${index}`,
label: formatFieldLabel(fields.find((item) => item.key === key) || {
key,
label: field?.label
}),
source: suffixes.length ? `${sourceLabel} · ${suffixes.join(' · ')}` : sourceLabel,
value: formatDebugValue(field?.value)
}
})
}
function formatDebugValue(value) {
if (Array.isArray(value)) return value.map((item) => String(item ?? '')).filter(Boolean).join('、') || '-'
if (value && typeof value === 'object') return JSON.stringify(value)