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 结构 | 标签嵌套要完整 |
| 4 | loading 不显示 | 转义过度 | 模板字符串里普通引号不需要 \ |
| 5 | 变量名错误 | 拼写 | chunkInfo vs chunksInfo — JS 不报错但显示空 |
| 6 | 404 | API 路径 | 前端 URL 要和后端路由对齐 |
经验
- “.map() 花括号体必须 return” — 这是个高频坑,不止一次掉进去了
- 持久化不要和渲染混在一起 — 独立函数,独立调用时机
- 修 bug 时别顺手重构 — 一次只改一个东西,每个改完立即验证
- 变量名检查要养成习惯 — 多加一个 s 或少了对象前缀,JS 静默吞掉