From e1220856d419dfa447e5e0909700c14a6c78d297 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 22 五月 2026 14:44:39 +0800
Subject: [PATCH] fix(financial): 修复财务数据处理中的数值转换和异常处理问题

---
 src/components/AIChatSidebar/index.vue |  149 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 147 insertions(+), 2 deletions(-)

diff --git a/src/components/AIChatSidebar/index.vue b/src/components/AIChatSidebar/index.vue
index a6d46da..7b22d7b 100644
--- a/src/components/AIChatSidebar/index.vue
+++ b/src/components/AIChatSidebar/index.vue
@@ -227,6 +227,12 @@
                       :id="`ai-chart-${index}-${key}`"
                   ></div>
                 </div>
+                <div
+                    v-else-if="message.chartMarkdownParseFailed"
+                    class="chart-empty-state"
+                >
+                  鍥捐〃瑙f瀽澶辫触锛岃绋嶅悗閲嶈瘯銆�
+                </div>
 
                 <!-- 琛ㄦ牸鍐呭 -->
                 <div v-if="message.type === 'todo_list' && message.tableData" class="table-wrapper">
@@ -2112,6 +2118,7 @@
           purchaseData: null,
           purchaseIntentData: null,
           financeData: null,
+          chartMarkdownParseFailed: false,
           localUploadFiles: isUser ? mapHistoryFilePathsToSnapshots(msg.filePaths, uuid.value, idx) : []
         }
 
@@ -2361,6 +2368,7 @@
   messageObj.purchaseData = null
   messageObj.purchaseIntentData = null
   messageObj.financeData = null
+  messageObj.chartMarkdownParseFailed = false
 
   if (isPurchaseIntentNotRecognized) {
     messageObj.purchaseIntentData = normalizePurchaseIntentNotRecognizedData(parsedData)
@@ -3615,7 +3623,8 @@
     salesData: null,
     purchaseData: null,
     purchaseIntentData: null,
-    financeData: null
+    financeData: null,
+    chartMarkdownParseFailed: false
   })
 
   outputState.value[botMsgIndex] = {
@@ -3671,6 +3680,7 @@
     if (extracted) {
       applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex, !outputState.value[botMsgIndex].hasRenderedChart)
     }
+    currentMsg.htmlContent = convertStreamOutput(currentMsg.content || '', botMsgIndex)
 
     // 鏈�缁堣В鏋愮‘淇濆浘琛ㄦ覆鏌�
     if (currentMsg.chartOptions && !outputState.value[botMsgIndex].hasRenderedChart) {
@@ -3744,7 +3754,8 @@
     salesData: null,
     purchaseData: null,
     purchaseIntentData: null,
-    financeData: null
+    financeData: null,
+    chartMarkdownParseFailed: false
   }
   messages.value.push(botMsg)
 
@@ -3794,6 +3805,7 @@
     if (extracted) {
       applyStructuredMessageData(currentMsg, extracted.data, botMsgIndex)
     }
