---
name: baoyu-workbench-frontend-pitfalls
description: 工作台前端铁律：搜索框/空状态/函数声明/字段映射/Tunnel运维/邮箱登录/备份恢复/CDN部署/离线登录/JSON优先加载/全面体检/Tailscale端口冲突/双路径部署/进程管理/跨文件合并/CDN缓存/init自调用/看板筛选器权限/KPI税合计全局修复/Manager默认筛选/app.html入口/KPI三层递进/Q3当季卡片bug/目标数据污染检测。新增#107。
---




# 宝锐工作台前端开发铁律

> 涉及「某角色看到全公司数据」或「删除某块内容，所有账户都不展示」：先读 `references/company-wide-scope-and-block-removal.md`（全公司口径数据源清单 + 删块四步：两入口同改 → node --check → 保留下游控件 → 部署后 grep 线上残留=0 + sha 比对）。
>
> 涉及「把某类要求分单独类别统计」「再单列一类」「清洗得不完全，把漏的拿出来」：先读 `references/derived-category-classification.md`（两级关键词规则：强词含备注/弱词只看标题正文；语境排除；互斥 vs 叠加；分桶求和 == 总数的硬校验）。
>
> 涉及「布局不合理 / 排布不合理的排布」「手机上输入框被压扁」「改版面」「新建含动态增删行的表单页」：先读 `references/responsive-form-layout.md`（排查顺序=先量 DOM 再改、视觉模型结论当假设；动态行栅格模板必须写在 CSS 类里否则媒体查询覆盖不了；只写单轴定位会打乱整行；底栏与主内容边线对齐；互斥分支的 toggle 语义与不变量检查；程序化填表必须派发 input）。
>
> 涉及「表格某列超出页面范围 / 列被裁掉 / 要适应窄屏 / 缩放调合适」：先读 `references/wide-table-fit-in-iframe.md`（工作台 iframe 外层 overflow:hidden 会静默剪掉溢出列；四招压缩 + 横滑兜底；验收必须量 1024 宽）。
>
> 涉及「服务被杀了没人拉起」「改了看门狗脚本要验」「定时任务到底有没有生效」：先读 `references/service-watchdog-verification.md`（看门狗 MAP 字段陷阱、必须“真杀一次”才算验证、测脚本时不要用管道接输出）。
>
> 涉及「地址栏改 hash 不切页」「分享带错点的链接打不开对应 Tab」「截图预览四张一模一样」：先读 `references/hash-tab-spa-navigation.md`（hash 标签页必须挂 `hashchange` 监听；截图/探活每页要换 URL 参数）。

## 112. 工作台内嵌页改动的发布闭环 + 免登录渲染验证

**发布闭环（改 `工具/X.html` 后逐条做完，漏一条用户就看到旧版）**：
1. 提取内联 JS 跑 `node --check`。
2. **升工作台 `工具/index.html` 里该页 iframe 的 `?v=`**：iframe 的 URL 不变时浏览器直接吃缓存，页面改对了用户仍是旧样（`src="/工具/X.html?v=20260910g"` → 换新串）。这是「改完没生效」最常见的原因，不是部署失败。
3. 部署页面本身 + 部署 `工具/index.html` → `/workbench/index.html`（双路径，来自同一个本地文件）。
4. 线上**比内容不比状态码**（`curl -H 'Cache-Control: no-cache'`）并断言本地 == 线上逐字节 + 本地改动特征串在线上命中。

**权限/API 页的免登录渲染验证（过不了登录 ≠ 不能验）**：这类页面靠 token 调 `/api/...` 拉数据，浏览器里直接开只会看到「请先登录」。验证办法：用本机 8765 起 http 源打开页面（`file://` 下 fetch 被 CORS 挡），同源 `fetch('/数据/x.json')` 取**真实数据** → 手动赋 `DATA`（补上 `visible_customers` 这类 render 依赖字段）、显示 `#app`、调 `render()`，再断言 KPI 文案、芯片计数、筛选后行数、行内标签数。用真实数据而不是手造样本，才能暴露口径错。

## 111. 看板趋势图：分段图合并为全年 + 分段合计先核对（2026-09-20）

