| | |
| | | </ul> |
| | | </div> |
| | | <div class="purchase-section-title">补充或确认数据</div> |
| | | <el-input |
| | | v-model="message.payloadText" |
| | | type="textarea" |
| | | :rows="8" |
| | | resize="vertical" |
| | | spellcheck="false" |
| | | class="payload-editor" |
| | | /> |
| | | <div class="payload-toolbar"> |
| | | <el-button |
| | | size="small" |
| | | plain |
| | | :disabled="message.confirming || message.confirmed" |
| | | @click="addPurchaseRootField(message)" |
| | | > |
| | | <el-icon><Plus /></el-icon> |
| | | 新增顶层字段 |
| | | </el-button> |
| | | </div> |
| | | <div class="payload-tree-table-wrapper"> |
| | | <el-table |
| | | :data="message.payloadTreeData || []" |
| | | row-key="id" |
| | | border |
| | | stripe |
| | | size="small" |
| | | default-expand-all |
| | | :tree-props="{ children: 'children' }" |
| | | empty-text="暂无待确认数据" |
| | | > |
| | | <el-table-column label="字段" min-width="240"> |
| | | <template #default="{ row }"> |
| | | <div class="payload-key-cell"> |
| | | <template v-if="row.parentType === 'object'"> |
| | | <el-input |
| | | v-if="row.keyEditable" |
| | | v-model="row.key" |
| | | size="small" |
| | | :disabled="message.confirming || message.confirmed" |
| | | placeholder="字段名" |
| | | /> |
| | | <div v-else class="payload-fixed-key" :title="row.key"> |
| | | <span>{{ getPurchaseFieldLabel(row.key) }}</span> |
| | | <small v-if="getPurchaseFieldLabel(row.key) !== row.key">{{ row.key }}</small> |
| | | </div> |
| | | </template> |
| | | <span v-else class="payload-array-index">{{ getPurchaseArrayItemLabel(row, message) }}</span> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="类型" width="130" align="center"> |
| | | <template #default="{ row }"> |
| | | <el-select |
| | | v-model="row.valueType" |
| | | size="small" |
| | | :disabled="message.confirming || message.confirmed" |
| | | @change="handlePurchaseNodeTypeChange(message, row)" |
| | | > |
| | | <el-option |
| | | v-for="option in purchaseValueTypeOptions" |
| | | :key="option.value" |
| | | :label="option.label" |
| | | :value="option.value" |
| | | /> |
| | | </el-select> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="值" min-width="250"> |
| | | <template #default="{ row }"> |
| | | <div v-if="row.valueType === 'object'" class="payload-container-cell"> |
| | | 对象({{ row.children?.length || 0 }}) |
| | | </div> |
| | | <div v-else-if="row.valueType === 'array'" class="payload-container-cell"> |
| | | 数组({{ row.children?.length || 0 }}) |
| | | </div> |
| | | <el-switch |
| | | v-else-if="row.valueType === 'boolean'" |
| | | v-model="row.value" |
| | | size="small" |
| | | :disabled="message.confirming || message.confirmed" |
| | | /> |
| | | <span v-else-if="row.valueType === 'null'" class="payload-null-value">null</span> |
| | | <el-input |
| | | v-else |
| | | v-model="row.value" |
| | | size="small" |
| | | :placeholder="row.valueType === 'number' ? '请输入数字' : '请输入内容'" |
| | | :disabled="message.confirming || message.confirmed" |
| | | /> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="操作" width="180" align="center"> |
| | | <template #default="{ row }"> |
| | | <div class="payload-row-actions"> |
| | | <el-tooltip v-if="row.valueType === 'object'" content="新增字段" placement="top"> |
| | | <el-button |
| | | :icon="Plus" |
| | | circle |
| | | size="small" |
| | | text |
| | | type="primary" |
| | | :disabled="message.confirming || message.confirmed" |
| | | @click="addPurchaseChildNode(message, row)" |
| | | /> |
| | | </el-tooltip> |
| | | <el-tooltip v-else-if="row.valueType === 'array'" content="新增数组项" placement="top"> |
| | | <el-button |
| | | :icon="Plus" |
| | | circle |
| | | size="small" |
| | | text |
| | | type="primary" |
| | | :disabled="message.confirming || message.confirmed" |
| | | @click="addPurchaseChildNode(message, row)" |
| | | /> |
| | | </el-tooltip> |
| | | <el-tooltip v-if="row.parentType === 'array'" content="新增同级项" placement="top"> |
| | | <el-button |
| | | :icon="Plus" |
| | | circle |
| | | size="small" |
| | | text |
| | | type="primary" |
| | | :disabled="message.confirming || message.confirmed" |
| | | @click="addPurchaseSiblingNode(message, row)" |
| | | /> |
| | | </el-tooltip> |
| | | <el-tooltip content="删除当前项" placement="top"> |
| | | <el-button |
| | | :icon="Delete" |
| | | circle |
| | | size="small" |
| | | text |
| | | type="danger" |
| | | :disabled="message.confirming || message.confirmed" |
| | | @click="removePurchaseNode(message, row)" |
| | | /> |
| | | </el-tooltip> |
| | | </div> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | <div class="payload-editor-tip"> |
| | | 日期请填写 yyyy-MM-dd,例如 2026-04-30。产品明细建议放在每条采购台账的 productData 中,确认时会自动兼容旧格式并清理审批字段。 |
| | | </div> |
| | |
| | | size="small" |
| | | :loading="message.confirming" |
| | | :disabled="message.confirmed || isSending" |
| | | @click="confirmPurchaseAnalysis(message)" |
| | | @click="confirmPurchaseAnalysisFromTable(message)" |
| | | > |
| | | 确认并执行 |
| | | </el-button> |
| | |
| | | const windowWidth = ref(window.innerWidth) |
| | | const drawerSize = computed(() => { |
| | | if (windowWidth.value < 768) return '100%' |
| | | if (windowWidth.value < 1200) return '500px' |
| | | return '600px' |
| | | if (windowWidth.value < 1200) return '50%' |
| | | return '50%' |
| | | }) |
| | | const messageListRef = ref(null) |
| | | const isSending = ref(false) |
| | |
| | | totalPriceWithTax: '含税总价', |
| | | invoiceType: '发票类型', |
| | | inventoryWarningQuantity: '库存预警数量', |
| | | isInspected: '是否质检' |
| | | isInspected: '是否质检', |
| | | isChecked: '是否质检' |
| | | } |
| | | const purchasePayloadFieldKeyMap = { |
| | | 采购台账: 'purchaseLedgers', |
| | |
| | | taxInclusiveTotalPrice: 'taxInclusiveTotalPrice', |
| | | invoiceType: 'invoiceType', |
| | | inventoryWarningQuantity: 'inventoryWarningQuantity', |
| | | isInspected: 'isInspected' |
| | | isInspected: 'isInspected', |
| | | isChecked: 'isInspected' |
| | | } |
| | | |
| | | // 历史会话相关 |
| | | const purchaseValueTypeOptions = [ |
| | | { label: '文本', value: 'string' }, |
| | | { label: '数字', value: 'number' }, |
| | | { label: '布尔', value: 'boolean' }, |
| | | { label: '空值', value: 'null' }, |
| | | { label: '对象', value: 'object' }, |
| | | { label: '数组', value: 'array' } |
| | | ] |
| | | const purchaseContainerValueTypes = new Set(['object', 'array']) |
| | | const purchaseHiddenFieldKeySet = new Set(['templatename', 'approvalstatus', 'phonenumber', 'type']) |
| | | const purchaseHiddenKeyWordList = [ |
| | | 'attachment', |
| | | 'file', |
| | | 'invoice', |
| | | 'ticketregistration', |
| | | 'receiptpayment', |
| | | 'payment' |
| | | ] |
| | | const purchaseHiddenChineseKeywordList = ['附件', '开票', '来票', '回款', '付款'] |
| | | let purchasePayloadTreeNodeSeed = 0 |
| | | |
| | | const shouldHidePurchaseField = (fieldKey = '') => { |
| | | const rawKey = String(fieldKey || '') |
| | | if (!rawKey) return false |
| | | const normalizedFieldKey = purchasePayloadFieldKeyMap[rawKey] || rawKey |
| | | const lowerKey = String(normalizedFieldKey).toLowerCase() |
| | | |
| | | if (lowerKey.endsWith('id') || lowerKey.endsWith('ids')) return true |
| | | if (purchaseHiddenFieldKeySet.has(lowerKey)) return true |
| | | if (purchaseHiddenKeyWordList.some(keyword => lowerKey.includes(keyword))) return true |
| | | if (purchaseHiddenChineseKeywordList.some(keyword => rawKey.includes(keyword))) return true |
| | | return false |
| | | } |
| | | |
| | | const showHistory = ref(false) |
| | | const sessions = ref([]) |
| | | const loadingSessions = ref(false) |
| | |
| | | chartOptions: null, |
| | | chartRenderReady: false, |
| | | type: '', |
| | | tableData: null |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null |
| | | } |
| | | |
| | | messages.value.push(messageObj) |
| | |
| | | if (parsedData.action === 'confirm_required' && parsedData.businessType) { |
| | | messageObj.type = 'purchase_analysis_confirm' |
| | | messageObj.purchaseAnalysisData = parsedData |
| | | if (!Array.isArray(messageObj.payloadTreeData) || !messageObj.payloadTreeData.length) { |
| | | initializePurchasePayloadTree(messageObj, parsedData.payload || {}) |
| | | } |
| | | if (!messageObj.payloadText) { |
| | | messageObj.payloadText = JSON.stringify(localizePurchasePayload(parsedData.payload || {}), null, 2) |
| | | const payloadFromTree = buildPurchasePayloadFromNodes(messageObj.payloadTreeData, 'object') |
| | | const payloadWithHidden = mergePurchasePayloadWithHidden(payloadFromTree, messageObj.payloadHiddenData) |
| | | messageObj.payloadText = JSON.stringify(localizePurchasePayload(payloadWithHidden), null, 2) |
| | | } |
| | | messageObj.confirmResult = '' |
| | | messageObj.confirmed = false |
| | |
| | | } |
| | | } |
| | | |
| | | const hasMeaningfulPayloadValue = (value) => { |
| | | if (value === null || value === undefined) return false |
| | | if (typeof value === 'string') return value.trim() !== '' |
| | | if (Array.isArray(value)) return value.some(item => hasMeaningfulPayloadValue(item)) |
| | | if (typeof value === 'object') return Object.values(value).some(item => hasMeaningfulPayloadValue(item)) |
| | | return true |
| | | } |
| | | |
| | | const mergeMappedPayloadValue = (existingValue, incomingValue) => { |
| | | if (existingValue === undefined) return incomingValue |
| | | |
| | | const existingHasValue = hasMeaningfulPayloadValue(existingValue) |
| | | const incomingHasValue = hasMeaningfulPayloadValue(incomingValue) |
| | | |
| | | if (existingHasValue && !incomingHasValue) return existingValue |
| | | if (!existingHasValue && incomingHasValue) return incomingValue |
| | | |
| | | if ( |
| | | existingValue && |
| | | incomingValue && |
| | | typeof existingValue === 'object' && |
| | | typeof incomingValue === 'object' && |
| | | !Array.isArray(existingValue) && |
| | | !Array.isArray(incomingValue) |
| | | ) { |
| | | return { ...existingValue, ...incomingValue } |
| | | } |
| | | |
| | | return incomingValue |
| | | } |
| | | |
| | | const mapPayloadKeys = (value, keyMap) => { |
| | | if (Array.isArray(value)) { |
| | | return value.map(item => mapPayloadKeys(item, keyMap)) |
| | | } |
| | | if (value && typeof value === 'object') { |
| | | return Object.entries(value).reduce((result, [key, item]) => { |
| | | result[keyMap[key] || key] = mapPayloadKeys(item, keyMap) |
| | | const mappedKey = keyMap[key] || key |
| | | const mappedValue = mapPayloadKeys(item, keyMap) |
| | | result[mappedKey] = mergeMappedPayloadValue(result[mappedKey], mappedValue) |
| | | return result |
| | | }, {}) |
| | | } |
| | | return value |
| | | } |
| | | |
| | | const clonePurchasePayloadValue = (value) => { |
| | | if (Array.isArray(value)) { |
| | | return value.map(item => clonePurchasePayloadValue(item)) |
| | | } |
| | | if (value && typeof value === 'object') { |
| | | return Object.entries(value).reduce((result, [key, item]) => { |
| | | result[key] = clonePurchasePayloadValue(item) |
| | | return result |
| | | }, {}) |
| | | } |
| | | return value |
| | | } |
| | | |
| | | const splitPurchasePayloadByVisibility = (value) => { |
| | | if (Array.isArray(value)) { |
| | | const splitItems = value.map(item => splitPurchasePayloadByVisibility(item)) |
| | | const visible = splitItems.map(item => item.visible) |
| | | const hidden = splitItems.map(item => item.hidden) |
| | | return { visible, hidden } |
| | | } |
| | | |
| | | if (value && typeof value === 'object') { |
| | | const visible = {} |
| | | const hidden = {} |
| | | |
| | | Object.entries(value).forEach(([key, item]) => { |
| | | if (shouldHidePurchaseField(key)) { |
| | | hidden[key] = clonePurchasePayloadValue(item) |
| | | return |
| | | } |
| | | const child = splitPurchasePayloadByVisibility(item) |
| | | visible[key] = child.visible |
| | | if (hasMeaningfulPayloadValue(child.hidden)) { |
| | | hidden[key] = child.hidden |
| | | } |
| | | }) |
| | | |
| | | return { visible, hidden } |
| | | } |
| | | |
| | | return { visible: value, hidden: undefined } |
| | | } |
| | | |
| | | const mergePurchasePayloadWithHidden = (visibleValue, hiddenValue) => { |
| | | if (hiddenValue === undefined || hiddenValue === null) return visibleValue |
| | | if (visibleValue === undefined || visibleValue === null) return clonePurchasePayloadValue(hiddenValue) |
| | | |
| | | if (Array.isArray(visibleValue) && Array.isArray(hiddenValue)) { |
| | | const maxLength = Math.max(visibleValue.length, hiddenValue.length) |
| | | const merged = [] |
| | | for (let i = 0; i < maxLength; i++) { |
| | | merged[i] = mergePurchasePayloadWithHidden(visibleValue[i], hiddenValue[i]) |
| | | } |
| | | return merged |
| | | } |
| | | |
| | | if ( |
| | | visibleValue && |
| | | hiddenValue && |
| | | typeof visibleValue === 'object' && |
| | | typeof hiddenValue === 'object' && |
| | | !Array.isArray(visibleValue) && |
| | | !Array.isArray(hiddenValue) |
| | | ) { |
| | | const merged = { ...clonePurchasePayloadValue(hiddenValue) } |
| | | Object.entries(visibleValue).forEach(([key, item]) => { |
| | | merged[key] = mergePurchasePayloadWithHidden(item, merged[key]) |
| | | }) |
| | | return merged |
| | | } |
| | | |
| | | return visibleValue |
| | | } |
| | | |
| | | const localizePurchasePayload = (payload) => mapPayloadKeys(payload, purchasePayloadFieldLabelMap) |
| | | |
| | | const normalizePurchasePayload = (payload) => mapPayloadKeys(payload, purchasePayloadFieldKeyMap) |
| | | |
| | | const createPurchasePayloadNodeId = () => `purchase-node-${Date.now()}-${purchasePayloadTreeNodeSeed++}` |
| | | |
| | | const detectPurchaseValueType = (value) => { |
| | | if (Array.isArray(value)) return 'array' |
| | | if (value === null) return 'null' |
| | | const valueType = typeof value |
| | | if (valueType === 'number') return 'number' |
| | | if (valueType === 'boolean') return 'boolean' |
| | | if (valueType === 'object') return 'object' |
| | | return 'string' |
| | | } |
| | | |
| | | const normalizePurchaseNodeValueForEdit = (value, valueType) => { |
| | | if (valueType === 'number') return value === null || value === undefined ? '' : String(value) |
| | | if (valueType === 'boolean') return Boolean(value) |
| | | if (valueType === 'null') return '' |
| | | return value === null || value === undefined ? '' : String(value) |
| | | } |
| | | |
| | | const createPurchaseTreeNode = ({ |
| | | key = '', |
| | | parentType = 'object', |
| | | keyEditable = false, |
| | | valueType = 'string', |
| | | value = '', |
| | | children = [] |
| | | } = {}) => ({ |
| | | id: createPurchasePayloadNodeId(), |
| | | key, |
| | | parentType, |
| | | keyEditable, |
| | | valueType, |
| | | value, |
| | | children |
| | | }) |
| | | |
| | | const reorderPurchaseObjectEntries = (value) => { |
| | | const entries = Object.entries(value || {}) |
| | | const productDataIndex = entries.findIndex(([key]) => key === 'productData') |
| | | if (productDataIndex <= -1 || productDataIndex === entries.length - 1) { |
| | | return entries |
| | | } |
| | | const [productDataEntry] = entries.splice(productDataIndex, 1) |
| | | entries.push(productDataEntry) |
| | | return entries |
| | | } |
| | | |
| | | const buildPurchasePayloadTreeNodes = (value, parentType = 'object') => { |
| | | if (Array.isArray(value)) { |
| | | return value.map(item => { |
| | | const itemType = detectPurchaseValueType(item) |
| | | const node = createPurchaseTreeNode({ |
| | | key: '', |
| | | parentType: 'array', |
| | | keyEditable: false, |
| | | valueType: itemType, |
| | | value: normalizePurchaseNodeValueForEdit(item, itemType) |
| | | }) |
| | | if (purchaseContainerValueTypes.has(itemType)) { |
| | | node.children = buildPurchasePayloadTreeNodes(item, itemType) |
| | | } |
| | | return node |
| | | }) |
| | | } |
| | | |
| | | if (value && typeof value === 'object') { |
| | | return reorderPurchaseObjectEntries(value).map(([key, item]) => { |
| | | const itemType = detectPurchaseValueType(item) |
| | | const node = createPurchaseTreeNode({ |
| | | key, |
| | | parentType, |
| | | keyEditable: false, |
| | | valueType: itemType, |
| | | value: normalizePurchaseNodeValueForEdit(item, itemType) |
| | | }) |
| | | if (purchaseContainerValueTypes.has(itemType)) { |
| | | node.children = buildPurchasePayloadTreeNodes(item, itemType) |
| | | } |
| | | return node |
| | | }) |
| | | } |
| | | |
| | | return [] |
| | | } |
| | | |
| | | const initializePurchasePayloadTree = (messageObj, payload = {}) => { |
| | | const sourcePayload = payload && typeof payload === 'object' && !Array.isArray(payload) |
| | | ? payload |
| | | : {} |
| | | const { visible, hidden } = splitPurchasePayloadByVisibility(sourcePayload) |
| | | const visiblePayload = visible && typeof visible === 'object' && !Array.isArray(visible) ? visible : {} |
| | | messageObj.payloadTreeData = buildPurchasePayloadTreeNodes(visiblePayload, 'object') |
| | | messageObj.payloadHiddenData = hidden && typeof hidden === 'object' ? hidden : {} |
| | | } |
| | | |
| | | const getPurchaseFieldLabel = (fieldKey) => purchasePayloadFieldLabelMap[fieldKey] || fieldKey || '字段' |
| | | |
| | | const createPurchaseDefaultNode = (parentType = 'object') => createPurchaseTreeNode({ |
| | | key: parentType === 'object' ? 'newField' : '', |
| | | parentType, |
| | | keyEditable: parentType === 'object', |
| | | valueType: 'string', |
| | | value: '' |
| | | }) |
| | | |
| | | const getPurchaseScalarNodeValue = (node) => { |
| | | if (node.valueType === 'null') return null |
| | | if (node.valueType === 'boolean') return Boolean(node.value) |
| | | if (node.valueType === 'number') { |
| | | const text = String(node.value ?? '').trim() |
| | | if (!text) return null |
| | | const numberValue = Number(text) |
| | | return Number.isFinite(numberValue) ? numberValue : text |
| | | } |
| | | return node.value === null || node.value === undefined ? '' : String(node.value) |
| | | } |
| | | |
| | | const buildPurchasePayloadFromNodes = (nodes, parentType = 'object') => { |
| | | if (!Array.isArray(nodes)) { |
| | | return parentType === 'array' ? [] : {} |
| | | } |
| | | |
| | | if (parentType === 'array') { |
| | | return nodes.map(node => { |
| | | if (purchaseContainerValueTypes.has(node.valueType)) { |
| | | return buildPurchasePayloadFromNodes(node.children, node.valueType) |
| | | } |
| | | return getPurchaseScalarNodeValue(node) |
| | | }) |
| | | } |
| | | |
| | | return nodes.reduce((result, node, index) => { |
| | | const rawKey = String(node.key ?? '').trim() |
| | | const key = rawKey || `field_${index + 1}` |
| | | if (purchaseContainerValueTypes.has(node.valueType)) { |
| | | result[key] = buildPurchasePayloadFromNodes(node.children, node.valueType) |
| | | } else { |
| | | result[key] = getPurchaseScalarNodeValue(node) |
| | | } |
| | | return result |
| | | }, {}) |
| | | } |
| | | |
| | | const findPurchaseNodeLocation = (nodes, targetId, parentNode = null) => { |
| | | if (!Array.isArray(nodes)) return null |
| | | for (let index = 0; index < nodes.length; index++) { |
| | | const node = nodes[index] |
| | | if (node.id === targetId) { |
| | | return { |
| | | siblings: nodes, |
| | | index, |
| | | node, |
| | | parentNode |
| | | } |
| | | } |
| | | const next = findPurchaseNodeLocation(node.children, targetId, node) |
| | | if (next) return next |
| | | } |
| | | return null |
| | | } |
| | | |
| | | const getPurchaseArrayItemLabel = (row, message) => { |
| | | const location = findPurchaseNodeLocation(message?.payloadTreeData, row.id) |
| | | return `[${(location?.index ?? 0) + 1}]` |
| | | } |
| | | |
| | | const handlePurchaseNodeTypeChange = (message, row) => { |
| | | if (!message || !row) return |
| | | if (purchaseContainerValueTypes.has(row.valueType)) { |
| | | row.children = [] |
| | | row.value = '' |
| | | return |
| | | } |
| | | row.children = [] |
| | | if (row.valueType === 'boolean') { |
| | | row.value = false |
| | | } else if (row.valueType === 'null') { |
| | | row.value = '' |
| | | } else { |
| | | row.value = '' |
| | | } |
| | | } |
| | | |
| | | const addPurchaseRootField = (message) => { |
| | | if (!message) return |
| | | if (!Array.isArray(message.payloadTreeData)) { |
| | | message.payloadTreeData = [] |
| | | } |
| | | message.payloadTreeData.push(createPurchaseDefaultNode('object')) |
| | | } |
| | | |
| | | const addPurchaseChildNode = (message, row) => { |
| | | if (!message || !row || !purchaseContainerValueTypes.has(row.valueType)) return |
| | | if (!Array.isArray(row.children)) { |
| | | row.children = [] |
| | | } |
| | | row.children.push(createPurchaseDefaultNode(row.valueType)) |
| | | } |
| | | |
| | | const addPurchaseSiblingNode = (message, row) => { |
| | | if (!message || !row) return |
| | | const location = findPurchaseNodeLocation(message.payloadTreeData, row.id) |
| | | if (!location || location.node.parentType !== 'array') return |
| | | location.siblings.splice(location.index + 1, 0, createPurchaseDefaultNode('array')) |
| | | } |
| | | |
| | | const removePurchaseNode = (message, row) => { |
| | | if (!message || !row) return |
| | | const location = findPurchaseNodeLocation(message.payloadTreeData, row.id) |
| | | if (!location) return |
| | | location.siblings.splice(location.index, 1) |
| | | } |
| | | |
| | | const hasPurchaseNodeValidationError = (nodes, parentType = 'object') => { |
| | | if (!Array.isArray(nodes)) return false |
| | | return nodes.some((node) => { |
| | | if (parentType === 'object' && !String(node.key ?? '').trim()) { |
| | | return true |
| | | } |
| | | if (node.valueType === 'number') { |
| | | const text = String(node.value ?? '').trim() |
| | | if (text && !Number.isFinite(Number(text))) { |
| | | return true |
| | | } |
| | | } |
| | | if (purchaseContainerValueTypes.has(node.valueType)) { |
| | | return hasPurchaseNodeValidationError(node.children, node.valueType) |
| | | } |
| | | return false |
| | | }) |
| | | } |
| | | |
| | | const purchaseDateFieldKeys = new Set([ |
| | | 'entryDateStart', |
| | |
| | | const getVisiblePurchaseMissingFields = (analysisData) => { |
| | | const fields = Array.isArray(analysisData?.missingFields) ? analysisData.missingFields : [] |
| | | const visibleFields = analysisData?.businessType === 'purchase_ledger' |
| | | ? fields.filter(field => !purchaseApprovalFieldKeys.has(field)) |
| | | ? fields.filter(field => { |
| | | if (purchaseApprovalFieldKeys.has(field)) return false |
| | | const normalizedField = purchasePayloadFieldKeyMap[field] || field |
| | | return !shouldHidePurchaseField(normalizedField) && !shouldHidePurchaseField(field) |
| | | }) |
| | | : fields |
| | | return visibleFields.map(field => purchasePayloadFieldLabelMap[field] || field) |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | const confirmPurchaseAnalysisFromTable = async (message) => { |
| | | if (!message?.purchaseAnalysisData || message.confirming || message.confirmed) return |
| | | |
| | | if (!Array.isArray(message.payloadTreeData)) { |
| | | initializePurchasePayloadTree(message, message.purchaseAnalysisData.payload || {}) |
| | | } |
| | | if (hasPurchaseNodeValidationError(message.payloadTreeData, 'object')) { |
| | | message.confirmResult = '请先补全字段名,并确保数字字段填写合法数字' |
| | | message.confirmed = false |
| | | return |
| | | } |
| | | |
| | | let payload |
| | | try { |
| | | const draftPayload = buildPurchasePayloadFromNodes(message.payloadTreeData, 'object') |
| | | const mergedPayload = mergePurchasePayloadWithHidden(draftPayload, message.payloadHiddenData) |
| | | const normalizedPayload = normalizePurchasePayload(mergedPayload) |
| | | payload = sanitizePurchasePayloadForSubmit( |
| | | normalizePurchasePayloadDates(normalizedPayload), |
| | | message.purchaseAnalysisData.businessType |
| | | ) |
| | | message.payloadText = JSON.stringify(localizePurchasePayload(normalizedPayload), null, 2) |
| | | } catch (err) { |
| | | message.confirmResult = '待提交数据格式有误,请检查后再确认' |
| | | message.confirmed = false |
| | | return |
| | | } |
| | | |
| | | message.confirming = true |
| | | message.confirmResult = '' |
| | | |
| | | try { |
| | | const res = await request.post(`${currentAssistant.value.apiBase}/analyze-files/confirm`, { |
| | | businessType: message.purchaseAnalysisData.businessType, |
| | | payload |
| | | }) |
| | | message.confirmed = true |
| | | message.confirmResult = res?.msg || '确认成功,业务处理已提交' |
| | | ElMessage.success(message.confirmResult) |
| | | } catch (err) { |
| | | message.confirmed = false |
| | | message.confirmResult = err?.message || '确认失败,请检查数据后重试' |
| | | } finally { |
| | | message.confirming = false |
| | | } |
| | | } |
| | | |
| | | const scrollToBottom = () => { |
| | | nextTick(() => { |
| | | if (messageListRef.value) { |
| | |
| | | chartOptions: null, |
| | | chartRenderReady: false, |
| | | type: '', |
| | | tableData: null |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null |
| | | }) |
| | | |
| | | outputState.value[botMsgIndex] = { |
| | |
| | | chartOptions: null, |
| | | chartRenderReady: false, |
| | | type: '', |
| | | tableData: null |
| | | tableData: null, |
| | | payloadTreeData: null, |
| | | payloadHiddenData: null |
| | | } |
| | | messages.value.push(botMsg) |
| | | |
| | |
| | | color: $deep-blue; |
| | | } |
| | | |
| | | .payload-editor { |
| | | :deep(.el-textarea__inner) { |
| | | font-family: Consolas, Monaco, monospace; |
| | | font-size: 12px; |
| | | line-height: 1.55; |
| | | .payload-toolbar { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .payload-tree-table-wrapper { |
| | | border: 1px solid rgba(0, 85, 212, 0.1); |
| | | border-radius: 10px; |
| | | overflow: auto; |
| | | |
| | | :deep(.el-table) { |
| | | --el-table-header-bg-color: #f5f8ff; |
| | | --el-table-border-color: rgba(0, 85, 212, 0.08); |
| | | } |
| | | } |
| | | |
| | | .payload-key-cell { |
| | | display: flex; |
| | | align-items: center; |
| | | min-height: 28px; |
| | | } |
| | | |
| | | .payload-fixed-key { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 2px; |
| | | line-height: 1.3; |
| | | color: #1f2937; |
| | | |
| | | small { |
| | | font-size: 11px; |
| | | color: #6b7280; |
| | | } |
| | | } |
| | | |
| | | .payload-array-index { |
| | | font-size: 12px; |
| | | color: #475467; |
| | | } |
| | | |
| | | .payload-container-cell { |
| | | color: #344054; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .payload-null-value { |
| | | color: #6b7280; |
| | | font-size: 12px; |
| | | } |
| | | |
| | | .payload-row-actions { |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | gap: 4px; |
| | | } |
| | | |
| | | .payload-editor-tip { |
| | | margin-top: 6px; |
| | | font-size: 12px; |