后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL 校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计, 优化 agent 运行和编排执行链路,清理旧开发文档,前端新增 系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈 对话框和工作台日期选择器,优化报销创建和审批详情交互, 补充单元测试覆盖。
225 lines
7.1 KiB
JavaScript
225 lines
7.1 KiB
JavaScript
import { computed } from 'vue'
|
|
|
|
const TRAIN_KEY_FIELD_DEFINITIONS = [
|
|
{
|
|
id: 'invoice_number',
|
|
label: '发票号码',
|
|
placeholder: '待识别',
|
|
keys: ['invoice_number', 'ticket_number'],
|
|
labels: ['发票号码', '票据号码', '票号']
|
|
},
|
|
{
|
|
id: 'invoice_date',
|
|
label: '开票日期',
|
|
placeholder: 'YYYY-MM-DD',
|
|
keys: ['invoice_date', 'issue_date'],
|
|
labels: ['开票日期', '发票日期']
|
|
},
|
|
{
|
|
id: 'fare',
|
|
label: '票价',
|
|
placeholder: '待识别',
|
|
keys: ['fare', 'amount'],
|
|
labels: ['票价', '金额']
|
|
},
|
|
{
|
|
id: 'passenger_name',
|
|
label: '姓名',
|
|
placeholder: '待识别',
|
|
keys: ['passenger_name'],
|
|
labels: ['乘车人', '旅客姓名', '姓名']
|
|
}
|
|
]
|
|
|
|
const DEFAULT_KEY_FIELD_DEFINITIONS = [
|
|
{
|
|
id: 'invoice_number',
|
|
label: '发票号码',
|
|
placeholder: '待识别',
|
|
keys: ['invoice_number', 'ticket_number'],
|
|
labels: ['发票号码', '票据号码', '票号']
|
|
},
|
|
{
|
|
id: 'invoice_date',
|
|
label: '开票日期',
|
|
placeholder: 'YYYY-MM-DD',
|
|
keys: ['invoice_date', 'issue_date'],
|
|
labels: ['开票日期', '发票日期']
|
|
},
|
|
{
|
|
id: 'amount',
|
|
label: '金额',
|
|
placeholder: '待识别',
|
|
keys: ['amount', 'fare'],
|
|
labels: ['金额', '价税合计', '合计金额', '票价']
|
|
},
|
|
{
|
|
id: 'merchant_name',
|
|
label: '商户',
|
|
placeholder: '待识别',
|
|
keys: ['merchant_name'],
|
|
labels: ['商户', '销售方', '开票方']
|
|
}
|
|
]
|
|
|
|
const RECEIPT_META_FIELD_DEFINITIONS = [
|
|
{
|
|
id: 'document_type_label',
|
|
label: '票据类型',
|
|
placeholder: '待识别',
|
|
keys: ['document_type_label'],
|
|
labels: ['票据类型', '识别类型']
|
|
},
|
|
{
|
|
id: 'scene_label',
|
|
label: '费用场景',
|
|
placeholder: '待识别',
|
|
keys: ['scene_label'],
|
|
labels: ['费用场景', '场景']
|
|
},
|
|
{
|
|
id: 'merchant_name',
|
|
label: '商户',
|
|
placeholder: '待识别',
|
|
keys: ['merchant_name'],
|
|
labels: ['商户', '销售方', '开票方']
|
|
}
|
|
]
|
|
|
|
export function createReceiptDetailFieldModel({ detailForm, isTrainTicket }) {
|
|
const activeKeyFieldDefinitions = computed(() => (
|
|
isTrainTicket.value ? TRAIN_KEY_FIELD_DEFINITIONS : DEFAULT_KEY_FIELD_DEFINITIONS
|
|
))
|
|
const keyReceiptFields = computed(() => (
|
|
activeKeyFieldDefinitions.value.map((definition) => ({
|
|
...definition,
|
|
value: getReceiptFieldValue(definition)
|
|
}))
|
|
))
|
|
const keyReceiptFieldTokens = computed(() => {
|
|
const tokens = new Set()
|
|
activeKeyFieldDefinitions.value.forEach((definition) => {
|
|
for (const token of [definition.id, ...(definition.keys || []), ...(definition.labels || [])]) {
|
|
const normalized = normalizeReceiptFieldToken(token)
|
|
if (normalized) tokens.add(normalized)
|
|
}
|
|
})
|
|
return tokens
|
|
})
|
|
const editableOtherFields = computed(() => (
|
|
detailForm.fields.filter((field) => {
|
|
const key = normalizeReceiptFieldToken(field?.key)
|
|
const label = normalizeReceiptFieldToken(field?.label)
|
|
return !keyReceiptFieldTokens.value.has(key) && !keyReceiptFieldTokens.value.has(label)
|
|
})
|
|
))
|
|
|
|
function findReceiptFieldForDefinition(definition) {
|
|
const keys = (definition.keys || []).map(normalizeReceiptFieldToken).filter(Boolean)
|
|
const labels = (definition.labels || []).map(normalizeReceiptFieldToken).filter(Boolean)
|
|
|
|
return detailForm.fields.find((field) => keys.includes(normalizeReceiptFieldToken(field?.key)))
|
|
|| detailForm.fields.find((field) => labels.includes(normalizeReceiptFieldToken(field?.label)))
|
|
|| null
|
|
}
|
|
|
|
function getReceiptFieldFallback(definition) {
|
|
if (definition.id === 'invoice_date') return detailForm.document_date
|
|
if (definition.id === 'fare' || definition.id === 'amount') return detailForm.amount
|
|
if (definition.id === 'merchant_name') return detailForm.merchant_name
|
|
if (definition.id === 'document_type_label') return detailForm.document_type_label
|
|
if (definition.id === 'scene_label') return detailForm.scene_label
|
|
return ''
|
|
}
|
|
|
|
function getReceiptFieldValue(definition) {
|
|
const field = findReceiptFieldForDefinition(definition)
|
|
return String(field?.value || getReceiptFieldFallback(definition) || '')
|
|
}
|
|
|
|
function ensureReceiptField(definition) {
|
|
const field = findReceiptFieldForDefinition(definition)
|
|
if (field) {
|
|
field.key = field.key || definition.keys?.[0] || definition.id
|
|
field.label = field.label || definition.label
|
|
return field
|
|
}
|
|
|
|
const created = {
|
|
key: definition.keys?.[0] || definition.id,
|
|
label: definition.label,
|
|
value: getReceiptFieldFallback(definition)
|
|
}
|
|
detailForm.fields.push(created)
|
|
return created
|
|
}
|
|
|
|
function ensureEditableReceiptFields() {
|
|
for (const definition of [...activeKeyFieldDefinitions.value, ...RECEIPT_META_FIELD_DEFINITIONS]) {
|
|
const field = ensureReceiptField(definition)
|
|
const fallback = getReceiptFieldFallback(definition)
|
|
if (!String(field.value || '').trim() && fallback) {
|
|
field.value = fallback
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateReceiptField(definition, value) {
|
|
const field = ensureReceiptField(definition)
|
|
field.value = value
|
|
syncEditableFieldsToTopLevel()
|
|
}
|
|
|
|
function readFieldValue(definition) {
|
|
return String(findReceiptFieldForDefinition(definition)?.value || '').trim()
|
|
}
|
|
|
|
function syncEditableFieldsToTopLevel() {
|
|
const invoiceDate = readFieldValue(TRAIN_KEY_FIELD_DEFINITIONS[1]) || readFieldValue(DEFAULT_KEY_FIELD_DEFINITIONS[1])
|
|
const amount = readFieldValue(TRAIN_KEY_FIELD_DEFINITIONS[2]) || readFieldValue(DEFAULT_KEY_FIELD_DEFINITIONS[2])
|
|
const merchant = readFieldValue(RECEIPT_META_FIELD_DEFINITIONS[2]) || readFieldValue(DEFAULT_KEY_FIELD_DEFINITIONS[3])
|
|
const documentTypeLabel = readFieldValue(RECEIPT_META_FIELD_DEFINITIONS[0])
|
|
const sceneLabel = readFieldValue(RECEIPT_META_FIELD_DEFINITIONS[1])
|
|
|
|
if (invoiceDate) detailForm.document_date = invoiceDate
|
|
if (amount) detailForm.amount = amount
|
|
if (merchant) detailForm.merchant_name = merchant
|
|
if (documentTypeLabel) detailForm.document_type_label = documentTypeLabel
|
|
if (sceneLabel) detailForm.scene_label = sceneLabel
|
|
}
|
|
|
|
function buildDetailPayload() {
|
|
syncEditableFieldsToTopLevel()
|
|
return {
|
|
document_type: detailForm.document_type,
|
|
document_type_label: detailForm.document_type_label,
|
|
scene_code: detailForm.scene_code,
|
|
scene_label: detailForm.scene_label,
|
|
summary: detailForm.summary,
|
|
amount: detailForm.amount,
|
|
document_date: detailForm.document_date,
|
|
merchant_name: detailForm.merchant_name,
|
|
fields: detailForm.fields
|
|
.map((field) => ({
|
|
key: String(field?.key || '').trim(),
|
|
label: String(field?.label || '').trim(),
|
|
value: String(field?.value || '').trim()
|
|
}))
|
|
.filter((field) => field.key || field.label || field.value)
|
|
}
|
|
}
|
|
|
|
return {
|
|
buildDetailPayload,
|
|
editableOtherFields,
|
|
ensureEditableReceiptFields,
|
|
keyReceiptFields,
|
|
syncEditableFieldsToTopLevel,
|
|
updateReceiptField
|
|
}
|
|
}
|
|
|
|
function normalizeReceiptFieldToken(value) {
|
|
return String(value || '').trim().toLowerCase().replace(/\s+/g, '')
|
|
}
|