feat: 本体字段治理与风险规则模板执行器重构

- 新增本体字段注册表与字段治理审计脚本
- 重构风险规则模板执行器、DSL 验证与清单分类器
- 完善票据夹服务与差旅请求详情页交互
- 优化趋势图表与总览页数据展示
- 增强报销平台风险分级与模拟公司筛选
- 补充本体字段、风险规则生成与票据夹服务测试覆盖
This commit is contained in:
caoxiaozhu
2026-06-03 15:46:56 +08:00
parent e12b140508
commit 34457f9c3e
81 changed files with 4858 additions and 1073 deletions

View File

@@ -126,9 +126,9 @@
</p>
</div>
<div v-if="!isApplicationDocument" class="detail-card-actions">
<button v-if="canOpenAiEntry" class="smart-entry-btn" type="button" @click="openAiEntry">
<button v-if="canOpenAiEntry" class="smart-entry-btn" type="button" :disabled="actionBusy" @click="triggerSmartEntryUpload">
<i class="mdi mdi-robot-outline"></i>
<span>智能录入</span>
<span>{{ uploadingExpenseId ? '识别中' : '智能录入' }}</span>
</button>
<button
v-if="isEditableRequest"
@@ -190,6 +190,10 @@
</div>
</div>
<div v-if="!isApplicationDocument" class="detail-expense-table">
<div v-if="smartEntryRecognitionBusy" class="expense-recognition-banner">
<i class="mdi mdi-loading mdi-spin"></i>
<span>{{ smartEntryRecognitionText }}</span>
</div>
<table>
<thead>
<tr>
@@ -199,6 +203,7 @@
<th class="col-desc">说明</th>
<th class="col-amount">金额</th>
<th class="col-attachment">附件材料</th>
<th class="col-risk-note">异常说明</th>
<th v-if="isEditableRequest" class="col-action">操作</th>
</tr>
</thead>
@@ -209,13 +214,17 @@
<strong>{{ item.filledAt }}</strong>
<span>条款填写时间</span>
</td>
<td :class="['expense-time col-time', { 'has-major-risk': isMajorExpenseRisk(item) }]">
<i
v-if="isMajorExpenseRisk(item)"
class="mdi mdi-alert expense-risk-indicator"
<td :class="['expense-time col-time', { 'has-major-risk': hasExpenseRiskIndicator(item) }]">
<button
v-if="hasExpenseRiskIndicator(item)"
class="expense-risk-indicator"
type="button"
:title="resolveExpenseRiskIndicatorTitle(item)"
:aria-label="resolveExpenseRiskIndicatorTitle(item)"
></i>
@click="focusExpenseRisk(item)"
>
<i class="mdi mdi-alert"></i>
</button>
<template v-if="editingExpenseId === item.id">
<div class="cell-editor">
<input v-model="expenseEditor.itemDate" class="editor-input" type="date" />
@@ -281,6 +290,10 @@
<td class="expense-attachment col-attachment">
<template v-if="editingExpenseId === item.id">
<div class="cell-editor editor-stack">
<div v-if="uploadingExpenseId === item.id" class="system-attachment-note pending">
<i class="mdi mdi-loading mdi-spin"></i>
<span>识别中</span>
</div>
<div class="attachment-action-group">
<button
v-if="isEditableRequest && !item.invoiceId && !item.isSystemGenerated"
@@ -318,7 +331,11 @@
</div>
</template>
<template v-else>
<div v-if="item.isSystemGenerated" class="system-attachment-note">
<div v-if="uploadingExpenseId === item.id" class="system-attachment-note pending">
<i class="mdi mdi-loading mdi-spin"></i>
<span>识别中</span>
</div>
<div v-else-if="item.isSystemGenerated" class="system-attachment-note">
<i class="mdi mdi-calculator-variant-outline"></i>
<span>无需附件</span>
</div>
@@ -358,6 +375,24 @@
</div>
</template>
</td>
<td class="expense-risk-note col-risk-note">
<template v-if="editingExpenseId === item.id">
<div class="cell-editor">
<textarea
v-model="expenseEditor.itemNote"
class="editor-textarea"
rows="3"
placeholder="如票据存在异常或风险,请补充原因"
></textarea>
<span>用于说明改签绕行超标票据异常等情况</span>
</div>
</template>
<template v-else>
<strong v-if="item.itemNote">{{ item.itemNote }}</strong>
<span v-else-if="hasExpenseRiskOrAbnormal(item)" class="risk-note-missing">待补充异常说明</span>
<span v-else>无异常说明</span>
</template>
</td>
<td v-if="isEditableRequest" class="expense-action-cell col-action">
<div v-if="item.isSystemGenerated" class="system-row-lock">
<i class="mdi mdi-lock-outline"></i>
@@ -438,7 +473,9 @@
<article
v-for="card in section.items"
:key="card.id"
:class="['risk-advice-card', card.tone]"
:id="resolveRiskCardDomId(card)"
:data-risk-card-id="card.id"
:class="['risk-advice-card', card.tone, { 'is-highlighted': isHighlightedRiskCard(card) }]"
>
<div class="risk-advice-card-main">
<div class="risk-advice-card-head">
@@ -545,6 +582,58 @@
accept="image/*,.pdf"
@change="handleExpenseFileChange"
/>
<input
ref="smartEntryUploadInput"
class="expense-upload-input"
type="file"
accept="image/*,.pdf"
multiple
@change="handleSmartEntryFileChange"
/>
<ConfirmDialog
:open="smartEntryUploadDialogOpen"
badge="智能录入"
title="上传报销附件"
description="请选择需要识别并归集到当前草稿的票据附件,确认前可以清除或重新选择。"
cancel-text="取消"
confirm-text="确认识别"
busy-text="识别中"
confirm-icon="mdi mdi-file-search-outline"
:busy="smartEntryUploadBusy"
@close="closeSmartEntryUploadDialog"
@confirm="confirmSmartEntryUpload"
>
<div class="smart-entry-upload-panel">
<button
class="smart-entry-upload-picker"
type="button"
:disabled="smartEntryUploadBusy"
@click="chooseSmartEntryFile"
>
<i class="mdi mdi-tray-arrow-up"></i>
<span>{{ smartEntrySelectedFileCount ? '重新选择附件' : '选择附件' }}</span>
</button>
<div class="smart-entry-upload-file">
<i :class="smartEntrySelectedFileCount ? 'mdi mdi-file-check-outline' : 'mdi mdi-file-outline'"></i>
<div>
<strong>{{ smartEntrySelectedFileSummary || '尚未选择附件' }}</strong>
<span>支持 JPGPNGPDF确认后系统会逐张识别并归集到草稿明细</span>
<ul v-if="smartEntrySelectedFileNames.length" class="smart-entry-upload-list">
<li v-for="fileName in smartEntrySelectedFileNames" :key="fileName">{{ fileName }}</li>
</ul>
</div>
<button
v-if="smartEntrySelectedFileCount"
class="smart-entry-upload-clear"
type="button"
:disabled="smartEntryUploadBusy"
@click="clearSmartEntryFile"
>
清除
</button>
</div>
</div>
</ConfirmDialog>
<Transition name="shared-confirm">
<div
v-if="attachmentPreviewOpen"