Chat.html 连环追坑:6 个 bug 的根因与修复实录

#SDA#JavaScript#Debug#localStorage#前端

背景

给 Smart Doc Analyzer 加聊天持久化(localStorage)时,顺便整理了一下 chat.html 的代码结构。结果一改改出 6 个 bug,花了一晚上逐个追。


Bug 1:刷新后聊天记录翻倍

症状:每刷新一次页面,聊天记录多一倍。刷 3 次就是 8 份。

根因

旧逻辑:appendMsg() = 写 DOM + push localStorage

页面加载 → 从 localStorage 恢复 → 调 appendMsg() → 又写了回去
刷新 3 次 → 3 份叠加

修复:拆分职责。

// ❌ 之前 — 两个操作耦合
function appendMsg(role, content) {
  // ... 写 DOM
  localStorage.setItem(CHAT_KEY, JSON.stringify(msgs));  // 也写 storage
}

// ✅ 之后 — 彻底解耦
function appendMsg(role, content) { /* 只写 DOM */ }

function syncChatToStorage() {
  // 只在问答完成后批量覆盖一次
  const msgs = Array.from(chatArea.querySelectorAll('.msg'))
    .filter((_, i) => i > 0)  // 跳过欢迎语
    .map(el => ({ role, content }));
  localStorage.setItem(CHAT_KEY, JSON.stringify(msgs));
}

关键点:恢复时 innerHTML = '' 先清空 DOM,再用 appendMsg 渲染历史,不触发持久化。


Bug 2:文档列表为空

症状loadDocs() 跑完了、API 返回了 3 条数据,但列表显示空白。

根因.map() 回调里的模板字符串少了 return

// ❌ — 花括号函数体,没有 return
list.innerHTML = docs.map(d => {
  const badge = ...;
  `<div class="doc-card">...</div>`;  // 表达式语句,返回 undefined
}).join('');  // join('') = ""

// ✅
list.innerHTML = docs.map(d => {
  const badge = ...;
  return `<div class="doc-card">...</div>`;  // 👈 加 return
}).join('');

({...}) 是函数体,里面的裸语句不会自动返回。要么写 return,要么用 () => (...) 省略花括号。


Bug 3:<nav> 开标签缺失

症状:导航栏外观异常。

导航按钮裸写成了:

<!-- ❌ -->
<nav class="tab-btn active" data-tab="chat">💬 聊天</button>

缺少外层 <nav class="nav-tabs"> 包装。


Bug 4:loading 动画引号转义

症状:loading 点点点不显示。

模板字符串里多写了反斜杠:

// ❌
loadingDiv.innerHTML = `
  <div class=\"msg-avatar\">🤖</div>   // 不需要转义!
  <div class=\\\"loading-dots\\\">    // 转义过度
`;

// ✅
loadingDiv.innerHTML = `
  <div class="msg-avatar">🤖</div>
  <div class="loading-dots">
`;

模板字符串里反引号才需要 \ 转义,普通双引号不需要。


Bug 5:变量名拼写不一致 ×2

症状:文档卡片的片段数和文件名不显示。

位置写的应该是
变量定义const chunksInfo = ...✓ 正确
模板中使用${chunkInfo}${chunksInfo} ← 少了个 s
删除按钮参数${id}${d.id} ← 缺少对象前缀

都是拼写问题,但 JS 不报错,只是显示 undefined


Bug 6:提问 404

症状:提问后显示 “⚠️ 回答失败: HTTP 404”。

根因:前端请求的端点不存在。

前端(错误)后端实际
URL/api/v1/chat/api/v1/documents/qa
请求体{ question }{ query: "..." }

FastAPI 的 ask_question 函数挂在 @router.post("/documents/qa") 下,接收的是 SearchRequest(query, top_k) 模型。


修复汇总

#Bug根因分类教训
1聊天翻倍副作用耦合持久化逻辑集中,别散落在渲染函数里
2列表空白语法遗漏.map() 花括号里忘了 return
3导航异常HTML 结构标签嵌套要完整
4loading 不显示转义过度模板字符串里普通引号不需要 \
5变量名错误拼写chunkInfo vs chunksInfo — JS 不报错但显示空
6404API 路径前端 URL 要和后端路由对齐

经验

  1. “.map() 花括号体必须 return” — 这是个高频坑,不止一次掉进去了
  2. 持久化不要和渲染混在一起 — 独立函数,独立调用时机
  3. 修 bug 时别顺手重构 — 一次只改一个东西,每个改完立即验证
  4. 变量名检查要养成习惯 — 多加一个 s 或少了对象前缀,JS 静默吞掉