# baoyu-workbench-patterns — deploy cdn

### 缓存破坏

浏览器可能缓存旧版JS导致修复无效。`node --check` 通过不代表浏览器拿到了新版本。

**三重防缓存meta标签（必须加）**：此问题已导致至少3次"未更新"反馈——用户端 HTTP 200 但看到旧版，服务器重启、curl验证均无效。

```html
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
```

排查流程：① `curl` 验证服务器文件已更新 → ② 让用户**无痕窗口**（`Cmd+Shift+N`）打开 `?v=N` URL → ③ 确认后告知用户 `Cmd+Shift+R` 强刷正常窗口。

## 常见陷阱

> ⚠️ 2026-07-28 新增：顺丰状态误判修复（opCode优先）、订单按单号去重、KPI金额 vs shipment金额、多选筛选器组件、备货状态标注、下单人角色查询（get_workitem_brief）。详见 `references/2026-07-28-session-patterns.md`。

> ⚠️ 2026-07-25 新增陷阱，详见 `references/2026-07-25-session-pitfalls.md`：三表一书关键人物≠当前负责人、HTML相对路径、客诉URL格式修正。

> ⚠️ 本 session (2026-07-13) 新增大量实战陷阱，详见 `references/2026-07-13-session-pitfalls.md`：replace_all 污染、孤儿 else-if、变量顺序、万元单位统一。

> 📊 ERP数据清洗铁律见 `references/erp-data-cleaning.md`：删除合计行、空白销售员映射、MCP vs ERP数据源确认。

> ⚠️ HTML JS 字符串编辑的深度避坑指南见 `references/html-string-editing.md`。