**改动形态**：把「📅 月度目标/达成趋势（H1/H2 分段切换）」与「📊 H2月度目标 vs 实际」两张卡并成一张全年 12 个月图 → 去掉 `state.h1h2` 分段按钮、删掉 H2 卡、`drawMonthlyChart(kpi)` 固定 12 个月，数据由 `getKPI()` 统一提供 `yearTargets`/`yearActuals`（admin 与个人**两个分支**都要加；个人分支 1–6 月目标为 0 是历史行为，不要临时发明）。

**合并两条独立序列前，先算两边合计再跟汇总卡对一遍**。H1 月目标来自 `h1Monthly.total.targets`、H2 月目标来自 `Σ kpiDashboard.depts[].h2_months`，而 KPI 卡的年度目标来自 `kpiDashboard.total_target` —— 三处各自展示时谁也看不出，合成一条曲线立刻自相矛盾（实测 3,255 万 + 4,786 万 = 8,041 万 ≠ 6,950 万，差 1,091 万）。**先把差异算出来报给用户定口径，再决定怎么上线**；不要默默发一条自己都对不上的曲线。

**12 个月标签必须窄屏自适应**：`el.clientWidth < 560` 时把 `1月…12月` 降级成 `1…12`。6 个月的图改成 12 个月后手机端必叠加（这是「分段改整年」新增的坑，不是原有问题）。

**数据源/当月口径全套对照表见 `references/dashboard-kpi-trend-data-sources.md`**（月度目标、逐月实际、年度目标卡片分别取哪个字段；当月为什么必须用 `kpiProgress` 覆盖快照）。同文件另有两节必读：**「月度目标：权威源与口径统一」**（权威源 = 年度拆解表的「汇总」表，按人×12 月；用「部门月合计 == 部门年度目标」判定哪份可信，公司目标下调后月度拆解常没同步）与 **「派生键防回归」**（注入 `workbench_data.json` 的新键必须同步加进 `merge_workbench_data.py` 的 `FILES` 映射并重跑 merge，否则管道重跑静默丢键、页面悄悄退回旧口径；改数据驱动的视图要**连 JSON 一起部署**）。

**验数据驱动改动要走本地服务**：`http://127.0.0.1:8765/工具/index.html` 打开再验（同源能直接 fetch `/数据/workbench_data.json`）。用 `file://` 打开时数据 fetch 失败，图表会**静默渲染成全 0**，极易误判为代码 bug（自己先白查一轮）。判定顺序：「图表全 0」→ 先查 `state.data` 是否真的加载到（长度/键数）→ 再怀疑绘图代码。

**部署回读的假警报**：`curl -sL "$url" | shasum` 在 curl 失败时拿到的是**空串 sha**（`e3b0c442...`），会被误读成「CDN 不一致」。改成 `curl -sL --retry 2 -m 30 "$url" -o /tmp/x` → 先 `wc -c` 确认有字节 → 再比 sha；并 `grep` 线上的新标记（新标题/新函数名）确认「部署的确是本次改动」，只看 sha 定位不到部署了什么。

## 6. 访前准备重点客户快捷入口

### 筛选规则：必须与 MCP `field_17186c` 标签重合

**重点客户 = MCP `field_17186c === 'A类'`**（不是取数组前 N 个）。374 个 A 类客户 / 1700 全量。

```javascript
// ❌ 错误：取前 10 个，不区分是否重点
var top10 = all.slice(0, 10);

// ✅ 正确：按 MCP field_17186c 筛选 A 类
var keyCustomers = all.filter(function(c) { return c.field_17186c === 'A类'; });
var top = keyCustomers.slice(0, 30);
```

### MCP 客户关键字段速查

| 字段 | 含义 | 示例值 |
|------|------|--------|
| `field_17186c` | 客户级别 | A类(374)/B类(136)/C类(884)/D类(306) |
| `field_e62869` | 产品线标签 | `[{label:"分子诊断"}]` / `[{label:"生命科学"}]` |
| `field_c3224b` | 集团简称 | "美吉集团" |
| `field_5ed7ab` | 销售部门 | "诊断原料销售拓展部" |
| `work_item_id` | 飞书项目工作项ID | 7057317049 |

`customer_levels.json` 中的 `level: "重点客户"` 是后续人工标注，与 MCP 原始分类独立。访前准备取 MCP 口径。

