---
name: baoyu-html-table-standard
description: 宝锐生物HTML页面表格标准化规范 — 所有新建/改造HTML页面必须遵循此标准，确保统一的可筛选、可排序、可索引底层数据的交互体验。含完整的故障排查指南。
category: baoyu-workbench-theming
---

# 宝锐生物 HTML 页面表格标准化规范

## 核心要求
所有HTML页面中的表格必须支持：
1. **可筛选** — 全局搜索 + 按列筛选
2. **可排序** — 点击列头升序/降序切换
3. **可索引** — 可查看底层原始数据（📋 原始数据按钮）

## 使用的组件
`/工具/hermes-table.js` — 自包含、无外部依赖的通用表格组件，适配白底蓝调科技风

## 集成步骤

### Step 1: 引入脚本
在所有页面的 `<head>` 中加入（在 Chart.js 之后如需要）：
```html
<script src="/工具/hermes-table.js?v=2"></script>
```
> ⚠️ 务必加 `?v=N` 版本号，每次修改 hermes-table.js 后递增，强制浏览器刷新缓存。

### Step 2: 改造 HTML 容器
v2.0 无需预先创建 `<table>` —— HermesTable.render() 自动生成完整卡片+表格+分页。

```html
<div id="xxxTable"></div>
```

**必须移除原有的外部搜索/Filters控件**。HermesTable v2.0 自带全局搜索 + 每列独立筛选输入框 + 分页。外部 oninput 回调会覆盖 DOM。

### Step 3: 改造 JS 渲染函数
将原来的手动 innerHTML 拼接替换为：
```javascript
HermesTable.render({
  container: '#xxxTable',        // 容器选择器
  data: dataArray,               // 数据数组
  pageSize: 20,                  // 每页行数
  id: 'ht-xxx',                  // 唯一ID（避免多表格冲突）
  dataSource: '/数据/xxx.json',  // 可选，底层数据JSON路径
  labels: {
    search: '搜索...',           // 搜索框占位符
    rawData: '原始数据',         // 索引按钮文字
    noData: '无匹配数据',
    rows: '行', page: '页', of: '/', exportCsv: '导出CSV'
  },
  columns: [
    {key: 'col1', label: '列名', sortable: true},
    {key: 'col2', label: '数值列', sortable: true, type: 'number'},
    {key: 'col3', label: '格式化列', sortable: true, format: function(v, row) {
      return '<span style="color:red">' + v + '</span>';
    }}
  ]
});
```

### Step 4: 核心参数说明

| 参数 | 类型 | 说明 |
|------|------|------|
| `container` | string/Element | CSS选择器或DOM元素 |
| `data` | Array | 对象数组，每个对象的key对应列定义中的key |
| `columns` | Array | 列定义：`{key, label, sortable, type:'number', format:fn}` |
| `columns[].type` | 'number' | 标记为数字列（右对齐+数字排序） |
| `columns[].format` | fn(value, row) | 格式化函数，**返回的HTML不会被转义**（v1.1+），可直接返回 `<span>`、`<a>` 等标签 |
| `dataSource` | string | 可选，底层JSON路径，点击"📋 原始数据"时弹窗显示 |
| `pageSize` | number | 默认20 |
| `searchable` | boolean | 是否显示搜索框，默认true |
| `id` | string | 多表格时必须不同 |

### Step 5: 功能特性

- **排序**: 点击列头切換 升序 → 降序 → 取消。支持数值列的数字排序
- **筛选**: 全局搜索匹配所有列的值（大小写不敏感）。列头下方有列级筛选输入框
- **分页**: 自动分页，页码控件显示
- **导出**: 自动生成"📥 导出CSV"按钮，导出当前筛选结果
- **重置**: "⟲ 重置"按钮清空所有筛选和排序
- **数据索引**: "📋 原始数据"按钮弹窗显示底层JSON（支持fetch远程或当前数据）
- **空状态**: 无数据时显示"无匹配数据"

## 常见故障与排查（CRITICAL）

