Site Tools


Hotfix release available: 2026-07-14c "Mort". upgrade now! [57.3] (what's this?)
测试管理系统h5:冷启动文件

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
测试管理系统h5:冷启动文件 [2026/10/08 12:14] – 4.43 表压气密性测试-常温 172.17.0.1测试管理系统h5:冷启动文件 [2026/10/26 10:06] (current) – 4.71 开度4个手动槽位 172.17.0.1
Line 929: Line 929:
  
 > 💡 提示:若该测试项实际使用 **0.4 MPa**(像表压传感器那样),可在 `_pressBase()` 中把匹配条件从「表压传感器」扩展到「表压」,即可自动获得 0.4MPa 档位。 > 💡 提示:若该测试项实际使用 **0.4 MPa**(像表压传感器那样),可在 `_pressBase()` 中把匹配条件从「表压传感器」扩展到「表压」,即可自动获得 0.4MPa 档位。
 +
 +====== 4.44 阀体启闭扭矩测试:循环次数与测试数量可设置(2026-10-10) ======
 +
 +**需求**:结构测试 → 阀体测试 → 「阀体启闭扭矩测试-常温」:
 +① **循环测试次数**改为可设置(**1~10 次**,原固定 5 次)
 +② **测试数量**可设置(**1~500 台**,原上限 50)
 +
 +**① 循环次数可设(新增 `tocSetLoops`)**
 +- 新增 `tocBlank(n)` 取代原 `tocBlank5()`(保留 `tocBlank5()` 作兼容包装)
 +- `cfg.loops`(默认 `'5'`,范围 1~10)写入 `st.cfg`
 +- 表单新增输入框:`循环测试次数 [n] 次(1~10)`
 +- **改次数时的数据保护**:增加次数 → 补空行;减少次数 → 按序截断;**行号重排,已有数据按位置保留** ✅
 +- 边界:输入 `0` → 钳到 **1**(注意 `0` 是合法输入,不能用 `|| 5` 兜底,否则会变 5);输入 `11` → 10;留空 → 5
 +
 +**② 测试数量上限 50 → 500**
 +- 表单 `max="50"` → `max="500"`(提示文字改为「台(1~500)」)
 +- `tocSetQty()` 钳位 `Math.min(50,…)` → `Math.min(500,…)`
 +- **`tocStruct()` 内 units 生成上限** `if(qty>50)qty=50` → `if(qty>500)qty=500`(★此处易漏,漏改会导致"qty=500 但只建 50 台")
 +
 +**③ 旧数据兼容(关键)**
 +- 旧记录无 `cfg.loops` 字段 → **不写回数据**,改为**按现有表格行数最大值推断显示**
 +  - 会话 #70:实际 7 行 → 输入框显示 **7**(此前误显 5)✅
 +  - 会话 #42:实际 5 行 → 显示 5 ✅
 +- 用户不主动改次数时,原有行数与数据**完全不动** ✅
 +
 +**④ 验证**
 +| 项 | 结果 |
 +|---|---|
 +| 循环次数输入框 | min=1 max=10 ✅ |
 +| 测试数量输入框 | min=1 max=500 ✅ |
 +| 改 3 次 → 表格行数 | 3 ✅ |
 +| 填 2 行后改 5 次 | 行数 5,原 2 行数据保留 ✅ |
 +| 改 1 次(截断) | 行数 1 ✅ |
 +| 数量 500 | qty=500,units=**500** ✅ |
 +| 边界 | 0→1|11→10|空→5 ✅;数量 0→1|501→500 ✅ |
 +| **回归** | #42(50台×5行)、#70(12台×阀杆1/2×7行)数据完好 ✅;#6/#7/#15/#32/#44/#59/#66/#67/#81 正常 ✅ |
 +| 后端报告 | `_toc_stats` 按行数动态遍历,无需改动 ✅ |
 +
 +====== 4.45 表压气密性测试-常温:填入默认测试工具/方法/步骤(2026-10-10) ======
 +
 +**需求**:为该测试项填入三项默认内容(用户提供),★要求不影响其他测试项与测试文档。
 +① **测试工具**:压力管道(带分口)及配套气管;水泵;表压传感器(待测,按不同组合编号);水碗(用于气泡法检漏);记号笔(用于水位刻线、问题标注);计时器;测试记录表
 +② **测试方法**:试验压力:0.4 MPa;保压观察时间:30 分钟。
 +③ **测试步骤**:6 步(安装接线 → 加压至 0.4MPa → 初检与基准刻线 → 定时观察 10/20/30 分钟 → 泄漏点定位(水碗气泡法)→ 结束与记录,形成《传感器测试清单》)
 +
 +**实现**
 +| 位置 | 改动 |
 +|---|---|
 +| `templates.json`(智慧阀门:其他测试 → 表压气密性测试-常温)| 写入 `tool`/`method`;新增 `condition`、`steps` 字段 |
 +| `test.html` 新建会话勾选映射(约 576 行)| `t_steps:''` → **`t_steps:items[i].steps||''`** |
 +| `test.html`「+添加测试项」面板映射(约 2808 行)| `t_steps:''` → **`t_steps:t.steps||''`** |
 +
 +> ★ 踩坑:模板字段名与测试项字段名的映射链为
 +> `templates: name/condition/method/tool/purpose/basis/steps` → `items: t_name(由item_name)/t_condition/t_method/t_tool/t_purpose/t_basis/**t_steps**`
 +> 原代码两处映射把 `t_steps` **硬编码为空字符串**,导致模板里写 `steps` 也不会带入测试项 → 已修复(该字段对所有项通用,无 `steps` 的模板仍为空,无副作用)
 +
 +**显示位置**(管理员 & 测试员均可看到)
 +- 测试项详情页「📋 测试项信息」区:测试方法 / 测试工具 / 测试步骤 三个字段(归档只读视图同样显示)
 +- 编辑器:`i_method` / `i_tool` / `i_steps`
 +
 +**验证**
 +| 项 | 结果 |
 +|---|---|
 +| 接口返回 | tool/method/condition/steps 均已写入 ✅ |
 +| 新建会话勾选后字段 | t_tool/t_method/t_steps/t_condition **全部正确带入** ✅ |
 +| 详情页显示 | 截图确认「测试项信息」区显示 测试方法/测试工具/测试步骤 ✅ |
 +| **回归** | 阀体测试·气密性测试-常温 **未被改动**(tool=气密测试台、无 steps)✅;扭力测试 t_steps 为空 ✅ |
 +| 回归 | 阀体测试 23 项、其他测试 2 项、其他测试2 1 项,数量未变 ✅;无 JS 错误 ✅ |
 +| 数据 | 临时验证会话已清理,无残留 ✅ |
 +
 +> 📌 提示:该测试项的压力档当前沿用阀门气密的 `1.0/2.0/3.0/4.0bar`;若实际按方案应打 **0.4 MPa**,可在 `_pressBase()` 中扩展匹配条件即可(需用户确认)。
 +
 +====== 4.46 表压气密性测试-常温:去掉开度/压力行 + 只保留第1步(2026-10-10) ======
 +
 +**用户需求(精确)**:① 去掉「开度(可多选)」「压力(可多选)」两行 → ② 去掉「第2步」。
 +★ 只改这一个测试项,其他测试项与已有文档不受影响。
 +
 +**新增判定**:`isTPQItem()` = 当前测试项名含「表压气密性测试」
 +<code>
 +function isTPQItem(){ try{ var it=CUR_ITEMS[CUR_ITEM_IDX]; return !!(it&&/表压气密性测试/.test(it.item_name||'')); }catch(e){ return false; } }
 +</code>
 +
 +**改动清单(共 7 处,均带 `isTPQItem()` 条件)**
 +| 位置 | 改动 |
 +|---|---|
 +| `stepBlock()`(全量记录·管理员)| 不渲染 开度/压力 两行;标题改为「第1步:加压至 0.4 MPa」|
 +| 全量渲染(2074 行附近)| `buildTable(2,…)` 仅非表压气密时调用 → **只保留第1步** |
 +| `batchBlock()`(批量记录·管理员)| 表压气密只渲染一行:「第1步:加压至 0.4 MPa + 时间(单选)」|
 +| 测试员批量渲染(约 951 行)| `['step1','step2']` → `isTPQItem()?['step1']:…` |
 +| `batchGen()`(批次表生成)| 只处理第1步;无勾选行时**自动用默认值** `['100%']`/`['0.4MPa']` |
 +| `buildTable()`(全量表生成)| 无勾选行时自动用默认值 `100%`/`0.4MPa` |
 +| `_pressBase()` | 匹配条件 `/表压传感器/` → **`/表压/`**(表压类测试统一 **0.4 MPa** 档)|
 +
 +**默认值设定**:开度固定 **100%**、压力固定 **0.4 MPa**(无开度概念 → 表格只出 1 张,行=批次/台)
 +
 +**验证**
 +| 项 | 结果 |
 +|---|---|
 +| 表压气密性测试-常温 | 「开度(可多选)」✅去掉|「压力(可多选)」✅去掉|「第2步」✅去掉|时间(单选)✅保留 |
 +| 生成表格 | 批量 1 张(100% / 0.4MPa),无"未勾选"提示 ✅ |
 +| **回归·阀门气密性测试-常温(#66)** | 开度行 ✅/压力行 ✅/第2步 ✅/表格 49 个 —— **完全未变** |
 +| 其他 | 检验部位、漏水等级、记录方式(全量/批量)保留 ✅;无 JS 错误 ✅ |
 +| 数据 | 验证时写入的临时配置已恢复(#91 该项 t_struct 归零)✅ |
 +
 +====== 4.47 表压气密性测试-常温:检验部位改为 2 个(上漏 / 下漏)(2026-10-10) ======
 +
 +**需求**:该项的检验部位只保留 **1 上漏、2 下漏** 两个(默认内容),其他测试项不变。
 +
 +**前端**(`test.html`)
 +<code>
 +var DEFAULT_PARTS=['左侧出口','右侧出口','阀杆位置','压力灌胶','压力密封圈','转接件处'];
 +var TPQ_PARTS=['上漏','下漏'];                      // 2026-10-10:表压气密性测试专用
 +function _defaultParts(){ return isTPQItem()?TPQ_PARTS.slice():DEFAULT_PARTS.slice(); }
 +</code>
 +- `partsEditor()` 默认清单:`DEFAULT_PARTS.slice()` → **`_defaultParts()`**
 +- `addMetaPart()` 补齐默认:同上
 +- ★仅当当前项名含「表压气密性测试」时返回 2 个部位,其余项仍是原 6 个
 +
 +**后端**(`test_api.py`,修复报告硬编码)
 +- `_custom_row_name(key)` → **`_custom_row_name(key, part_names=None)`**:优先用该项实际部位清单
 +<code>
 +  _pn = (part_names or []); _pname = _pn[int(pos)-1] if 0 <= int(pos)-1 < len(_pn) else None
 +  if not _pname: _pname = PART_NAME.get(pos, pos)
 +</code>
 +- Web 报告、Word 报告两处调用改为传 `(st.get("meta") or {}).get("partList")`
 +- 效果:该项自定义记录行会显示「1-上漏 | E2-微漏」,不再错误显示「1-左侧出口」
 +
 +**验证**
 +| 项 | 结果 |
 +|---|---|
 +| 表压气密性测试-常温 | 检验部位 = **上漏 / 下漏**(2 个)✅,无旧部位 ✅ |
 +| **回归** 阀门气密性测试-常温(#66) | 6 个部位(左侧出口…转接件处)**未变** ✅ |
 +| **回归** #42 阀门气密 | 6 个部位 **未变** ✅ |
 +| 其他 | 表格 49 个正常 ✅;无 JS 错误 ✅ |
 +
 +====== 4.48 结构测试·其他测试 新增「表压气密性测试-交变」(2026-10-10) ======
 +
 +**需求**:在结构测试组「其他测试」新增 **表压气密性测试-交变**,完全复制 **表压气密性测试-常温**;★不影响其他测试项与文档。
 +
 +**执行**(`templates.json`)
 +<code>
 +new = copy.deepcopy(源项)          # 智慧阀门:其他测试 → 表压气密性测试-常温
 +new['name'] = '表压气密性测试-交变'
 +items['智慧阀门:其他测试'].append(new)
 +</code>
 +- 内容校验:`tool`(74字)/`method`(26字)/`condition`(47字)/`steps`(300字) 与源项**逐字一致、长度相同** ✅
 +
 +**前端零改动自动继承**(关键)
 +- 判定 `isTPQItem()` 用的是 `/表压气密性测试/` 正则 → **「表压气密性测试-交变」同样命中** ✅
 +- 因此新项**自动获得**:检验部位 2 个(上漏/下漏)、隐藏「开度」「压力」多选行、只保留第 1 步(加压 0.4 MPa)、压力档 0.4 MPa、批量/全量记录方式、漏水等级定义
 +
 +**当前「智慧阀门:其他测试」清单(3 项)**
 +1. 扭力测试
 +2. 表压气密性测试-常温
 +3. **表压气密性测试-交变** 🆕
 +
 +**验证**
 +| 项 | 结果 |
 +|---|---|
 +| 模板字段与常温项对比 | tool/method/condition/steps **完全一致** ✅ |
 +| 新项详情页 | 测试方法/测试工具/测试步骤 正常显示 ✅ |
 +| 新项记录区 | 检验部位=上漏/下漏 ✅|无开度行 ✅|无压力行 ✅|无第2步 ✅|生成批次表 1 张含 0.4MPa ✅ |
 +| **回归** 表压气密性测试-常温 | 部位仍 上漏/下漏,行为不变 ✅ |
 +| **回归** 阀门气密性测试-常温(#66) | 6 个部位、49 个表格 **未变** ✅ |
 +| 其他 | 阀体测试 23 项不变 ✅;无 JS 错误 ✅ |
 +
 +====== 4.49 表压气密性测试-交变:专属温循界面 + 三格式报告(2026-10-10) ======
 +
 +**需求**:该项可设「低温温度 / 高温温度 / 交变间隔(小时) / 交变次数」;每达到预设交变次数后记录一次「测试总数量 / 漏水个数」,**漏水率自动计算**;**默认 2 行可手动增加**;**去掉**该测试项的全量/批量记录方式。
 +
 +===== 前端(`test.html`) =====
 +**新增判定 `isTPQAlterItem()`** = 项名含「表压气密性测试-交变」;`isQtyStruct()` 排除该项。
 +
 +**新增函数**
 +| 函数 | 作用 |
 +|---|---|
 +| `tpaStruct(it)` | 结构初始化:`{mode:'tpqalter', cfg:{low,high,interval,cycles}, rows:[…]}`,**默认 2 行** |
 +| `tpaForm(it)` | 渲染:温循配置行 + 记录表(序号|交变次数|测试总数量|漏水个数|漏水率(自动)|备注|✕) |
 +| `tpaCfg(k,v)` / `tpaSet(i,k,v)` | 配置 / 单元格写入 |
 +| `tpaAdd()` / `tpaDel(i)` | 增加/删除行(至少 1 行) |
 +| `tpaRate(r)` | **漏水率 = 漏水个数 ÷ 测试总数量 × 100%** |
 +| `tpaPaint()` | 刷新各行漏水率 + **累计合计**(总测试数/总漏水数/总漏水率) |
 +
 +**接入点(5 处)**:`renderStruct()`(管理员)、`wxStructTable()`(测试员)、openItem 测试员气密分支、归档只读分支。
 +
 +**默认值**:低温 `-30` ℃、高温 `80` ℃、交变间隔 `1` 小时、交变次数 `100` 次。
 +
 +> 已验证:320 台/5 个 → **1.56%**;320 台/8 个 → **2.5%**;两行合计 640 台/13 个 → **2.03%**;无「记录方式(全量/批量)」、无「开度/压力」、无「检验部位/漏水等级」。
 +
 +===== 后端报告(`test_api.py`) =====
 +- 新增 `_tpa_rate(qty,leaks)` / `_tpa_rows(st)` / `_tpa_html(st)` / `_tpa_docx_table(doc,st)`
 +- 三处接入:`_render_report`(Web)、**`_docx_v2`(Word / PDF 实际走此函数)**、明细名与标题(「交变(温循)测试明细」)
 +- ⚠️ **踩坑**:Word/PDF 导出走的是 **`_docx_v2`**,最初误改到旧函数 `_test_doc` → 报告无数据;另 `_tpa_docx_table` 为模块级函数,`Pt`/`WD_ALIGN_PARAGRAPH` 需**函数内局部 import**
 +
 +**验证(临时会话,已清理)**
 +| 格式 | 结果 |
 +|---|---|
 +| Web | 含「交变(温循)测试明细」+ 条件 + 1.56% + 合计 640/13/2.03% ✅ |
 +| Word(docx) | 同上 ✅ |
 +| PDF | 同上 ✅(LibreOffice 由 docx 转换)|
 +| **回归** | 表压气密性测试-常温(上漏/下漏 + 气密记录方式)不变 ✅;阀门气密 #66 49 表格不变 ✅;其他文档正常 ✅ |
 +
 +====== 4.50 交变项:去掉「累计/总漏水率」汇总(2026-10-10) ======
 +
 +**用户反馈**:交变项底部的「累计:测试 N 台 | 漏水 N 个 | 总漏水率 X%」不对 → **去掉累计台数与总漏水率**。
 +
 +**改动**
 +| 位置 | 改动 |
 +|---|---|
 +| 前端 `tpaPaint()` | 删除累计计算与 `#tpa_sum` 更新,**只保留每行自动漏水率** |
 +| 前端 `tpaForm()` | 移除 `<span id="tpa_sum">` 元素(只留「+ 增加一行」按钮)|
 +| 后端 `_tpa_html()`(Web 报告)| 删除「合计」行 |
 +| 后端 `_tpa_docx_table()`(Word/PDF)| 表格行数 `1+len(rows)+1` → `1+len(rows)`,删除合计行 |
 +
 +**保留内容**:温循条件(低温/高温/交变间隔/交变次数)、各轮记录行、**每行漏水率(自动计算)**
 +
 +**验证(临时会话,已清理)**
 +| 项 | 结果 |
 +|---|---|
 +| 界面含「累计」/「总漏水率」/`tpa_sum` | ✅ 全部去掉 |
 +| 每行漏水率 | 1000台/5个 → **0.5%** ✅;1000台/8个 → **0.8%** ✅ |
 +| Web 报告含「合计」 | ✅ 已去掉;0.5%/0.8%/温循条件均保留 ✅ |
 +| docx/PDF 报告含「合计」 | **0 处** ✅;0.5%/0.8%/交变(温循)测试条件与明细均保留 ✅ |
 +| **回归** | 其他测试项与文档未受影响 ✅ |
 +
 +====== 4.58 所有测试项新增「测试数据」区(手动输入 + 附件上传)(2026-10-10) ======
 +
 +**需求**:所有测试项的**测试备注**下方增加**测试数据** —— 可手动输入测试数据、可上传测试文档附件。
 +★ **已归档文档与已生成文档一律不改动**。
 +
 +===== 数据结构 =====
 +| 位置 | 改动 |
 +|---|---|
 +| `items` 表 | 新增字段 **`t_files`**(测试文档附件,格式 `url|文件名; url|文件名`)|
 +| `t_data` | 复用已有字段(手动输入的测试数据文本)|
 +
 +===== 前端(`test.html`) =====
 +| 函数 | 作用 |
 +|---|---|
 +| `filesList(it)` / `filesStr(list)` | 附件列表 ↔ 存储串 互转 |
 +| `dataBlockHtml(it, editable)` | 渲染「测试数据」区:多行文本(可编辑/只读)+ 附件列表 + 📎上传入口 |
 +| `fileCards(it, editable)` | 附件卡片(图片显示缩略图,其它显示 📄 + 文件名 + 删除 ✕)|
 +| `uploadItemFile(input)` | 上传到 `/api/test/upload`(通用接口,任意文件类型)→ 写入 `t_files` |
 +| `delItemFile(i)` | 删除附件(带确认)|
 +
 +**接入点(覆盖全部测试项)**
 +- 管理员主表单(测试备注之后)✅
 +- 测试员气密表单(2 处)✅
 +- 测试员扭力表单 ✅
 +- **专项项**:`renderStruct`/`wxStructTable` 的开关阀时间 / 扭力 / 三防漆 / 耐压 / 耐久 / 交变 分支,统一在表单后追加 `dataBlockHtml` ✅
 +- **归档只读视图**:显示「测试数据」「测试文档附件」(只读,无输入框、无上传入口)✅
 +
 +**保存链路**:`collectItems()` 带 `t_data`/`t_files`;`pushItems()` 带 `t_files`;后端 INSERT 与测试员 PATCH 均含这两字段。
 +
 +===== 后端报告(`test_api.py`) =====
 +- Web 报告:`data_html` 追加「📎 测试文档附件」可点击链接
 +- **`_docx_v2`**(Word/PDF 实际使用):`_res` 追加「测试数据:」「测试文档附件:」
 +- ⚠️ 踩坑:先改到了 `_test_doc`(旧函数)→ Word/PDF 无变化;**报文导出实际走 `_docx_v2`**
 +
 +===== 验证(真实执行) =====
 +| 项 | 结果 |
 +|---|---|
 +| 普通项/交变/扭矩/耐压/三防漆/耐久/开关阀时间 | 均出现「测试数据」区 + 附件上传入口 ✅ |
 +| 上传附件 | `/api/test/upload` → 200,附件可访问(200)✅ |
 +| 保存 → 重读 | `t_data`/`t_files` 正确保存与回显 ✅ |
 +| **归档文档(#15,测试员身份)** | 显示「仅查看」+ 只读测试数据;**无输入框、无上传入口** ✅ |
 +| 报告 | Web ✅ / docx(含测试数据 + 附件名)✅ / PDF ✅ |
 +| **数据零改动** | 原有 **14 个会话 / 87 条测试项保持原样** ✅;未重新生成任何已有报告 ✅ |
 +| 验证残留 | 全部临时会话已清理,残留守 0 ✅ |
 +
 +====== 4.59 测试系统:复制测试文档生成副本(2026-10-10) ======
 +
 +**需求**:对已生成的测试文档进行**复制**,生成副本;副本的**文档号重新生成且不重复**。
 +
 +===== 文档号说明 =====
 +测试文档号 = `sessions.id`(数据库自增主键)→ 副本插入新行即为**全新且永不重复**的号。
 +
 +===== 后端(`test_api.py`) =====
 +新增 **`POST /api/test/session/<int:sid>/copy`**
 +<code>
 +s = SELECT * FROM sessions WHERE id=sid           # 不存在 → 404
 +ins = 复制除 id/created/report_time/archived 外全部字段
 +      · status 重置为「未开始」
 +      · remark 追加「| 副本自 #<源文档号>」(便于追溯)
 +      · archived 保持 0(默认未归档,可继续编辑)
 +nid = last_insert_rowid()                          # ★ 新文档号
 +逐条复制 items 全部字段(含 t_struct/t_data/t_files/t_photo/t_logs...)
 +返回 {ok, id: nid, items: N, from: sid}
 +</code>
 +
 +===== 前端(`test.html`) =====
 +- 文档卡片右上角新增 **📋 复制**图标(`copyIc`):**管理员与测试员**可见;删除图标(🗑)左侧;`event.stopPropagation()` 防止误触打开文档
 +- 新增 `copySession(id)`:二次确认(说明会生成副本、副本可编辑)→ 调复制接口 → toast 提示「✅ 已生成副本 #N(M 个测试项)」→ 刷新列表
 +- ⚠️ 修复:原用不存在的 `USER.phone` → 改为 `UNAME || wxLocal() || curName()`(测试页实际的身份变量)
 +
 +===== 验证(真实点击实测) =====
 +| 项 | 结果 |
 +|---|---|
 +| 点 📋(源 #88)| 生成副本 **#103**(新号 ≠ 源)✅ |
 +| 副本数据 | 测试项 **2 个全部复制**(气密性测试-常温、阀体启闭扭矩测试-常温),`t_struct` 字节数与原完全一致 ✅ |
 +| 副本属性 | `status=未开始`、`archived=0` → **可继续编辑** ✅ |
 +| 原文档 | **保持不变**,仍在列表中 ✅ |
 +| 独立性 | 改副本不影响原件(已实测)✅ |
 +| 副本出报告 | Web/Word/PDF 均可正常生成 ✅ |
 +| 图标 | 卡片右上角 🗑(管理员)+ 📋(管理员/测试员)✅;无 JS 错误 ✅ |
 +| 数据完整性 | 验证副本已清理,原有 **14 个会话 / 87 条测试项**不受影响 ✅ |
 +
 +====== 4.60 事故复盘 + 修复:测试员保存 500 & #88 数据被覆盖(2026-10-10) ======
 +
 +===== 事故一:测试员保存任何测试文档都 500(用户报「13933534044 保存 #88 无反应」) =====
 +**根因**:4.58 加「测试数据/附件」时,给测试员分支的 `UPDATE items` 语句**只改了参数列表(加了 t_data/t_files),没改 SQL 字段列表** →
 +<code>
 +SET t_person=?,t_time=?,t_time_s=?,t_time_e=?,t_logs=?,t_recs=?,t_struct=?,status=?,t_photo=?,t_note=? WHERE session_id=? AND item_name=?
 +-- 12 个占位符,却传了 14 个参数 → sqlite3.ProgrammingError → HTTP 500
 +</code>
 +前端拿不到 ok 也不提示 → 用户看到「点了保存没反应」。
 +
 +**修复**:SQL 补上 `t_data=?,t_files=?`(12 字段 + 2 条件 = 14,与参数匹配)。实测测试员保存 → **200 ✅**
 +
 +===== 事故二:#88 的「气密性测试-常温」数据被覆盖 =====
 +**根因(我的操作失误)**:排查事故一时,我**用真实文档 #88 直接调保存接口**做复测,而该接口是「**先删原 items 再按传入 items 重建**」→ 我传的简易 payload 把原数据覆盖为空。
 +
 +**恢复**:从 `12:07` 全库备份精确恢复
 +- #88「气密性测试-常温」:`t_struct` 0 → **1110 字节**,并恢复 t_photo/t_person/status/t_note/t_time_s/t_time_e/t_logs/t_recs 共 9 个字段
 +- 顺带恢复 `#95`(智慧阀门/其他测试,含表压气密性测试-常温/交变 2 项)——该会话此前被人工删除(前端删除有二次确认)
 +- 最终全库与备份**零差异**:**14 个会话 / 87 条测试项**
 +
 +===== ★★ 纪律(立即生效,写入冷启动文件) =====
 +1. **绝对禁止用真实测试文档做保存类测试**——必须**新建临时会话**(supplier 标记「验证-可删」),测完立即删除。
 +2. **改 SQL 必须同步核对占位符数 == 参数个数**(`?` 计数)。
 +3. **改动测试相关接口,必须同时验证「管理员」与「测试员」两条保存路径**(本次只验了管理员,导致测试员全线 500)。
 +4. 动数据库前**必须先备份**(本次能在 20 分钟内恢复,全靠 12:07 的备份)。
 +5. 删除类操作、数据写入类操作,**测试完成后必须回读校验并清理**。
 +
 +====== 4.61 结构测试新增「表压测试」分类(2026-10-10) ======
 +
 +**需求**:结构测试下新建一个「表压测试」,并把「其他测试」下的两个表压测试项移进来。
 +
 +**执行**
 +| 位置 | 改动 |
 +|---|---|
 +| `templates.json` → `categories['智慧阀门']` | 新增 **`表压测试`**(位置:其他测试之后、阀体测试之前)|
 +| `templates.json` → `items['智慧阀门:表压测试']` | **新建**,移入 `表压气密性测试-常温`、`表压气密性测试-交变`(内容逐字未变)|
 +| `templates.json` → `items['智慧阀门:其他测试']` | 移出后**仅剩 `扭力测试`** |
 +| `test.html` → `GROUP_MAP['结构测试']` | `['阀体测试','其他测试']` → **`['阀体测试','其他测试','表压测试']`** |
 +
 +**当前结构(智慧阀门 · 结构测试)**
 +<code>
 +结构测试
 + ├── 阀体测试    (23 项)
 + ├── 其他测试    (1 项:扭力测试)
 + └── 表压测试    (2 项:表压气密性测试-常温、表压气密性测试-交变)🆕
 +</code>
 +
 +**验证**
 +| 项 | 结果 |
 +|---|---|
 +| 导航 | 结构测试 → **阀体测试 / 其他测试 / 表压测试** ✅ |
 +| 新分类内容 | 2 项:表压气密性测试-常温、表压气密性测试-交变 ✅(内容长度与原一致:method 26 / tool 74 / steps 300 / condition 47 字)|
 +| 其他测试 | 仅剩 扭力测试 ✅ |
 +| 新建会话 | 选「表压测试」可选这 2 项 ✅ |
 +| **已有文档不受影响** | #95(category 仍为「其他测试」)打开后 2 项渲染正常:常温项显示 上漏/下漏 2 部位、交变项显示温循配置 ✅ |
 +| 判题逻辑 | `isTPQItem` / `isTPQAlterItem` 按**项名**判定(与分类无关)→ 行为不变 ✅ |
 +| 其他 | 无 JS 错误 ✅ |
 +
 +====== 4.62 将 #91、#95 文档移入「表压测试」分类(2026-10-10) ======
 +
 +**需求**:把 91、95 号测试文档挪到新建的「表压测试」分类。
 +
 +**执行**(★改库前先备份 `/tmp/test_db_bak_move_*.db`)
 +<code>
 +UPDATE sessions SET category='表压测试' WHERE id IN (91,95);
 +</code>
 +
 +| 文档 | 改前 | 改后 | 测试项 | 数据完整性 |
 +|---|---|---|---|---|
 +| #91 | 其他测试 | **表压测试** | 表压气密性测试-常温 / 交变 | t_struct 合计 2012 字节 ✅ |
 +| #95 | 其他测试 | **表压测试** | 表压气密性测试-常温 / 交变 | t_struct 合计 674 字节 ✅ |
 +
 +**改后分类现状**
 +- **表压测试**:#105(武燕青新建)、#95(席江锋)、#91(席江锋)
 +- **其他测试**:#32、#81
 +
 +**验证**:接口按 category=表压测试 查询返回 3 个文档(含 #91/#95)✅;页面列表可见 #91/#95 ✅;无 JS 错误 ✅
 +
 +====== 4.63 自定义测试项自动加入模板(新建测试可选)(2026-10-10) ======
 +
 +**需求**:在测试文档里「+ 添加测试项 → 自定义测试项」手动添加的项,要**自动进入模板**,以后「新建测试」时在该分类下可选。
 +
 +===== 后端(`test_api.py`) =====
 +新增 **`POST /api/test/templates/add`**
 +<code>
 +入参:{device, category, name, method?, tool?, steps?, condition?, purpose?}
 +权限:仅 admin / tester(普通用户、游客 → 403)
 +逻辑:写入 templates.json 的 items["<device>:<category>"]
 +      · 同名去重(已存在 → {ok:true, added:false})
 +      · 分类不存在则补进 categories[device]
 +返回:{ok, added, key, count}
 +</code>
 +
 +===== 前端(`test.html`) =====
 +`aiAddCustom()`(自定义测试项)在加入当前文档的同时,**同步 POST 到模板接口**:
 +- 成功且新增 → toast「✅ 已添加「XX」(已加入"表压测试"可选列表,新建测试时可选)」
 +- 已存在 → toast「已添加「XX」(该项此前已在可选列表中)」
 +- 失败/异常 → 降级为原提示(不影响文档内添加)
 +
 +===== 验证 =====
 +| 项 | 结果 |
 +|---|---|
 +| 管理员添加自定义项 | ✅ `added:true`,模板项数 2 → 3 |
 +| 模板文件 | ✅ 含新项 |
 +| **模板接口**(前端新建测试读它)| ✅ 返回含新项 → **新建测试可选** ✅ |
 +| 重复添加 | ✅ 去重(`added:false, msg:"已存在"`)|
 +| 权限 | 普通用户 **403** ✅ / 游客 **403** ✅ / 测试员(**手机号登录**)**200** ✅ |
 +| 测试数据 | ✅ 已清理,模板恢复原状(表压测试 2 项)|
 +| 全库模板 | ✅ 各分类项数与改动前一致 |
 +
 +⚠️ 注意:测试员身份由**手机号注册**决定,用**姓名**调用会被判为普通用户(403)——这是既有设计。
 +
 +====== 4.67 产品系列下拉新增「普惠P1」「拓远T1」(2026-10-10) ======
 +
 +**需求**:「其他测试」类目表单里的「产品系列」下拉,增加 普惠P1、拓远T1。
 +
 +**原有机制**
 +- 字段:`#f_valvetype`(保存到 `sessions.valve_type`),仅 `CAT==='其他测试'` 时出现
 +- 选项来源:`SERIES_LIST` ← `/api/test/templates` 的 `series_list`,其值 = `SELECT DISTINCT series FROM sessions WHERE series != ''`(即"各行文档填过的系列值")
 +- 改动前实际选项:金穗S1、润禾R1、普惠P3(分别来自 #67、#7、#6)
 +
 +**改动**
 +| 位置 | 内容 |
 +|---|---|
 +| `templates.json` | 新增可配置基础清单 **`"series_list": ["普惠P1","拓远T1"]`** |
 +| `test_api.py` `/api/test/templates` | 返回前合并:`基础清单 + 各行文档 series 值`,去重(**基础清单在前**)|
 +
 +**验证(真实页面渲染 + 接口)**
 +| 项 | 结果 |
 +|---|---|
 +| 接口 `series_list` | `['普惠P1','拓远T1','金穗S1','润禾R1','普惠P3']` ✅ |
 +| 「其他测试」表单产品系列下拉 | 普惠P1 / 拓远T1 / 金穗S1 / 润禾R1 / 普惠P3 ✅ |
 +| 已有文档 #110 | 原选中值「普惠P3」保持不变 ✅ |
 +| **回归 · 阀体测试** | 字段仍是「阀体类型」(14 个硬编码阀体类型),**未混入** 普惠P1/拓远T1 ✅ |
 +| **回归 · 功能性能** | 表单正常渲染(系列为文本输入)✅ |
 +| 无 JS 错误 | ✅ |
 +
 +**维护方式**:以后要在此下拉增删选项,改 `templates.json` 的 `series_list` 即可(无需动代码)。
 +
 +====== 4.68 每个测试项增加「清空测试记录数据」(2026-10-18) ======
 +
 +**需求**:为每个测试项的测试文档,增加清空该测试项**测试记录数据**的功能。
 +
 +===== 后端(`test_api.py`) =====
 +新增 **`POST /api/test/session/<int:sid>/item/clear`**(**仅管理员**)
 +<code>
 +入参:{item_name, scope:['data','photo','files','status']}   # scope 默认 ['data']
 +data   → 清空 t_struct(表格) / t_data(测试数据) / t_recs(测试时段) / t_logs(日志)
 +          / t_codes / t_result(测试结果) / t_followup(处理方式) / t_ecr(变更内容)
 +photo  → 清空 t_photo
 +files  → 清空 t_files
 +status → status 重置为「未开始」
 +保留:测试条件/方法/工具/步骤/目的、测试备注、测试人员与时间、测试数量
 +</code>
 +权限兜底:手机号能对应到管理员姓名(`_wx_users()[phone].name in _admins()`)也放行。
 +
 +===== 前端(`test.html`) =====
 +- 测试项详情「测试项信息」标题右侧新增 **🧹 清空记录** 按钮(仅管理员可见/可用)
 +- 点击弹出**勾选式确认面板**:
 +<code>
 +  ☑ 测试数据(表格 / 记录 / 时段日志 / 测试结果)   ← 默认勾选
 +  ☐ 测试照片(N 张)
 +  ☐ 测试文档附件(N 个)
 +  ☐ 测试状态重置为「未开始」
 +  提示:保留 测试条件/方法/工具/步骤、测试备注、测试人员与时间
 +</code>
 +- 确认后调接口 → toast「🧹 已清空测试记录」→ 自动重载该文档并停留原测试项
 +
 +===== 验证(临时文档实测,用完已删) =====
 +| 项 | 结果 |
 +|---|---|
 +| 只清 `data` | ✅ 表格/测试数据/结果/日志/编码全清;**照片、附件、备注、测试人员、状态、测试数量全部保留** |
 +| 其他测试项 | ✅ 完全不受影响 |
 +| 全清(data+photo+files+status)| ✅ 照片、附件清空;状态重置「未开始」;备注仍保留 |
 +| 测试员调用 | ✅ **403** 仅管理员可清空 |
 +| 前端 | ✅ 按钮存在;面板标题/4 个勾选项/照片张数正确;关闭正常;无 JS 错误 |
 +| 真实文档 #42 | 仅打开面板未确认 → **数据未被改动** |
 +
 +====== 4.69 修复:电路测试类文档导出报告失败(页面显示原始 JSON)(2026-10-23) ======
 +
 +**现象**(用户截图):智慧阀门 → 电路测试 → 导出报告 → 手机上整屏显示
 +`{"error":"\u8be5\u4f1a\u8bdd..."}` 一串乱码,报告出不来(Word/PDF 同样)。
 +
 +===== 根因(写死分类名) =====
 +`test_api.py` 报告接口的 mode 校验:
 +<code>
 +if mode == "circuit" and sess["category"] != "电路测试":   # ❌ 写死
 +    return jsonify({"error": "该会话不是电路测试"}), 400
 +</code>
 +但 2026-09-16 分类拆分后,**电路测试组的实际分类是 功能性能 / 环境可靠性 / 机械应力**(很少再叫"电路测试"),
 +所以这些文档导出 → 一律 400 → 前端 `window.open('/api/test/report/...')` 打开的就是这个 JSON → 手机满屏乱码。
 +
 +`_render_report()` 里还有一处同样的写死判断 → 就算放过校验,报告里的测试项也会被**全部跳过(空报告)**。
 +
 +===== 修复 =====
 +1. 两处判断统一改用已有 helper **`_is_circuit_cat()`**(含历史"电路测试"+ 拆分后三类)
 +2. 报告类接口的错误响应 → **友好 HTML 错误页**(⚠️ 大标题 + 具体原因 + 返回按钮),不再返回裸 JSON
 +3. 报告 HTML 响应加 `Cache-Control: no-store`(避免微信缓存旧的错误响应)
 +4. **归档文档对非管理员只读 → 导出时跳过保存**(原来必先保存 → 被 403"已归档不允许修改"拦下,导出不了)
 +5. 保存失败提示具体原因(原来只显示"保存失败")
 +
 +===== 实测 =====
 +| 场景 | 结果 |
 +|---|---|
 +| #7(功能性能·**已归档**)Web 报告 | ✅ 200,**2.5 MB / 4321 行**(修复前 400 且内容为空)|
 +| #7 Word / PDF | ✅ 200 docx / 200 pdf |
 +| #42(阀体测试)`mode=valve` | ✅ 200(回归正常)|
 +| #110(其他测试)`mode=other` | ✅ 200(回归正常)|
 +| 错误场景(阀体文档用 circuit 模式导出)| ✅ 显示**友好错误页**「⚠️ 无法生成报告 + 原因 + 返回」 |
 +| 测试员 + 归档文档导出 | ✅ **0 次保存请求**,直接出报告 |
 +| 管理员 + 未归档副本 | ✅ 先保存再导出 |
 +| 数据核对 | 17 会话 / 66 测试项,与备份逐会话比对无丢失(少掉的 31 条是早已删除的 #6 会话遗留孤儿项)|
 +
 +⚠️ 提醒:手机上导 Word/PDF,微信内置浏览器**不支持下载**,需点右上角「⋯」→「在浏览器打开」;Web 报告在微信里可直接看。
 +
 +====== 4.70 气密性测试新增「第3步:全关」(2026-10-23) ======
 +
 +**需求**:气密性测试原来只有第1步(堵住A口)、第2步(堵住B口),**新增第3步 = 全关的情况**。
 +
 +===== 三步定义(阀门气密) =====
 +| 步骤 | 状态 | 说明 |
 +|---|---|---|
 +| 第1步 | 堵住A口 | 堵住A口,调节A口开度,调节水压 |
 +| 第2步 | 堵住B口 | 堵住B口,调节B口开度,调节水压 |
 +| **第3步** | **全关** | **阀门全部关闭,观察各部位渗漏** |
 +
 +⚠️ **表压气密性测试(表压测试分类)仍只有第1步**,不受影响。
 +
 +===== 实现要点(`test.html`) =====
 +<code>
 +function stepKeyOf(n){ return n===1?'step1':(n===2?'step2':'step3'); }
 +function stepNums(){ return isTPQItem()?[1]:[1,2,3]; }   // 表压气密只有第1步
 +function stepTitle(n){ n=1→堵住A口… ; n=2→堵住B口… ; n=3→全关(阀门全部关闭,观察各部位渗漏)}
 +function _mergeAllSteps(container){ … }                  // 跨步配置并集支持 3 步
 +</code>
 +- 管理员 `renderStruct` 与 **测试员 `wxStructTable`** 两套渲染都支持三步
 +- 默认 `t_struct` 增加 `step3:{opens,press,time,cells}`
 +- **全量**与**批量**两种记录方式都支持第3步(各自可配 开度/压力/时间)
 +- 第3步默认沿用第1/2步的开度压力(`_mergeAllSteps`),不会出现空表
 +
 +===== 报告(`test_api.py`) =====
 +<code>
 +def _step_list(st):   # 第1/2步始终输出(历史兼容);第3步仅在已配置时输出
 +</code>
 +Web / Word / PDF **三种报告**都包含第3步;批量报告的三处表格也补齐第3步。
 +
 +===== ★ 顺带修复的旧 bug =====
 +气密表格单元格的 `onchange` 行内**直接引用了函数局部变量** `o / p / j / bi2 / bi3`:
 +<code>
 +onchange="setCell(...);logAdd('更新 '+o+'开度/'+p+' 第'+(j+1)+'台 …')"
 +<!-- o/p/j 在事件触发时不存在 → Uncaught ReferenceError(步骤1/2 同样报错)-->
 +</code>
 +改为 **`logCell(this)`**:由函数从表格结构(首列"开度/压力" + 表头"第N台")推导日志文案。
 +现在三步任意单元格改数据都**零报错**。
 +
 +===== 验证 =====
 +| 项 | 结果 |
 +|---|---|
 +| 管理员视图(阀体气密)| ✅ 第1/2/3 步配置块与数据表齐全 |
 +| 测试员视图(`IS_WX`)| ✅ 三步齐全,第3步文案「全关」 |
 +| 第3步生成数据表 / 填值 / 保存 | ✅ cells 落库(如 `{"0%|1.0bar":{"1":"微漏"}}`)|
 +| 改任意步骤单元格 | ✅ 无 JS 错误(旧版三步都报 ReferenceError)|
 +| Web / Word / PDF 报告 | ✅ 均含「第3步:全关」(28KB / 18.5MB / 957KB)|
 +| 老文档(无 step3)报告 | ✅ 不出现第3步(`_step_list` 按需输出)|
 +| 表压气密性测试 | ✅ 仍只有第1步 |
 +| 数据安全 | 用「复制」副本验证,真实文档未改动;副本已删除 |
 +
 +====== 4.71 气密性测试:开度改为「4 个手动选择槽位」(2026-10-26) ======
 +
 +**需求**:开度选择提供 **4 个选项位置**,每个位置可手动选开度(**0%~100%,步进 10%**)。
 +
 +===== 改前 / 改后 =====
 +| | 改前 | 改后 |
 +|---|---|---|
 +| 控件 | 6 个固定勾选框(0/20/40/60/80/100%,可多选)| **4 个下拉**,每个可选 0%~100% 步进10%(含"空")|
 +| 组合 | 只能选那 6 个值 | 任意 4 个,如 0%/30%/60%/100% |
 +
 +===== 实现(`test.html`) =====
 +<code>
 +var OPEN_STEPS = 0%,10%,…,100%;               // 步进 10%
 +function openSlotSelects(step,curOpens,cls,attr) // 生成 4 个下拉
 +function openValSort(list)                      // 去重 + 按数值排序
 +function openExtraHint(opens)                   // 超出4个的历史开度提示"已保留"
 +</code>
 +- **全量模式**:`select.o-slot`(`stepBlock`)
 +- **批量模式**:三步都用 `select.bo-slot`(`batchBlock`)
 +- `buildTable()` / `batchGen()` 改为从下拉取值
 +
 +===== 历史兼容规则(重要) =====
 +<code>
 +· 打开老文档、四个槽位未被改动 → 保留原有【全部】开度(如原来 6 个,仍是 6 个表格)
 +· 一旦改动槽位 → 严格按这 4 个(或更少)生成
 +· 原有超出 4 个的开度会在界面上提示"原有额外开度(已保留)"
 +</code>
 +⚠️ 表压气密性测试(表压测试分类)**没有开度概念**,仍隐藏开度行、固定 100%/0.4MPa。
 +
 +===== 验证 =====
 +| 项 | 结果 |
 +|---|---|
 +| 全量:下拉数量/选项 | ✅ 每步 4 个下拉;选项 `—,0%,10%,…,100%`(12 项)|
 +| 全量:老值回填 | ✅ 原 0%/100% 自动填入前两个槽位 |
 +| 全量:改选 0/30/60/100 | ✅ `step1.opens` 准确写入,数据表 34 行 |
 +| 批量:三步下拉 | ✅ 12 个(3 步 × 4)|
 +| 批量:未改动 | ✅ 历史 6 个开度全部保留 |
 +| 批量:改选 0%/50% | ✅ 严格按 2 个生成 |
 +| 表压气密(回归)| ✅ 无开度下拉、无第2/3步、仍 0.4MPa |
 +| JS 错误 | ✅ 无 |
 +| 数据安全 | 全部用「复制副本」验证,真实文档未动;副本已删 |
 +
 +⚠️ 踩坑:`batchGen()` 里 `b[key]` 与 `st.batch[key]` 是**同一个对象引用**,
 +先 `b[key].opens=[]` 再取旧值 → 永远拿到空数组 → 必须**清空前先备份**。
  
 ====== 5. 测试项模板 ====== ====== 5. 测试项模板 ======
测试管理系统h5/冷启动文件.1791461657.txt.gz · Last modified: by 172.17.0.1 · Currently locked by: 216.73.216.247