### 链接规则：跳转客户详情页，不用 create URL

```javascript
// ✅ 正确：FEISHU_URL + work_item_id 直达客户详情
return '<a href="' + FEISHU_URL + c.work_item_id + '" target="_blank">...</a>';

// ❌ 错误：硬编码 project_key=6593cd71471290e3cc6be6e6 创建活动
// 生命科学客户不在该空间 → "空间不存在"
return '<a href="https://project.feishu.cn/baoyu/workitem/create?project_key=...">...</a>';
```

## 69. 订单下单人必须从飞书「下单人」角色获取，不能用客户 owner（2026-07-28）

**症状**：离职人员（黄文清/李丽佳）名下 58 个客户的所有订单仍显示他们为下单人。

**根因**：`pull_orders.py` 通过 `mcp_customers.json` 的 `owner` 字段映射创建者。离职人员的客户 owner 未更新。

**修复**：调用 `get_workitem_brief` API 获取每个订单的 `role_members`，提取「下单人」角色用户。逐订单查询（186次），用缓存文件 `order_role_cache.json` 避免重复。

```python
# 关键代码
result = mcp("get_workitem_brief", {"project_key": PK, "work_item_id": wid})
role_members = result.get('work_item_attribute', {}).get('role_members', [])
for rm in role_members:
    if rm.get('name') == '下单人':
        members = rm.get('members', [])
        if members: xdr = {'name_cn': members[0].get('name', ''), 'email': members[0].get('email', '')}
```

**缓存**：`order_role_cache.json`（按 orderId 索引），已缓存的订单不会重复查询。

**效果**：黄文清 26 客户→0，李丽佳 32 客户→0。186/186 下单人角色全部命中。

## 47. 飞书链接格式校正（各空间不同）

| 空间 | 正确格式 |
|------|---------|
| 客户(`xsguanli`) | `https://project.feishu.cn/xsguanli/kh/detail/{id}` |
| 客诉(`br-shgl`) | `https://project.feishu.cn/br-shgl/khts/detail/{work_item_id}` |
| 通用(`baoyu`) | `https://project.feishu.cn/baoyu/workitem/{id}` |

**NOT** `baoyu/workitem/{id}` for everything. 定义专用常量避免串用。

## 7. Tunnel 运维（2026-07-26 起：Tailscale Funnel 固定域名）

**当前架构**：`https://macbook-air-2.tail6864e8.ts.net` → 127.0.0.1:8765（Tailscale Funnel，URL 永久固定）。trycloudflare 临时隧道已全部弃用（曾出现 3 个死 URL：times-edinburgh / gazette-pretty / drug-round-concert）。

```bash
# 检测 API 是否可用
curl -s --max-time 10 "https://macbook-air-2.tail6864e8.ts.net/api/todos" | head -c 20
# 无输出/超时 → 检查 Tailscale 是否在线
tailscale status --json | python3 -c "import json,sys;d=json.load(sys.stdin);print(d['Self']['Online'])"
# Funnel 状态
tailscale funnel status
# 恢复：tailscale up → tailscale funnel --bg --https=443 http://127.0.0.1:8765
```

### Tailscale Serve 端口冲突（2026-07-28 已踩坑）

**症状**：`python3 销售工作台服务.py 8765` 启动即报 `OSError: [Errno 48] Address already in use`，但 `lsof -i :8765` 无输出。

**根因**：Tailscale Serve/Funnel 可能已绑定该端口，但 `lsof` 不显示（沙箱进程）。`netstat -anv | grep 8765` 可看到所有者是 `io.tailscale.ipn`。

**诊断**：
```bash
netstat -anv | grep 8765  # → io.tailscale.ipn 即 Tailscale
tailscale serve status     # 查看当前绑定的端口和代理目标
```

**修复**：
```bash
tailscale serve reset      # 清除所有 serve 配置，释放端口
# 然后重启工作台服务
```

> ⚠️ `tailscale serve reset` 会清除所有 Funnel/Serve 配置。如需保留其他服务，用 `tailscale serve --https=<新端口> / http://127.0.0.1:8000` 迁移而非 reset。

## 76. CloudBase 双路径部署——`/index.html` 与 `/workbench/index.html` 是独立文件

这两条路径在 CloudBase 上是**完全独立**的文件，修改一处不会自动同步到另一处：