### 故障1: 页面数据显示为纯文本乱码（标签/颜色不渲染）
**根因**: `hermes-table.js` 的 `esc()` 对 `format()` 返回的HTML进行了转义，`<span>` 变成 `&lt;span&gt;`
**验证**: 在 `hermes-table.js` 中搜索 `html ? String(val) : esc(String(val))`，确认存在此逻辑。旧版只有 `esc(String(val))`
**修复**: 更新 `hermes-table.js` 到 v1.1+，或手动修改渲染行的 `esc()` 调用，对 format 输出跳过转义

### 故障2: 页面加载但数据不显示（KPI卡片有数字，表格空白）
**根因**: JSON 中缺少某些字段（如 `monthly`、`salespeople`、`customers`），导致 `if(!D.monthly)return;` 提前退出
**排查**: 用 `curl http://localhost:8765/数据/xxx.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(list(d.keys()))"` 检查 JSON 的顶层 keys
**修复**: 将所有 `if(!D.field)return;` 改为 `var field = D.field||[];`，用空数组兜底

### 故障3: 改造后页面"显示异常"（所有子页面都乱）
**根因组合**（按频率排序）:
1. 浏览器缓存了旧版 `hermes-table.js` → 脚本加 `?v=N` 破缓存，用户 Shift+Cmd+R 强制刷新
2. 外部搜索/筛选控件 oninput 回调覆盖了 HermesTable DOM → 移除所有外部控件
3. `javascript:` URL 中引号嵌套错误（双引号内套双引号）→ 改用单引号包裹 JS 参数
4. CSS 变量拼写错误（如 `#5f63685f6368`）→ 检查 `--muted` 等变量值

### 故障4: 链接返回404
**根因**: HTML 中 href 路径与实际文件路径不匹配
**排查**: `ls -la` 确认文件实际路径，检查目录层级（如 `/仪表盘/` vs `/工具/仪表盘/`）
**修复**: 修正 `href` 属性，确保与文件系统路径一致

### 故障10: HermesTable 渲染空白 — 容器是 `<table>` 而不是 `<div>`
**根因**: HermesTable v2.0+ 会创建 `<div class="ht-card">` 包裹整个组件。若容器是 `<table id="xxx">`，浏览器HTML解析器会将 `<div>` 弹出 `<table>` 结构外，导致所有内容丢失。
**症状**: KPI卡片正常，表格区域完全空白，无报错。排查一天才发现。
**修复**: 所有 HermesTable 容器必须用 `<div>`：
```html
<!-- ❌ -->
<div class="card"><table id="ordTable"></table></div>
<!-- ✅ -->
<div class="card"><div id="ordTable"></div></div>
```
**注意**: KPI表/原生innerHTML的 `<table>` 不受影响（它们不经过HermesTable）。
**排查**: `grep 'id=".*Table"' page.html` — 确保HermesTable容器是 `<div>`。
**教训**: 2026-07-09 销售单明细空白一整晚就是这个原因——replace_all把`<table id=`改成`<div id=`时要同步改`</table>`为`</div>`。

### 故障6: 数据表格显示"无匹配数据"（销售员/客户维度为空）
**根因**: JSON 中缺少聚合维度（如 `salespeople`、`customers`），而原始明细数据 (`raw`) 存在
**修复**: 在 JS 中从 `raw` 动态计算聚合，而非依赖 JSON 预聚合。
```javascript
function aggregateFromRaw(){
  var raw=D.raw||[], map={};
  raw.forEach(function(r){
    var key=r.salesperson||'未知';
    if(!map[key])map[key]={name:key,total:0,positive:0,negative:0,prods:{}};
    map[key].total++;
    if(r.sentiment==='positive')map[key].positive++;
    else if(r.sentiment==='negative')map[key].negative++;
    // count top products...
  });
  D.salespeople=Object.values(map).map(function(s){...});
}
```
> ⚠️ 注意：sentiment 可能有多于2种值（positive/negative/neutral/mild），需全部处理，并在表格中增加"其他"列展示 non-polarized 部分。

### 故障7: 正反馈% + 负反馈% ≠ 100%
**根因**: 数据中存在 neutral（中性）和 mild（轻度）情绪，计数时被忽略但 total 中包含
**修复**: 
- 列名从"正反馈%"改为"正面"，同时显示百分比+绝对数：`16% (325)`
- 增加"其他"列显示 neutral+mild 合计
- 在 aggregateFromRaw 中统计所有4种情绪值

