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/10 15:22] – 4.50 交变项去掉累计 172.17.0.1测试管理系统h5:冷启动文件 [2026/10/26 10:06] (current) – 4.71 开度4个手动槽位 172.17.0.1
Line 1153: Line 1153:
 | docx/PDF 报告含「合计」 | **0 处** ✅;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/冷启动文件.1791645754.txt.gz · Last modified: by 172.17.0.1