**本地源文件 → CloudBase 路径映射（铁律）**：
| 本地文件 | CloudBase 路径 | 部署命令 |
|---------|---------------|---------|
| `工具/index.html` | `/workbench/index.html` | `tcb hosting deploy 工具/index.html /workbench/index.html -e ...` |
| `工具/销售工作台.html` | `/工具/销售工作台.html` 或本地使用 | 仅本地 localhost:8765 部署，一般不上 CloudBase |
| `数据/workbench_data.json` | `数据/workbench_data.json` | 由 `cb_sync.sh` 自动部署 |

**⚠️ 关键陷阱**：用户通过飞书工作台入口访问的是 `/workbench/index.html`（由 `工具/index.html` 部署），**不是** `工具/销售工作台.html`。本地改 `销售工作台.html` 后如果不改 `index.html` 并重新部署到 CloudBase，用户永远看不到更新。

```bash
# 主入口（/index.html）—— 一般不用
tcb hosting deploy /tmp/index_workbench.html /index.html -e bier-sales-d0gatbvlx288724e9

# 飞书工作台入口（/workbench/index.html）—— 主力入口，源文件是 工具/index.html
tcb hosting deploy 工具/index.html /workbench/index.html -e bier-sales-d0gatbvlx288724e9
```

**检测**：
```bash
# 比较两处代码是否一致
curl -s -H "Cache-Control: no-cache" "https://bier-sales-...tcloudbaseapp.com/index.html?v=$(date +%s)" | md5
curl -s -H "Cache-Control: no-cache" "https://bier-sales-...tcloudbaseapp.com/workbench/index.html?v=$(date +%s)" | md5
```

用户说"工作台打不开"或"飞书里的工作台有问题"时，先确认他访问的是哪个路径，再部署对应位置。飞书应用通常引用 `/workbench/index.html`。

## 8. replace_all 铁律 + patch 转义陷阱

### replace_all 禁令

HTML/JS 混合文件严禁 replace_all=true——语义相同的代码片段可能在不同函数中，patch 会注入到错误位置。

### patch 工具 Escape-drift 陷阱

patch 工具处理含引号的 HTML 文件时，频繁报 Escape-drift detected 错误。这是工具链序列化问题，不是代码错误。

铁律：HTML 文件改动用 Python execute_code 直接读写替换，不用 patch：
  with open('file.html', 'r') as f: content = f.read()
  content = content.replace(old, new)
  with open('file.html', 'w') as f: f.write(content)
单处简单替换仍可尝试 patch，遇 escape-drift 立即切 Python。

**真实案例**：用 `replace_all=true` 替换 `adaptMcpFields` 中的 `if (key === 'complaints') { return items; }`，结果也匹配了 `renderThreeYearMonthly` 中的 `cumY` 函数体内的相似代码，30 行投诉映射代码被注入到中间，导致：
- JS 语法错误 `Unexpected end of input`
- `init()` 未定义，登录下拉框空白
- 整个页面不可用

**恢复**：从备份 `销售工作台_backup_20260725.html` 恢复 → 部署 → Python 脚本批量安全重加改动。

**铁律**：
1. replace_all 仅用于极其唯一的常量字符串（如 Tunnel URL）
2. 同一语义多处出现时，加前后 3-5 行上下文使 old_string 唯一
3. 改完立即 `python3 -c "...re..."` 确认旧模式已清理 + `node --check` 验 JS 语法
4. 如果已损坏：从备份恢复优先于手动修复

## 11. 从备份恢复后的三步检查清单

`replace_all` 破坏后最可靠恢复路径：备份 → Python 批量应用 → 补齐遗漏。

**恢复后必查**（三道关，缺一不可）：
1. **CloudBase SDK**：备份可能是 v1（无 SDK）。确认文件中有 `<script src="...cloudbase.full.js">`。没有就加。
2. **`CB_ENV` 配置**：v1 备份可能缺 `var CB_ENV = ...; var ROLE_KEY = ...`。在 Config 段补齐。
3. **登录方式**（2026-07-26 以后）：v1 备份有硬编码 `<option>` 下拉框。已改为邮箱文本框登录，见下文第 12 条。