| 错误做法 | 正确做法 |
|----------|----------|
| `if(s==='positive')p++; if(s==='negative')n++;` | 完整统计 positive/negative/neutral/mild |
| 列名"正反馈%" | "正面" + 括号内显示绝对数 |
| 无"其他"列 | 加上 `other = total - positive - negative` |
**排查**: 在 fetch 链中加 `console.log` 和 `.catch(function(e){console.error(e);})` 
**修复**: 
- 确认 JSON 文件名拼写正确（`test_feedback.json` vs `test_feedback_full.json`）
- 确认 JSON 字段名与代码中使用的匹配（`total_products_rated` vs `total_products`）
- 为所有可能缺失的字段加 `||` 兜底

### 故障排查速查表

| 症状 | 第一检查点 | 命令/方法 |
|------|-----------|----------|
| 页面全白 | JS语法错误 | `curl` 页面 → 检查 `</script>` 配对 |
| 数据不显示 | JSON字段缺失 | `curl JSON | python3 -c "print(list(json.load(sys.stdin).keys()))"` |
| 颜色/标签不渲染 | hermes-table.js 版本 | `grep 'html ? String' /工具/hermes-table.js` |
| 点击链接无效 | URL引号嵌套 | 检查 `href="javascript:fn("arg")"` → 应为单引号 |
| 页面有旧数据 | 浏览器缓存 | Shift+Cmd+R 强制刷新 |
| 子页面404 | 文件路径 | `ls -la` 确认实际路径 |

## 注意事项

1. **容器ID唯一性**: 每个表格的 `id` 参数必须不同
2. **数据格式**: `data` 数组每个对象必须有与 `columns[].key` 对应的属性
3. **HTML安全 (v1.1+)**: `format()` 返回的HTML**不会被转义**，可直接返回 `<span>`、`<a>` 等标签
4. **format()引号**: JS参数用**单引号**，避免与HTML双引号冲突
5. **JSON字段兜底**: 数组加 `||[]`，数值加 `||0`，字符串加 `||''`
6. **缓存管理**: 修改后递增 `?v=N`，用户 Shift+Cmd+R
7. **外部控件冲突**: 移除页面原有搜索/筛选/排序DOM
8. **CSV兼容**: BOM头（Excel友好），导出当前筛选结果
9. **v2.0容器**: 传空 `<div>` 即可，组件自建卡片+表格+分页
10. **v2.0徽章**: `.ht-badge-ok`(绿) / `.ht-badge-warn`(橙) / `.ht-badge-bad`(红) / `.ht-badge-info`(蓝)
11. **v2.0加载态**: 自动显示旋转spinner，数据就绪后淡出

## HermesTable v2.2 聚合模式（2026-07-09）
- v2.2修复：聚合时自动切换列定义 → 期间 | 笔数 | sumKeys列
- `renderCols` 动态替换，聚合行用专用列，非聚合行用原始列
- `sumKeys` 空数组时只显示期间+笔数（无数值聚合）
- 聚合行class `.ht-aggr-row` 自动加灰底+粗体
- **dateKey**: 设置后出现日期范围筛选 + 日/周/月/季/年按钮
- **period**: 默认'day'，切换后自动分组聚合
- **sumKeys**: 聚合时求和的列名，如 `['amount','unit_price']`

### v2.2 聚合模式Pitfall
聚合时 tbody 渲染用聚合列（期间/笔数/sumKeys），但表头不变（仍显示明细列名）。
切换回「日」时恢复明细列+原始表头。
设计原因：避免 `rebuild()` 重渲染整个表头导致排序状态丢失。

### 外部控件移除规则（v2.2+）
HermesTable v2.2 自带了：搜索、每列筛选、日期范围、周期折叠、排序、分页、导出。
**所有外部工具栏控件必须移除**——包括但不限于：pivot选择器、部门下拉、搜索框、周期按钮、筛选输入框。
这些控件的 `oninput`/`onchange` 回调会覆盖 HermesTable 的 DOM，导致渲染错误。
页面只需要：KPI卡片 + info-bar + `<div id="xxxTable"></div>`。