+    currentMsg.htmlContent = convertStreamOutput(currentMsg.content || '', botMsgIndex)
   }).catch(err => {
     if (err.name === 'CanceledError' || err.name === 'AbortError') {
       console.log('Request aborted by user')
@@ -3835,6 +3847,126 @@
       .replace(/</g, '&lt;')
       .replace(/>/g, '&gt;')
       .replace(/\n/g, '<br>')
+}
+
+const localChartMarkdownImagePattern = /!\[[^\]]*]\((https?:\/\/local\/generate_chart\?[^)\s]+)\)/gi
+
+const parseLocalChartOptionText = (optionText = '') => {
+  const text = String(optionText || '').trim()
+  if (!text) return null
+
+  const parseCandidates = [text]
+  try {
+    const decoded = decodeURIComponent(text)
+    if (decoded && decoded !== text) {
+      parseCandidates.push(decoded)
+    }
+  } catch (err) {
+    // Keep original text candidate.
+  }
+
+  for (const candidate of parseCandidates) {
+    try {
+      const parsed = JSON.parse(candidate)
+      if (isPlainObject(parsed)) {
+        return parsed
+      }
+    } catch (err) {
+      continue
+    }
+  }
+
+  return null
+}
+
+const parseLocalChartOptionFromUrl = (urlText = '') => {
+  try {
+    const url = new URL(String(urlText || '').trim())
+    if (String(url.hostname || '').toLowerCase() !== 'local' || !String(url.pathname || '').includes('/generate_chart')) {
+      return null
+    }
+    const optionText = url.searchParams.get('options')
+    return parseLocalChartOptionText(optionText)
+  } catch (err) {
+    return null
+  }
+}
+
+const extractLocalChartMarkdown = (text = '') => {
+  const sourceText = String(text || '')
+  if (!sourceText) {
+    return {
+      cleanedText: '',
+      hasLocalChartMarkdown: false,
+      chartOptions: null,
+      parseFailed: false
+    }
+  }
+
+  let hasLocalChartMarkdown = false
+  let chartIndex = 0
+  const chartOptions = {}
+
+  const cleanedText = sourceText.replace(localChartMarkdownImagePattern, (fullMatch, chartUrl) => {
+    hasLocalChartMarkdown = true
+    const option = parseLocalChartOptionFromUrl(chartUrl)
+    if (option) {
+      chartOptions[`markdownChart_${chartIndex++}`] = option
+    }
+    return ''
+  })
+
+  const normalizedText = cleanedText
+      .replace(/\n[ \t]*\n[ \t]*\n+/g, '\n\n')
+      .trim()
+  const hasParsedCharts = Object.keys(chartOptions).length > 0
+
+  return {
+    cleanedText: normalizedText,
+    hasLocalChartMarkdown,
+    chartOptions: hasParsedCharts ? chartOptions : null,
+    parseFailed: hasLocalChartMarkdown && !hasParsedCharts
+  }
+}
+
+const applyLocalChartMarkdownFallback = (displayText, msgIndex) => {
+  const messageObj = messages.value[msgIndex]
+  if (!messageObj || messageObj.isUser) return displayText
+
+  const {
+    cleanedText,
+    hasLocalChartMarkdown,
+    chartOptions,
+    parseFailed
+  } = extractLocalChartMarkdown(displayText)
+
+  if (!hasLocalChartMarkdown) {
+    return displayText
+  }
+
+  if (chartOptions) {
+    messageObj.chartOptions = chartOptions
+    messageObj.chartRenderReady = true
+    messageObj.chartMarkdownParseFailed = false
+
+    const streamState = outputState.value[msgIndex]
+    if (!streamState || !streamState.hasRenderedChart) {
+      renderCharts(msgIndex, chartOptions)
+      if (streamState) {
+        streamState.hasRenderedChart = true
+      }
+    }
+
+    return cleanedText || '宸蹭负鎮ㄧ敓鎴愬垎鏋愬浘琛ㄣ��'
+  }
+
+  if (!messageObj.chartOptions || !messageObj.chartRenderReady) {
+    messageObj.chartOptions = null
+    messageObj.chartRenderReady = false
+    messageObj.chartMarkdownParseFailed = parseFailed
+  }
+
+  return cleanedText || '鍥捐〃瑙f瀽澶辫触锛岃绋嶅悗閲嶈瘯銆�'
 }
 
 const convertStreamOutput = (output, msgIndex) => {
@@ -3902,6 +4034,7 @@
     }
   }
 
+  display = applyLocalChartMarkdownFallback(display, msgIndex)
   let html = convertTextToHtml(display)
 
   // 杩樺師浠g爜鍧�
@@ -4884,6 +5017,18 @@
   margin-bottom: 12px;
 }
 
+.chart-empty-state {
+  margin-top: 12px;
+  width: 100%;
+  border-radius: 10px;
+  border: 1px dashed rgba(148, 163, 184, 0.6);
+  background: #f8fafc;
+  color: #64748b;
+  font-size: 13px;
+  line-height: 1.6;
+  padding: 12px;
+}
+
 .table-wrapper {
   margin-top: 12px;
   background: #fff;

--
Gitblit v1.9.3