**备份自身可能也有 bug**——部署前必须 `node --check` 验 JS。今天备份就缺了一个 `}`。

**Python 脚本批量操作**：当改动太多无法逐个 patch 时，用 Python 脚本读文件→字符串替换→写回。比 patch 快 10 倍，且不会出现 escape 问题。

## 详细资料（按需查阅）

> 以下文件不在本 SKILL.md 内，需要时再读；正文只保留每次都会用到的部分。

- **`references/archive-changelog.md`** — 含 9 节：30a. 多步 Python 替换括号必验——断一次就回滚重来（2026-07-27）、21. loadData() 数组作字符串拼接 Bug、23. 工作台文件无自动备份——回滚即死路、26. state 对象拼进字符串 → `[object Object]` 全员串号（2026-07-26）、2…
- **`references/auth-permission.md`** — 含 13 节：110. 带鉴权的数据 Tab（上传/台账/文件下载）三个坑（2026-09-19）、110b. user_roles 锁库 + 用户管理服务端化（2026-09-20）、105/104. 档案页权限泄露 & 集团级归属（2026-09-10）、101. 待办跨设备不同步：todos 集合 ACL …
- **`references/data-pipeline.md`** — 含 31 节：113. 清单页新增统计维度：默认交叉、四处同步、页面端派生、100. AI 助手文件上传：前端校验别比后端严（2026-09-04）、102. 看板环比基准月错误：kpi.last 写死 6 月而非上月（2026-09-05）、3. 表格空数据必须有提示、4. MCP 字段映射（必读！字段不匹配是…
- **`references/deploy-cdn.md`** — 含 8 节：114. 浏览器里 CDN 页面不对，先用 curl 复核再下结论、52. 缓存破坏代码插进 `function loadData()` 定义导致语法错误、17. Tunnel URL 更新：必须更新 3 处以上、20. CDN 缓存验证 vs Origin 文件、29. 审计无本地源的 Cloud…
- **`references/details.md`** — 含 2 节：13. Tunnel 假活检测与故障分类、13. Tunnel 假活检测与故障分类
- **`references/feishu-api.md`** — 含 7 节：103. 飞书文件夹消息网关不处理（2026-09-05）、53. Feishu open_id 按 App 独立——跨 App 推送必须重新获取（2026-07-27）、71. 客诉列表按创建时间倒序（2026-07-28）、15. MQL 数组解析与 workitem_related_multi…
- **`references/frontend-ui.md`** — 含 8 节：109. Tab 分组 + 移动端底栏精简 + AI 页移动端布局（2026-09-19）、107. 客户tab搜索框被侧栏遮挡（2026-09-11）、106. 搜索框打断中文输入法（2026-09-11）、102. 看板「环比」基准月写死6月，H2月份环比全错（2026-09-05）、2. 函数…

### 既有参考文件（原正文未引用，此处补齐索引）