## 铁律（用户明确要求，2026-07-09）
1. **不得估算**：所有数据必须真实。未经验证的数字不得写入页面。不确定就说不确定。
2. **输出前质检**：页面改动后必须 `node --check` 验证JS语法，必须确认JSON字段存在，必须确认数据格式匹配。
3. **功能完成才算完成**：用户说"要排序+筛选+周期折叠"，加了 dateKey 但没加 sumKeys/聚合列渲染 = 偷工减料 = 用户骂"什么原因？老老实实做好我的需求"。
4. **改完立即验证**：不要输出"刷新试试"然后等用户反馈。改完先用 `curl` 测API、用 `node --check` 验JS、确认每个 `render()` 调用的参数齐全。

## ⚠️ 铁律（用户明确要求）
1. **不得估算**：所有数据必须真实。未经验证的数字不得写入页面。不确定就是不确定。
2. **输出前质检**：页面改动后必须 `node --check` 验证JS语法，必须 `curl` 确认JSON字段存在，必须确认数据格式匹配。
3. **功能完成才算完成**：用户提出排序+筛选+周期折叠，只加 dateKey 不补 sumKeys/聚合列渲染是偷工减料。每个 `render()` 调用必须覆盖所有声明的功能。

## HermesTable v2.2 参数速查
| 参数 | 必需 | 说明 |
|------|:--:|------|
| `container` | ✅ | CSS选择器 |
| `columns` | ✅ | `[{key,label,sortable,type,format}]` |
| `data` | ✅ | 数据数组 |
| `id` | ✅ | 唯一ID |
| `dateKey` | — | 设置后出现日期筛选+周期按钮 |
| `sumKeys` | — | 聚合时求和的列名数组 |
| `period` | — | 默认'day' |
| `dateFrom/dateTo` | — | 默认日期范围 |
| `pageSize` | — | 默认20 |
| `dataSource` | — | 原始JSON路径 |

## 已改造页面

| 页面 | 表格 | 版本 |
|------|------|:--:|
| 宝锐统一工作平台 — 📋 活动明细 | 日期/部门/客户/销售员/类型/摘要 + 日期筛选+周期折叠 | v2.2 |
| 宝锐统一工作平台 — 📦 销售明细 | 日期/单号/客户/货号/单价/价税合计 + KPI目标+达成率 + 日期筛选+周期折叠 | v2.2 |
| 宝锐统一工作平台 — 🧪 测试明细 | 日期/客户/产品/数量/金额/销售员 + 日期筛选+周期折叠 | v2.2 |
| 宝锐统一工作平台 — 💬 客诉跟进 | 天数/客诉/等级 + 徽章 | v2.0 |
| 宝锐统一工作平台 — 🚦 审批阻塞 | 等级/天数/工作项/节点 + 徽章 | v2.0 |
| 宝锐测试反馈中心 | 6个表格 | v1.1 |
| 宝锐客户综合管理工作台 | 5个表格 | v1.1 |

## 数据管道模式

| 文件 | 说明 |
|------|------|
| `references/mcp-feishu-pipeline.md` | MCP→飞书项目管道（拉取/客户解析/Prefect/缓存） |

| `references/mcp-feishu-pipeline.md` | MCP→飞书项目数据管道模式（销售/活动/客诉拉取、客户名解析、Prefect管道） |

## 数据增强模式（Data Enrichment）

### 模式1: 多数据源并行加载
当页面需要多个JSON（主数据+采购转化+月度统计等），用 `Promise.all`：
```javascript
Promise.all([
  fetch('/数据/main.json?v=2').then(function(r){return r.json();}),
  fetch('/数据/extra.json?v=2').then(function(r){return r.json();}).catch(function(){return null;})
]).then(function(results){
  D=results[0]; DExtra=results[1];
  if(DExtra) enrichData();  // 辅助数据加载失败不阻塞主流程
  initAll();
});
```
- 辅助数据用 `.catch(function(){return null;})` 兜底，不阻塞主流程
- 每个JSON加 `?v=N` 破缓存

