From 875cf0b42803bc0fa9381b80a4a4cf48100a83af Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Tue, 12 May 2026 08:30:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai-chat):=20=E5=A2=9E=E5=BC=BAMarkdown?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E5=8F=8A=E8=B0=83=E6=95=B4=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E6=B0=94=E6=B3=A1=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .local.config.js | 2 +- pages_tool/ai-chat/ai-chat-message.vue | 198 +++++++++++++++++++++++-- 2 files changed, 184 insertions(+), 16 deletions(-) diff --git a/.local.config.js b/.local.config.js index 1902e69..4d86284 100644 --- a/.local.config.js +++ b/.local.config.js @@ -62,5 +62,5 @@ const localDevConfig = ({ uniacid: 2075, domain: 'https://xcx.aigc-quickapp.com/', }, -})['2811']; // 选择要使用的环境配置 +})['1']; // 选择要使用的环境配置 export default localDevConfig; \ No newline at end of file diff --git a/pages_tool/ai-chat/ai-chat-message.vue b/pages_tool/ai-chat/ai-chat-message.vue index 2eddd4d..60ce73a 100644 --- a/pages_tool/ai-chat/ai-chat-message.vue +++ b/pages_tool/ai-chat/ai-chat-message.vue @@ -1126,20 +1126,190 @@ async loadChatHistoryIfExist() { } return `感谢您的咨询!关于"${userMessage}"的问题,我们的专业客服会尽快为您解答。您也可以查看我们的帮助文档获取更多信息。`; }, - - - // 解析Markdown + + + // 增强版Markdown解析(无第三方依赖) parseMarkdown(content) { if (!content) return '' - - // 简单的Markdown解析 + + // 辅助:内联格式处理 + const parseInline = (text) => { + return text + // 行内图片 + .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1') + // 链接 + .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') + // 行内代码 + .replace(/`([^`]+)`/g, '$1') + // 粗体(优先匹配**) + .replace(/\*\*(.+?)\*\*/g, '$1') + // 斜体 + .replace(/\*(.+?)\*/g, '$1') + // 删除线 + .replace(/~~(.+?)~~/g, '$1') + // 手机号 + .replace(/\b(1[3-9]\d{9})\b/g, '$1') + // 400电话 + .replace(/\b(400[\d-]{7,10})\b/g, '$1') + } + + // 1. 转义HTML特殊字符 let html = content - .replace(/\*\*(.*?)\*\*/g, '$1') // 粗体 - .replace(/\*(.*?)\*/g, '$1') // 斜体 - .replace(/`(.*?)`/g, '$1') // 代码 - .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') // 链接 - .replace(/\n/g, '
') // 换行 - + .replace(/&/g, '&') + .replace(//g, '>') + + // 2. 提取代码块(保护其内容不被后续处理污染) + const codeBlocks = [] + html = html.replace(/```(\w*)\n?([\s\S]*?)```/g, (m, lang, code) => { + const idx = codeBlocks.length + const safe = code.replace(//g, '>') + codeBlocks.push( + `
` +
+          `${safe}
` + ) + return `%%CODEBLOCK_${idx}%%` + }) + + // 3. 按行处理,用
连接单换行,双换行分割段落块 + const lines = html.split('\n') + const out = [] + let inList = false + let buf = [] // 普通文本行缓冲区 + let inTable = false + let tableRows = [] // 表格行缓冲区 + + const closeList = () => { + if (inList === 'ul') { inList = false; return '' } + if (inList === 'ol') { inList = false; return '' } + return '' + } + const flushBuf = () => { + if (buf.length === 0) return + // 去掉首尾的空串,避免产生尾随
+ while (buf.length > 0 && buf[0] === '') buf.shift() + while (buf.length > 0 && buf[buf.length - 1] === '') buf.pop() + if (buf.length === 0) return + const content = buf.map(l => parseInline(l)).join('
') + out.push(`
${content}
`) + buf = [] + } + const flushTable = () => { + if (!inTable || tableRows.length === 0) return + const thead = tableRows[0].map(c => + `${parseInline(c)}` + ).join('') + const tbody = tableRows.slice(1).map(row => + '' + row.map(c => + `${parseInline(c)}` + ).join('') + '' + ).join('') + out.push( + `` + + `${thead}${tbody}
` + ) + tableRows = [] + inTable = false + } + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] + const trimmed = line.trim() + + // 代码块占位符 + if (/^%%CODEBLOCK_\d+%%$/.test(trimmed)) { + flushBuf(); flushTable(); out.push(closeList()) + out.push(line) + continue + } + + // 表格行(以 | 开头) + if (trimmed.charAt(0) === '|') { + flushBuf(); out.push(closeList()) + // 去除首尾空 cell + const rawCells = trimmed.split('|').filter((c, j, a) => j > 0 && j < a.length - 1) + if (rawCells.length === 0) continue + // 跳过分隔行 | --- | :--- | :---: | ---: | + const isSep = rawCells.every(c => /^[\s:\-|]+$/.test(c.trim())) + if (isSep) continue + if (!inTable) { inTable = true; tableRows = [] } + tableRows.push(rawCells.map(c => c.trim())) + continue + } + // 非表格行 → 刷新已积累的表格 + if (inTable) flushTable() + + // 标题 + const hMatch = line.match(/^(#{1,6})\s+(.+)$/) + if (hMatch) { + flushBuf(); out.push(closeList()) + const lv = hMatch[1].length + const size = ['', '40rpx', '36rpx', '32rpx', '28rpx', '26rpx', '24rpx'][lv] + const wgt = ['', '700', '700', '600', '600', '500', '500'][lv] + const mg = ['', '20rpx 0 10rpx', '16rpx 0 8rpx', '14rpx 0 6rpx', '12rpx 0 6rpx', '10rpx 0 4rpx', '8rpx 0 4rpx'][lv] + out.push(`${parseInline(hMatch[2])}`) + continue + } + + // 分隔线 + if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(trimmed)) { + flushBuf(); out.push(closeList()) + out.push('
') + continue + } + + // 引用 + const bqMatch = line.match(/^>\s?(.+)$/) + if (bqMatch) { + flushBuf(); out.push(closeList()) + out.push(`
${parseInline(bqMatch[1])}
`) + continue + } + + // 无序列表 + const ulMatch = line.match(/^[-*+]\s+(.+)$/) + if (ulMatch) { + flushBuf() + if (!inList) { out.push('