| 陷阱 | 现象 | 修复 |\n|------|------|------|\n| **HTML修改后未部署到CloudBase** | `cb_sync_data.sh` 完成但页面没变 | 该脚本只同步数据JSON，HTML必须手动 `tcb hosting deploy 工具/index.html /workbench/index.html -e {env}` |\n| **KPI ytd只取total_h1漏H2** | 看板累计显示2164万(H1)而非2631万 | `getKPI()` 中 `ytd` 必须用 `dash.total_h1_h2 \\|\\| dash.total_h1`，`kpi_dashboard.json` 已有此字段 |\n| **改渲染忘改数据源** | 改了卡片模板但数字不变 | 改 `renderDashboard()` 卡片时同步检查 `getKPI()` 返回的变量来源——两层都要改 |\n| inline onclick 引号嵌套 | JS语法错误或单击无响应 | DOM绑定/事件代理 |
| 文本节点click target | `e.target.closest()` 返回 null | `e.target.nodeType===3?parentElement:e.target` |
| localStorage 格式升级 | 旧 string → 新 array，forEach 报错 | 类型守卫 `typeof==='string'?[val]:val` |
| `colspan` 列数不对 | 空数据提示行没对齐 | 每次加列后同步更新 colspan |
| **API卡死导致白屏** | fetch永不resolve，Promise.all全部阻塞 | ①改用本地JSON ②fetchWithTimeout(5s) ③重启API服务器 |
| **浏览器缓存旧JS** | node--check通过但页面不变 | URL加?v=N参数或Cmd+Shift+R强刷 |
| **patch 工具转义失败** | "Found N matches"或"Escape-drift" | 改用 `terminal` + Python 直接写文件，避免在 `<script>` 内用 patch 修改含转义引号的字符串 |
| **replace_all 污染其他函数** | `replace_all=true` 将 `var h=''` 替换到所有函数 | 变量声明行绝不使用 replace_all；超50行修改直接用 Python write_file |
| **变量丢失导致静默失败** | addTodo()不报错但不工作 | 检查 `var TODO_KEY` 和 helper functions 是否存在——代码重组时容易误删定义 |
| **replace_all 污染所有函数** | `replace_all=true` 将 `var h=''`/`active.length` 等通用变量声明插入render()/renderDashboard()等所有函数，造成 `list is not defined` 等连锁错误 | 🔴 **铁律：变量声明行绝不使用 replace_all；≥3个同名匹配时直接 Python write_file 重写文件** |
| **变量丢失导致静默失败** | addTodo()不报错但不工作 | 检查 `var TODO_KEY` 和 helper functions——代码重组时容易误删定义行 |
| **孤儿 else-if 残留** | `SyntaxError: Unexpected token 'else'` | Python 行级删除 filter blocks 时只删 `if` 行留下了 `else-if`；配对删除必须同时移除 |
| **Promise.all fetch 链断裂** | `H1_MONTHLY`/`KPI_PROGRESS` 为 Response 对象而非解析后的 JSON | **铁律：每个 `fetch()` 必须在 `Promise.all` 内独立链 `.then(r=>r.json()).catch(()=>null)`，不能多条 fetch 共用一条 `.then()`** |
| **MCP销售订单`金额`=0** | MQL查询`金额`/`单价`始终返回0 | 飞书MCP的销售订单不存储实际金额。**实时销售进度必须用ERP导出(xlsx)的`价税合计`列**。详见 `references/erp-data-cleaning.md` |
| **ERP合计行导致翻倍** | 未删`单据编号='合计'`行→金额翻倍 | 必须 `erp[~erp['单据编号'].str.contains('合计',na=False)]` |
| **2025年ERP销售员列使用代号** | 筛选大客户部时返回空 — 2025年ERP用"大客户1/大客户2"而非真实姓名 | 做代号映射`{'大客户1':'黄明月','大客户2':'赵云浩'}`，详见`references/erp-data-cleaning.md` §7 |
| **ERP测试单提取需按单据号去重** | 一个测试单多行(多物料)→计数翻倍 | `groupby('客户')['单据编号'].nunique()`，测试单价税合计为0(赠品)不计金额。详见`references/erp-test-order-analysis.md` |
| **仪表盘分析文字不显眼** | 用户反馈"分析的文字要显眼" | 分析结论使用红橙渐变背景卡片：`background:linear-gradient(135deg,#fff2f0,#fff7e6);border:1px solid #ffccc7;font-size:13px`，标题红色加粗14px，关键数字红色高亮 |
| **分析默认用销售员维度** | 用户要求"具体到客户维度，而不是销售维度" | 默认深入到客户/集团/实体级别，销售员维度仅作辅助标注 |
| **万元单位统一** | 用户明确要求所有金额用"万"为单位 | `function fmtW(v){if(!v||v===0)return'0万';return(v/1e4).toFixed(1)+'万';}` — 不再用"百万"/"元"混排 |
| **KPI由用户确认优先** | dashboard显示5030万vs实际6950万 | 总经理明确年度目标时直接硬编码，不从不完整JSON聚合 |
| **数据来源如实标注** | 用户纠正"MCP"数据实际来自ERP导出 | 数据标注必须如实反映来源（ERP≠MCP），不做虚假宣传 |
| **MCP定时任务覆盖ERP数据** | 页面上数据突然变成0 | cron运行`update_kpi_progress.py`用MCP空数据覆盖了ERP写入的`kpi_progress.json`。**铁律：销售金额定时任务绝不使用MCP，仅用ERP。详见 `references/mcp-sales-data-limitation.md`** |
| **H1/H2合并中文键名陷阱** | `h1Summary.delivered` 始终为0 | MCP h1_summary.status 键名是中文（`已签收`/`无物流`），JS必须用 `h1Status['已签收']` 读取 |
| **innerHTML 全量重建导致搜索框失焦/跳动** | 输入文字自动中断、焦点被移走、搜索框闪烁 | 🔴 **debounce + render-first-then-focus**：1)保存光标位置 2)debounce(render, 300ms) 3)render()后getElementById找到新元素 4)focus+恢复光标。**关键顺序：render() → getElementById → focus（反了就白做）**。详见 `references/mcp-realtime-data-migration.md` §搜索框修复模式。 |
| **固定页眉（`position:fixed`）遮挡 `.app-main` 外的工具栏** | 搜索框/筛选栏被固定页眉挡住，无法点击或输入 | `#custToolbar` 等持久化工具栏如果放在 `.app-main` 外面（直接作为 `.app` 的子元素），不会继承 `.app-main` 的 `margin-top: var(--header-h)`。**所有 `.app-main` 外的固定页眉下级元素必须显式添加 `margin-top: var(--header-h)`**。修复：`#custToolbar { margin-top: var(--header-h); display: flex; ... }` |
| **MCP token 无写权限** | `create_workitem` / `add_comment` 等写操作报错 | 需用户在飞书项目网页版 → 个人偏好设置 → 开启 MCP 写权限 |
| **MQL 数组字段返回空** | `field_79aa0b` 等 multi-select 字段始终为空 | `mcp_pull_all` 需 `isinstance(v, list)` 分支,遍历提取 `[{key,label}]`。详见 `references/mcp-data-pipeline-v2.md` |
| **客户链接未按业务线路由** | 生命科学课题组跳客户详情页 | 新增 `feishuCustomerLink(c)`: dept含生命科学/浙江/广东 → `/story/detail/{opp_id}`；否则 `/kh/detail/{wid}`。详见 `references/mcp-data-pipeline-v2.md` |

## 飞书活动 + 新Tab集成（2026-07-25 更新）

新增「访前准备」「我的活动」两个Tab的工作流。
