From 8149a49ff032c376bb3b487d2ac58eafa8b74ba7 Mon Sep 17 00:00:00 2001 From: zoujing Date: Fri, 27 Mar 2026 00:57:22 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E4=BC=98=E5=8C=96=E5=9B=9E?= =?UTF-8?q?=E7=AD=94=E6=B6=88=E6=81=AF=E7=9A=84=E8=BF=94=E5=9B=9E=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E4=B8=8E=E4=BA=A4=E4=BA=92=E6=A0=B7=E5=BC=8F=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/chat/ChatMainList/index.vue | 79 ++++++++++++----- .../module/AnswerComponent/index.vue | 31 +++++-- .../zero-markdown-view/zero-markdown-view.vue | 87 ++++++++++--------- 3 files changed, 126 insertions(+), 71 deletions(-) diff --git a/src/pages/index/components/chat/ChatMainList/index.vue b/src/pages/index/components/chat/ChatMainList/index.vue index f995910..6183ef3 100644 --- a/src/pages/index/components/chat/ChatMainList/index.vue +++ b/src/pages/index/components/chat/ChatMainList/index.vue @@ -477,31 +477,62 @@ const handleWebSocketMessage = (data) => { } let aiMsgIndex = -1; - if (currentSessionMessageId && pendingMap.has(currentSessionMessageId)) { - aiMsgIndex = pendingMap.get(currentSessionMessageId); - if (aiMsgIndex >= 0 && aiMsgIndex < chatMsgList.value.length) { - const item = chatMsgList.value[aiMsgIndex]; - if (item && item.msgType === MessageRole.AI && - item.replyMessageId.length > 0 && data.replyMessageId && - item.replyMessageId !== data.replyMessageId) { - // 已经存在对应的AI消息项,继续使用 - const aiMsg = { - msgId: `msg_${chatMsgList.value.length}`, - msgType: MessageRole.AI, - msg: "", - isLoading: false, - messageId: currentSessionMessageId, - replyMessageId: '', - componentName: "", - title: "", - finish: false, - }; - chatMsgList.value.push(aiMsg); - aiMsgIndex = chatMsgList.value.length - 1; - } + // Prefer matching by replyMessageId if provided + if (data.replyMessageId) { + // 1) Try to find an existing AI message that already has the same replyMessageId + for (let i = chatMsgList.value.length - 1; i >= 0; i--) { + const it = chatMsgList.value[i]; + if (it && it.msgType === MessageRole.AI && it.replyMessageId === data.replyMessageId) { + aiMsgIndex = i; + break; + } + } + + // 2) If not found, check pendingMap for currentSessionMessageId + if (aiMsgIndex === -1 && currentSessionMessageId && pendingMap.has(currentSessionMessageId)) { + const idx = pendingMap.get(currentSessionMessageId); + if (idx >= 0 && idx < chatMsgList.value.length) { + const item = chatMsgList.value[idx]; + // If the pending item already has a different non-empty replyMessageId, create a new AI entry + if (item && item.msgType === MessageRole.AI && item.replyMessageId && item.replyMessageId !== data.replyMessageId) { + const aiMsg = { + msgId: `msg_${chatMsgList.value.length}`, + msgType: MessageRole.AI, + msg: "", + isLoading: false, + messageId: currentSessionMessageId, + replyMessageId: data.replyMessageId || '', + componentName: "", + title: "", + finish: false, + }; + chatMsgList.value.push(aiMsg); + aiMsgIndex = chatMsgList.value.length - 1; + } else { + // Reuse the pending item + aiMsgIndex = idx; + } + } + } + + // 3) If still not found, create a new AI message for this replyMessageId + if (aiMsgIndex === -1) { + const aiMsg = { + msgId: `msg_${chatMsgList.value.length}`, + msgType: MessageRole.AI, + msg: "", + isLoading: false, + messageId: currentSessionMessageId, + replyMessageId: data.replyMessageId || '', + componentName: "", + title: "", + finish: false, + }; + chatMsgList.value.push(aiMsg); + aiMsgIndex = chatMsgList.value.length - 1; } } else { - // 向后搜索最近的 AI 消息(回退逻辑) + // No replyMessageId: fall back to most recent AI message for (let i = chatMsgList.value.length - 1; i >= 0; i--) { if (chatMsgList.value[i] && chatMsgList.value[i].msgType === MessageRole.AI) { aiMsgIndex = i; @@ -572,7 +603,7 @@ const handleWebSocketMessage = (data) => { } // 清理 pendingMap / timeout - const ownedMessageId = chatMsgList.value[aiMsgIndex].messageId || msgId; + const ownedMessageId = chatMsgList.value[aiMsgIndex].messageId || null; if (ownedMessageId) { if (pendingTimeouts.has(ownedMessageId)) { clearTimeout(pendingTimeouts.get(ownedMessageId)); diff --git a/src/pages/index/components/module/AnswerComponent/index.vue b/src/pages/index/components/module/AnswerComponent/index.vue index 18f1be4..2eca5e2 100644 --- a/src/pages/index/components/module/AnswerComponent/index.vue +++ b/src/pages/index/components/module/AnswerComponent/index.vue @@ -3,7 +3,7 @@ - + 游玩划重点 @@ -12,7 +12,7 @@ - + 查看详情 @@ -43,10 +43,28 @@ const props = defineProps({ // 用于强制重新渲染的key const textKey = ref(0); -// 处理文本内容(纯计算,不应有副作用) +// 处理文本内容:按行截断以保证预览最多显示三行(更贴近视觉行数) +// 点击“查看详情”会跳转到完整页面(不受预览截断影响)。 +const PREVIEW_LINES = 3; +const PREVIEW_CHAR_LIMIT = 100; // 作为备用,当没有换行但过长时也会截断 const processedText = computed(() => { - if (!props.text) return ""; - return String(props.text); + const txt = props.text ? String(props.text) : ""; + if (!txt) return ""; + + // 按行分割(保留空行) + const lines = txt.split(/\r?\n/); + + // 如果行数超过限制,截取前 PREVIEW_LINES 行并添加省略号 + if (lines.length > PREVIEW_LINES) { + return lines.slice(0, PREVIEW_LINES).join("\n") + "..."; + } + + // 若虽然行数不超过,但总长度仍然很长,做字符级截断作为兜底 + if (txt.length > PREVIEW_CHAR_LIMIT) { + return txt.slice(0, PREVIEW_CHAR_LIMIT) + "..."; + } + + return txt; }); // 监听 text 变化:更新 textKey 并同步 isOverflow(合并为单一响应函数,避免冗余) @@ -54,7 +72,8 @@ watch( () => props.text, (newText, oldText) => { const textStr = newText ? String(newText) : ""; - isOverflow.value = textStr.length > 100; + const lines = textStr.split(/\r?\n/); + isOverflow.value = lines.length > PREVIEW_LINES || textStr.length > PREVIEW_CHAR_LIMIT; if (newText !== oldText) { textKey.value++; } diff --git a/src/uni_modules/zero-markdown-view/components/zero-markdown-view/zero-markdown-view.vue b/src/uni_modules/zero-markdown-view/components/zero-markdown-view/zero-markdown-view.vue index 82db93b..2409d7f 100644 --- a/src/uni_modules/zero-markdown-view/components/zero-markdown-view/zero-markdown-view.vue +++ b/src/uni_modules/zero-markdown-view/components/zero-markdown-view/zero-markdown-view.vue @@ -111,38 +111,40 @@ export default { `, // 一级标题 h1: ` - margin:4px 0; - font-size: 20px; - text-align: center; - font-weight: bold; - color: ${themeColor}; + margin:8px 0; + font-size: 20px; + line-height: 1.6; + text-align: center; + font-weight: bold; + color: ${themeColor}; font-family: ${fontFamily}; - padding:3px 10px 1px; - border-bottom: 2px solid ${themeColor}; - border-top-right-radius:3px; - border-top-left-radius:3px; - - `, + padding:6px 10px 4px; + border-bottom: 2px solid ${themeColor}; + border-top-right-radius:3px; + border-top-left-radius:3px; + `, // 二级标题 h2: ` - margin:4px 0; - font-size: 18px; - text-align:center; - color:${themeColor}; + margin:6px 0; + font-size: 18px; + line-height: 1.55; + text-align:center; + color:${themeColor}; font-family: ${fontFamily}; - font-weight:bolder; - padding-left:10px; - // border:1px solid ${themeColor}; - `, + font-weight:bolder; + padding-left:10px; + // border:1px solid ${themeColor}; + `, // 三级标题 h3: ` - margin:4px 0; - font-size: 16px; - color: ${themeColor}; + margin:6px 0; + font-size: 16px; + line-height: 1.5; + color: ${themeColor}; font-family: ${fontFamily}; - padding-left:10px; - border-left:3px solid ${themeColor}; - `, + padding-left:10px; + border-left:3px solid ${themeColor}; + `, // 引用 blockquote: ` margin:4px 0; @@ -151,7 +153,7 @@ export default { color: #777777; border-left: 4px solid #dddddd; padding: 0 10px; - `, + `, // 列表 ul: ` font-size: 14px; @@ -231,25 +233,28 @@ export default { `, // 一级标题 h1: ` - margin:4px 0; - font-size: 20px; - color: ${themeColor}; - font-family: ${fontFamily}; - `, + margin:8px 0; + font-size: 20px; + line-height: 1.6; + color: ${themeColor}; + font-family: ${fontFamily}; + `, // 二级标题 h2: ` - margin:4px 0; - font-size: 18px; - color: ${themeColor}; - font-family: ${fontFamily}; - `, + margin:6px 0; + font-size: 18px; + line-height: 1.55; + color: ${themeColor}; + font-family: ${fontFamily}; + `, // 三级标题 h3: ` - margin:4x 0; - font-size: 16px; - color: ${themeColor}; - font-family: ${fontFamily}; - `, + margin:6px 0; + font-size: 16px; + line-height: 1.5; + color: ${themeColor}; + font-family: ${fontFamily}; + `, // 四级标题 h4: ` margin:4px 0;