- **`references/2026-07-29-songxuewei-onboarding.md`** — 宋学伟上线全链路修复记录（2026-07-29）
- **`references/2026-08-01-app-html-merge.md`** — app.html + index.html 合并踩坑记录 (2026-08-01)
- **`references/2026-08-01-kpi-tax-fix.md`** — KPI 税合计 + 看板筛选器修复 (2026-08-01)
- **`references/2026-09-order-date-and-amount.md`** — 订单管理日期偏一天 + 金额数据源修复（2026-09-07）
- **`references/20260728-session-patterns.md`** — 2026-07-28 会话新增模式
- **`references/ai-assistant-fetch-pitfalls.md`** — AI 助手流式改造 + fetch 错误处理陷阱（2026-09-08）
- **`references/ai-assistant-sse-streaming.md`** — AI 助手多端点降级 + SSE 流式超时修复（2026-09-08 实战）
- **`references/ai-assistant-streaming.md`** — AI助手链路架构 + 「回答超时」根治（流式输出）
- **`references/ai-chat-copy-feature.md`** — AI 助手「一键复制对话」实现与验证（2026-09-18）
- **`references/ai-chat-streaming-fetch.md`** — AI 助手流式 fetch 排坑（2026-09-08）
- **`references/batch-reapply-from-backup.md`** — 从备份干净重打补丁模板
- **`references/blob-download-feishu-webview.md`** — 飞书 webview 打开/下载文件铁律（2026-09-10）
- **`references/complaint-customer-isolation.md`** — MCP `key_label_value_list` 字段格式与客诉客户关联
- **`references/complaints-data-reference.md`** — 客诉（投诉）数据处理参考
- **`references/complaints-pipeline-three-bugs.md`** — 客诉数据管道：三个致命缺陷（2026-07-27 诊断）
- **`references/cust360-and-decision-chain.md`** — 客户 360 弹窗 + 决策链集成（2026-07-29）
- **`references/customer-toolbar-occlusion.md`** — 客户tab搜索框被侧栏遮挡 —— toolbar 缺 margin-left（2026-09-11）
- **`references/daily-brief-architecture.md`** — 销售日常简报架构（2026-07-27）
- **`references/deprecated-email-login.md`** — 邮箱登录体系（2026-07-26 历史方案，已被 #74 替代）
- **`references/email-login-migration.md`** — 邮箱登录迁移记录
- **`references/feishu-customer-link.md`** — 宝锐飞书客户链接路由逻辑
- **`references/feishu-per-creator-push.md`** — 飞书推送：按销售员分发（2026-07-27 终版）
- **`references/frp-tunnel-tsnet-blocked.md`** — 工作台线上出口：ts.net被墙 → frp反向隧道方案
- **`references/ima-api-limitations.md`** — IMA OpenAPI 能力边界与知识库搜索（2026-07-27实测）
- **`references/kpi-multi-horizon-cards.md`** — 看板 KPI 卡片三层递进模式 (2026-08-09)
- **`references/kpi-tax-fix-all-modules.md`** — KPI 税合计数据源全局修复 (2026-08-01)
- **`references/life-science-opp-view.md`** — 生命科学 MCP 商机视图查询参考
- **`references/manifest-and-funnel-pitfalls.md`** — 清单格式、Funnel 与存档区铁律（2026-07-27 血案汇总）
- **`references/market-intel-agent.md`** — 诊断原料市场调研 Agent（market_intel_agent.py）
- **`references/mcp-field-5ed7ab-options.md`** — MCP field_5ed7ab 选项清单（2026-07-31）
- **`references/mcp-field-mappings.md`** — MCP 客户工作项字段映射表
- **`references/mql-value-type-parsing.md`** — MQL moql_field_list 值类型解析参考
- **`references/multi-base-api-fallback.md`** — 多基址 API 回退：401 必须换基址，不能「拿到 JSON 就 resolve」
- **`references/new-tab-and-api-fallback.md`** — 新增 Tab 的标准三步 + 多基址回退陷阱
- **`references/order-period-kpi-layout.md`** — 订单管理「本年累计」与周期切换的对应规则
- **`references/permission-isolation.md`** — 权限隔离与集团级归属（2026-09-10）
- **`references/pipeline-scripts.md`** — 宝锐工作台数据管道脚本清单
- **`references/preloaded-images-fallback.md`** — Preloaded Image Fallback Pattern for HTML Dashboards
- **`references/preparation-status-pattern.md`** — 备货状态模式（2026-07-28）
- **`references/rollback-investigation.md`** — 工作台回滚请求排查清单
- **`references/search-input-ime.md`** — 搜索框打断中文输入法：组合输入期间禁止重渲染（2026-09-11）
- **`references/security-hardening-20260729.md`** — 安全加固实录（2026-07-29）
- **`references/sf-delivery-pipeline.md`** — 顺丰物流推送完整链路
- **`references/three-year-compare-permission.md`** — 三年逐月销售对比权限：只给 admin+gm（2026-08-21）
- **`references/toISOString-utc-offset.md`** — toISOString().slice(0,10) 取日期偏一天——UTC 时区坑（2026-09-07）
- **`references/toisostring-timezone-offset.md`** — toISOString() 时区偏移导致日期显示偏一天（2026-09-07）
- **`references/visit-card-api.md`** — 访前准备拜访卡 API（完整版）
- **`references/visit-prep-sync-verification.md`** — 访前准备 Tab 归档同步机制 + 浏览器实测验证（2026-08-22 实测）
- **`references/wps-dispimg-extraction.md`** — WPS Office DISPIMG Image Extraction
- **`references/配置字段解析.md`** — 配置字段解析：标量 vs 数组
