From e522a99b9144bb7b299dc2a90844817e4dcad8d6 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 07 五月 2026 13:38:38 +0800
Subject: [PATCH] chore(config): 更新测试环境API端口配置

---
 src/components/AIChatSidebar/index.vue |  662 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 638 insertions(+), 24 deletions(-)

diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index 3bc2b8f..369d75d 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -254,14 +254,141 @@
                     </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>
@@ -274,7 +401,7 @@
                         size="small"
                         :loading="message.confirming"
                         :disabled="message.confirmed || isSending"
-                        @click="confirmPurchaseAnalysis(message)"
+                        @click="confirmPurchaseAnalysisFromTable(message)"
                     >
                       纭骞舵墽琛�
                     </el-button>
@@ -453,8 +580,8 @@
 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)
@@ -535,7 +662,8 @@
   totalPriceWithTax: '鍚◣鎬讳环',
   invoiceType: '鍙戠エ绫诲瀷',
   inventoryWarningQuantity: '搴撳瓨棰勮鏁伴噺',
-  isInspected: '鏄惁璐ㄦ'
+  isInspected: '鏄惁璐ㄦ',
+  isChecked: '鏄惁璐ㄦ'
 }
 const purchasePayloadFieldKeyMap = {
   閲囪喘鍙拌处: 'purchaseLedgers',
@@ -621,10 +749,45 @@
   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)
@@ -696,7 +859,9 @@
           chartOptions: null,
           chartRenderReady: false,
           type: '',
-          tableData: null
+          tableData: null,
+          payloadTreeData: null,
+          payloadHiddenData: null
         }
 
         messages.value.push(messageObj)
@@ -931,8 +1096,13 @@
   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
@@ -1079,22 +1249,361 @@
   }
 }
 
+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',
@@ -1257,7 +1766,11 @@
 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)
 }
@@ -1372,6 +1885,53 @@
   }
 }
 
+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) {
@@ -1428,7 +1988,9 @@
     chartOptions: null,
     chartRenderReady: false,
     type: '',
-    tableData: null
+    tableData: null,
+    payloadTreeData: null,
+    payloadHiddenData: null
   })
 
   outputState.value[botMsgIndex] = {
@@ -1549,7 +2111,9 @@
     chartOptions: null,
     chartRenderReady: false,
     type: '',
-    tableData: null
+    tableData: null,
+    payloadTreeData: null,
+    payloadHiddenData: null
   }
   messages.value.push(botMsg)
 
@@ -2774,14 +3338,64 @@
   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;

--
Gitblit v1.9.3