### 模式2: 跨数据集交叉引用
当两个数据集使用不同的编码体系（如测试反馈用短码 "AS412-9"，ERP用层级码 "2.04.02.0496"），通过**名称模糊匹配**建立映射：
```python
# Python端生成映射表
def norm(s):
    s = s.lower().strip()
    s = re.sub(r'[（(][^)）]*[)）]', '', s)  # 去括号内容
    s = re.sub(r'[\s\-_/]+', '', s)          # 去空格/横线
    return s
```
> 匹配率通常80-90%，未匹配产品需人工核验。

### 模式3: 客户端动态聚合（aggregateFromRaw）
当JSON缺少聚合维度（如 `salespeople`、`customers`），在浏览器端从 `raw` 记录动态计算：
```javascript
function aggregateFromRaw(){
  var raw=D.raw||[], spMap={};
  raw.forEach(function(r){
    var key=r.salesperson||'未知';
    if(!spMap[key])spMap[key]={name:key,total:0,positive:0,negative:0,neutral:0,mild:0,prods:{}};
    spMap[key].total++;
    var s=r.sentiment||'neutral';
    if(s==='positive')spMap[key].positive++;
    else if(s==='negative')spMap[key].negative++;
    else if(s==='mild')spMap[key].mild++;
    else spMap[key].neutral++;
    // ... top products
  });
  D.salespeople=Object.values(spMap).map(function(s){
    s.pos_pct=Math.round(s.positive/s.total*100);
    s.neg_pct=Math.round(s.negative/s.total*100);
    s.other=s.total-s.positive-s.negative;
    return s;
  });
}
```

### 模式4: 采购数据→正反馈加权
将ERP采购记录作为正向信号融入产品评级：
```javascript
// 加载采购转化数据后
prodData.forEach(function(p){
  if(p._purchCount>0){
    var adjPos=(p.positive||0)+p._purchCount;  // 采购=额外正向
    p.pos_pct=Math.round(adjPos/p.total*100);
    // 重算评级
    if(p.total>=10&&p.pos_pct>=60){p.rating='green';p.is_star=true;}
  }
});
```
- 表格增加"曾采购"列显示 `✓ 100%` 或 `-`
- 总览KPI增加"💰 曾采购"卡
- 有采购记录但未达⭐阈值的标记 `💰 采购验证`

### 模式5: 月度趋势数据独立生成
当JSON不含月度统计，从源Excel单独提取为轻量JSON：
- 源文件：飞书活动导出 Excel
- 按"创建时间"列 → `YYYY-MM` 分组计数
- 输出：`/数据/test_feedback_monthly.json`（<1KB）
- 页面用 `Promise.all` 并行加载，注入 `D.monthly`
- 数据密集型表格优先（仪表盘、价格查询）
- CRUD操作型表格可保留原样（订单列表含选中/发货等交互）
## IMA风格工作台设计模式（2026-07-10）

宝锐工作台已全面改为IMA参考设计语言，不再使用HermesTable组件。

### 视觉标准
- **顶栏**: 暗色渐变 `linear-gradient(135deg,#1a1a2e,#16213e,#0f3460)` + 白色文字, 56px高
- **角色切换**: `rgba(255,255,255,.1)` 背景按钮组，`.active` → `#e94560` 玫红
- **统计卡片**: `.stat-card` 白底圆角 + 左侧4px彩色边框
- **维度面板**: `.dim-panel` 白底 + `.dim-header` 暗色（#1a1a2e）
- **徽章**: `bg-green`/`bg-red`/`bg-yellow`/`bg-blue` 四色
- **页面背景**: `#f0f2f5`

### 架构原则
- 单DATA对象 + Promise.all一次性加载
- 角色分权：ROLE='ceo'|'dir'|'sales'
- 纯innerHTML，零外部依赖
- 数据源优先手动Excel（准确），MCP API补充
- 每个面板底部tooltip-info引用管理规程

### Excel管道
```python
# 跳过标题行，过滤NaN，按列名索引
df = pd.read_excel(path, header=None, skiprows=2)
# 关键列: [0]单据编号 [2]价税合计 [6]合同日期 [3]客户 [21]货号 [30]含税单价
```