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/09/17 11:14] – 签名合并机制修复 172.17.0.1研发任务管理系统h5:冷启动文件 [2026/10/23 02:08] (current) – 4.72 评审会四按钮失效根因 172.17.0.1
Line 67: Line 67:
 - 弹窗显示 + 📋 复制按钮;⚠️ 弹窗打开必须 classList.add('open')(style.display 会导致关闭按钮失效) - 弹窗显示 + 📋 复制按钮;⚠️ 弹窗打开必须 classList.add('open')(style.display 会导致关闭按钮失效)
  
-===== 5. 钉钉请假同步(2026-08-31) =====+===== 4.x 统一项目列表:任务 → 测试 / 库房 / 评审会(2026-09-17) ===== 
 + 
 +**需求**:研发任务系统管理员可**手动添加项目**;项目列表**同步**到测试系统、库房系统、评审会三个子模块。 
 + 
 +**① 数据源(唯一)** 
 +- 文件:`/home/ubuntu/docs/projects.json`(数组,首次自动生成默认 13 项) 
 +- 默认:拓远T1 / 普惠P1 / 金穗S1 / LoRa网关 / P3 / P5 / 流量计 / 表压 / 软管泵 / 施肥阀 / 施肥机 / 生产订单 / 其他 
 + 
 +**② 后端接口**(`rd_h5.py`) 
 +| 方法 | 路径 | 权限 | 说明 | 
 +|---|---|---|---| 
 +| GET | `/api/projects` | 全员 | 返回 `{ok, projects:[...]}` | 
 +| POST | `/api/projects` | **仅管理员**(`is_admin`)| `{action:"add"/"del"/"rename"/"move", name, old?, dir?}` | 
 + 
 +- `add` 重名拦截 400;`del` 不存在 400;`rename` 校验新旧名;`move` 上移/下移排序 
 +- 关键函数:`load_projects()` / `save_projects(lst)` / `api_projects()` 
 + 
 +**③ 四处接入** 
 +| 系统 | 文件 | 接入方式 | 
 +|---|---|---| 
 +| 研发任务 | `rd_h5.html` | `loadProjects()` 启动时拉取覆盖 `PROJ_LIST`;管理员见顶部「⚙️ 项目管理」按钮 → `projManage()` 面板(列表 + 添加 + 删除 + 改名 + ▲▼排序);`projRefreshUI()` → 调用 `load()` 刷新 | 
 +| 评审会 | `review.html` | `PROJECTS` 全局 + 启动 `loadProjects()`;`renderEdit()` 的项目下拉改用 `PROJECTS`(当前值不在列表时自动补入)| 
 +| 库房 | `rd_store.html` | `META.projects` 合并统一列表;`hisProj` datalist 与顶部「项目(全部)」筛选下拉同步刷新 | 
 +| 测试 | `test.html` | 物料编码输入框加 `list="projDL"` + `loadProjects()` 填充项目名提示(2 处输入框)| 
 + 
 +**④ 项目筛选框支持"直接输入新建"(2026-09-17 追加)** 
 +- UI:`<select id="fProj">` → **`<input id="fProj" list="projDL">`**(可下拉选、可直接打字);右侧出现「+ 新建项目」链接 
 +- `renderProj()` 改为填充 `<datalist id="projDL">`;`applyFilter()` 读 `fProj.value` 兼容不变 
 +- 新增 `projFilterHint()`:输入值不在项目列表且当前用户是管理员 → 显示「+ 新建项目」 
 +- 新增 `projCreateFromFilter()`:调用 `POST /api/projects {action:add}` → 成功后刷新 `PROJ_LIST`/datalist/筛选;**保存即同步到 测试/库房/评审会** 
 +- 非管理员输入新名称 → 不显示新建按钮(接口也会 403 拦截) 
 +- 页面结构验证:`fProj` input + `projDL` datalist + `projCreateFromFilter()` 均已上线 ✅ 
 +- 接口验证:新建成功 ✅ → 列表末尾出现 ✅ → 三系统页面均含 `/api/projects` 调用 ✅ → 删除清理无残留 ✅ 
 + 
 +**⑤ 项目清单更新(2026-09-17)** 
 + 
 +新增 4 个项目:**普惠P3 | 普惠P5 | 云枢AV1 | 云联LR1**(现共 18 项) 
 + 
 +| # | 项目 | 
 +|---|---| 
 +| 1-6 | 拓远T1 | 普惠P1 | 金穗S1 | LoRa网关 | P3 | P5 | 
 +| 7-12 | 流量计 | 表压 | 软管泵 | 施肥阀 | 施肥机 | 生产订单 | 
 +| 13-16 | 其他 | M10面板端连接器 | **普惠P3** | **普惠P5** | 
 +| 17-18 | **云枢AV1** | **云联LR1** | 
 + 
 +**四系统同步验证** 
 +- 统一接口 `/api/projects` → 18 项,4 个新项目全部在列 ✅ 
 +- **评审会**(`/review/`)→ 新建会议的项目下拉 18 项含新增 4 项 ✅(浏览器实测) 
 +- **库房**(`/store/`)→ 项目筛选下拉 18 项含新增 4 项 ✅(浏览器实测) 
 +- **测试**(`/test/`)→ 物料编码输入框项目提示 18 项含新增 4 项 ✅(浏览器实测) 
 +- **研发任务**(`/rd/`)→ 页面含 3 处 `/api/projects` 调用,机制相同(钉钉环境下浏览器自动化受限,未截图验证) 
 + 
 +> ⚠️ 测试系统踩坑:`projDL`(datalist)随**表单渲染**才存在,页面加载时填充无效 → 修复:`fillProjDL()` 在 `renderEdit()`/`openItem()` 时调用 + 输入框加 `onfocus="fillProjDL()"` + 加载后重试 3 次。 
 + 
 +**⑥ 验证(接口级,全部通过)** 
 +- GET 返回 13 项 ✅ 
 +- 普通用户 POST → **403**(仅管理员可维护项目)✅ 
 +- 管理员 add → 成功 ✅|重复 add → 400 拦截 ✅|rename ✅|move(上移)✅|del ✅ 
 +- 清理后无残留 ✅ 
 +- 4 个页面 200:`/rd/` `/review/` `/store/` `/test/` ✅ 
 + 
 +> ⚠️ 浏览器自动化验证受限:`/rd/` 在钉钉环境下会跳转登录页,无法稳定取到页面内全局函数(已知限制),UI 已由代码审查 + 接口验证覆盖。 
 + 
 +====== 4.64 修复:新建项目弹窗被底部导航遮挡(2026-10-10) ====== 
 + 
 +**问题**:研发任务系统「+ 新建项目 / ⚙️ 项目管理」弹窗底部被**底部导航栏挡住**,看不见也点不到「+ 添加项目」「关闭」。 
 + 
 +**根因(z-index 层叠)** 
 +| 元素 | 原 z-index | 说明 | 
 +|---|---|---| 
 +| `.modal`(弹窗遮罩)| **100** | ❌ 低于导航栏 | 
 +| `.tabbar`(底部导航)| **999** | 盖住了弹窗 | 
 + 
 +**修复(`rd_h5.html`)** 
 +<code> 
 +.modal{ ... z-index:1100; padding:20px 20px 78px }   /* 100→1100;底部留白避开导航 */ 
 +.mbox { ... max-height:calc(100vh - 140px) }          /* 88vh→按视口扣除,不越过导航栏 */ 
 +</code> 
 + 
 +**排查其他页面(确认无同类问题)** 
 +| 页面 | tabbar z-index | 弹窗 z-index | 结果 | 
 +|---|---|---|---| 
 +| rd_h5.html | **999** | 100 → **1100** | ✅ 已修 | 
 +| test.html | 50 | 9999 / 99999 | ✅ 正常 | 
 +| review.html | 50 | 9999 | ✅ 正常 | 
 +| family.html | — | 9999(.mask 99)| ✅ 正常 | 
 + 
 +**验证**:服务端与页面均已生效(`.modal{z-index:1100}`、`.mbox{max-height:calc(100vh - 140px)}`);弹窗 z-index(1100) > 导航(999) → 弹窗必在导航之上;内容框高度扣除 140px → 底部按钮(+添加项目 / 关闭)不再被遮挡。 
 + 
 +====== 4.65 修复:提示条 toast 被底部导航遮挡(2026-10-10) ====== 
 + 
 +**问题**(用户截图):页面底部弹的深色圆角提示条**只露出上半截**,下半截被白色底部导航栏吃掉。 
 + 
 +**根因(层叠 + 定位双重问题)** 
 +| 属性 | 原值 | 问题 | 
 +|---|---|---| 
 +| `.toast` z-index | **200** | ❌ 低于 `.tabbar` 的 **999** → 被压在导航栏之下 | 
 +| `.toast` bottom | **24px** | ❌ 正好落在导航栏占据的区域内(导航栏高约 33~53px)| 
 + 
 +两个问题叠加 → 重叠部分被不透明导航栏盖住,看起来就是"只露上半截"。 
 + 
 +**修复(`rd_h5.html`)** 
 +<code> 
 +.toast{ ... bottom:86px; ... z-index:1200 }   /* 24→86px;200→1200 */ 
 +</code> 
 +- bottom 86px > 导航栏高度(约 33~53px)→ 提示条整体位于导航栏之上 
 +- z-index 1200 > 999 → 即使重叠也显示在导航之上 
 + 
 +**实测验证(浏览器真实渲染)** 
 +| 项 | 数值 | 
 +|---|---| 
 +| 视口高 / 导航栏高 | 577px / 33px(测试页同款导航;研发页导航含 20px 图标更高,约 53px)| 
 +| 提示条高度 | 39px | 
 +| **修复后**(bottom 86px)| 提示条底边距视口 **86px**,与导航栏顶边**零重叠** → 完整可见 ✅ | 
 +| **修复前**(bottom 24px)| 底边 y=553,导航顶边 y=544 → **被覆盖 9px**(研发页导航更高 → 覆盖约 29px,接近整条)❌ | 
 +| 截图对比 | 上方(新值)完整;下方(旧值)被导航截断 —— 与用户截图一致 ✅ | 
 + 
 +**其他页面排查**:test/review/family 的 toast 未发现同类问题(其 tabbar z-index 为 50/无)。 
 + 
 +====== 4.66 修复:项目下拉缺新增项目(银穗Y1 等)(2026-10-10) ====== 
 + 
 +**问题**:项目「银穗Y1」在项目管理面板显示"已添加",但下拉列表里看不到(同理 M10面板端连接器 / 普惠P3 / 普惠P5 / 云枢AV1 / 云联LR1 也会消失)。 
 + 
 +**排查** 
 +| 检查项 | 结果 | 
 +|---|---| 
 +| `projects.json` | ✅ 已有「银穗Y1」(共 19 项)| 
 +| `GET /api/projects` | ✅ 返回含「银穗Y1」| 
 +| `/rd/` 缓存头 | ✅ no-store(不是缓存问题)| 
 +| **`loadProjects()` 调用点** | ❌ **只在 `addOwnerMore()` 里被调用;页面初始化流程从未调用** | 
 + 
 +**根因**:`PROJ_LIST` 的默认值是**硬编码的 13 项**(不含 M10/普惠P3/P5/云枢AV1/云联LR1/银穗Y1),页面每次加载都不会去拉 `/api/projects` → **刷新后下拉退回旧列表**,新增项目消失(管理面板里因为一次操作刷新过 PROJ_LIST 所以显示"已添加",两者不一致)。 
 + 
 +**修复(`rd_h5.html`)** 
 +<code> 
 +function load(){ 
 +  loadProjects(true);      // ★ 2026-10-10:初始化即拉取统一项目列表 
 +  ... 
 +} 
 +function loadProjects(noRefresh){         // 增加参数避免初始化时的 load() 递归 
 +  fetch('/api/projects').then(...).then(function(d){ 
 +    if(d&&d.ok&&d.projects&&d.projects.length){ 
 +      PROJ_LIST=d.projects; 
 +      renderProj();          // 刷新筛选框下拉(projDL) 
 +      refreshProjSelects();  // 刷新已打开表单的项目下拉(#aProj / #mProj,保留已选值) 
 +      if(!noRefresh){ projRefreshUI(); } 
 +    } 
 +  }); 
 +} 
 +function refreshProjSelects(){ ... }      // 新增 
 +</code> 
 + 
 +**行为验证(Node 里跑 rd_h5.html 的真实函数 + stub DOM/fetch)** 
 +| 场景 | 结果 | 
 +|---|---| 
 +| 修复前(硬编码默认列表)| 下拉 **13 项**,含「银穗Y1」:**❌ 没有** ← 复现用户问题 | 
 +| 修复后(初始化调 `loadProjects(true)`)| PROJ_LIST = **19 项**(末尾 云枢AV1, 云联LR1, **银穗Y1**)| 
 +| 筛选框 `projDL` | ✅ 含「银穗Y1」(19 项)| 
 +| 新增任务表单 `#aProj` | ✅ 含「银穗Y1」| 
 +| 编辑任务表单 `#mProj` | ✅ 含「银穗Y1」| 
 + 
 +**同类排查**:`test.html`(初始化顶层调用 `loadProjects()` ✅)、`review.html`(line 137 初始化调用 ✅)、`rd_store.py`(不使用该下拉)→ 仅 `rd_h5.html` 有此问题。 
 + 
 +====== 4.67 修复:添加附件后"无法保存任务修改"(2026-10-16) ====== 
 + 
 +**现象**(用户报):在任务详情里上传附件后点保存,任务修改没有保存 / 附件不见了。 
 + 
 +**排查**:后端全部正常(`/api/upload` 200、`POST /api/tasks/<id>/note` 200、`POST /api/tasks/<id>` 200)。用浏览器真实复现后定位到**两个前端缺陷**: 
 + 
 +===== 缺陷① 更新内容留空时,附件与耗时被静默丢弃(主因) ===== 
 +`saveTask()` 里判断是 `if(noteText){ ...POST note(带附件)... } else { 只保存任务字段 }` —— 
 +**只有填了"更新"文字才会记录 note**,因此"只加附件 / 只填耗时"时: 
 +- 附件已上传成功(UI 也显示了文件名)→ 但**不落库** ❌ 
 +- 耗时也不记录 ❌ 
 +用户截图正是这个状态:`更新 [空] | 耗时(时) [空] | 添加文件 📎 | 📎 YQ销售产品…` 
 + 
 +===== 缺陷② 上传中点保存会丢附件 ===== 
 +上传是异步的,`mAttUrl` 还没赋值就点保存 → 附件同样丢失。 
 + 
 +**修复(`rd_h5.html`)** 
 +<code> 
 +var mAttNameCur='';    // 附件原始文件名(用于默认文案) 
 +var mAttPending=null;  // 上传中的 Promise 
 +function waitAtt(cb){ if(!mAttPending) return false; toast('附件上传中,完成后自动保存…'); mAttPending.then(cb); return true; } 
 + 
 +// saveTask:① 上传中先等待 ② 只要 文本/附件/耗时 任一有值就记录 
 +if(waitAtt(function(){ saveTask(id); }))return; 
 +var hasExtra=!!(mAttUrl||(!isNaN(dur2)&&dur2>0)); 
 +if(noteText||hasExtra){ 
 +  var nb={text: noteText||(mAttUrl?('📎 附件:'+(mAttNameCur||'附件')):'(记录耗时)')}; 
 +  ... 
 +} 
 +</code> 
 +- `uploadAtt()`:上传中显示「📎 文件名(上传中…)」、成功记录 `mAttUrl+mAttNameCur`、失败给 toast、用 `.catch` 兜底 
 +- `markDone()`(标记完成)同步应用相同的等待与判定 
 + 
 +**浏览器实测(真实页面 + 真实文件上传)** 
 +| 场景 | 修复前 | 修复后 | 
 +|---|---|---| 
 +| 填了更新内容 + 附件 → 保存 | ✅ 正常 | ✅ 正常(未回归)| 
 +| **只加附件、更新留空 → 保存** | ❌ 无 `/note` 请求,附件丢失 | ✅ `/note` 200,附件落库,记录文案「📎 附件:文件名」| 
 +| **上传中点保存** | ❌ 附件丢失 | ✅ 提示"附件上传中,完成后自动保存…",随后自动补存 ✅ | 
 +| 只填耗时不填文字 | ❌ 耗时丢失 | ✅ 记录「(记录耗时)」+ 耗时 ✅ | 
 + 
 +**清理**:验证用的临时任务与测试附件已删除(任务数回到 106);用户自己上传的 3 个 xlsx 保留未动。 
 + 
 +====== 4.68 评审会:新增「会议现场照片」+ 去掉「记录员」(2026-10-22) ====== 
 + 
 +===== ① 会议现场照片 ===== 
 +**数据**:`meetings` 表新增 `photos TEXT DEFAULT ''`(多张用 `;` 分隔 URL),`migrate()` 自动加列。 
 +**接口**:`POST /api/review/meeting/<id>` 的 UPDATE 增加 `photos=?`(沿用旧值兜底);GET 用 `SELECT *` 自动带出。 
 +上传复用已有 `POST /api/review/upload` → 返回 `/review/attachments/<file>`。 
 + 
 +**前端**(`review.html` 会议编辑表单,签名行下方): 
 +<code> 
 +会议现场照片  📷 拍照   🖼 手机图库 
 +  [缩略图 74×74] × N   ← 点图可放大,右上角 ✕ 单张删除 
 +</code> 
 +- `upMeetPhoto()` 上传后写入 `CUR.photos` 并即时刷新缩略图(提示"记得保存") 
 +- `delMeetPhoto()` 从列表移除 
 +- `collectForm()` 带上 `photos` → 新建/保存/生成报告三条路径都会提交 
 + 
 +===== ② 去掉「记录员」 ===== 
 +- 要点记录表单里 **删除「记录人员」输入行**(原 `p_user` + 👥 选择器) 
 +- **数据库 `points.p_user` 字段保留**,保存时沿用旧值(`collectPoints()` 里若无输入框则取 `p.p_user`)→ **历史数据不丢** 
 +- 清理无用的 `pickPointUser()` 选择器;把共用的选择辅助函数改名 **`pickIntoBtn()`** 供「记录负责人」使用(重要:原 `pickOwner` 依赖它,改名时必须同步) 
 +- 保留:记录时间 / 记录内容 / 记录负责人 / 记录图片 / 附件 / 状态 
 + 
 +===== 验证 ===== 
 +| 项 | 结果 | 
 +|---|---| 
 +| `photos` 列迁移 | ✅ 自动加列成功 | 
 +| 上传 2 张 → 保存 → 回读 | ✅ `photos` 两个 URL、顺序正确、文件真实落盘 | 
 +| 要点 `p_user` 历史值 | ✅ 保留(前端不再显示)| 
 +| 浏览器表单 | ✅ 含「会议现场照片」+ 拍照/图库入口 + `#meetPhotos` 容器 | 
 +| 要点区 | ✅ 记录时间/内容/负责人/图片/附件正常,**「记录人员」已消失** | 
 +| JS 报错 | ✅ 无 | 
 +| 真实数据 | ✅ 14 个真实会议完好;临时验证会议与测试图片已清理 | 
 + 
 +⚠️ **踩坑记录**:改 UPDATE 语句时先加了 `photos=?` 占位符但参数没同步插入 → `sqlite3.ProgrammingError: 17 placeholders vs 16 parameters` → 保存 500。 
 +**教训(已在纪律中)**:改 SQL 必须核对**占位符数量 = 参数个数**,改完立即用临时数据跑一次保存。 
 + 
 +====== 4.69 修复:评审会「无法导出报告」(2026-10-22) ====== 
 + 
 +**现象**:评审会点「网页报告 / Word版 / PDF版」没有反应,导不出来。 
 + 
 +**排查**:服务端三种格式都正常 —— `GET /api/review/report/3` → 200(4091 字节 HTML);`?fmt=docx` → 200(2.5 MB,Word 2007+);`?fmt=pdf` → 200(143 KB,6 页)。**问题纯在前端**。 
 + 
 +**根因(两个)** 
 +1. **弹窗被拦截**:导出流程是 `fetch(保存) → .then(() => window.open(url))`。 
 +   `window.open` 写在**异步回调**里,不再算用户手势 → 微信/Chrome/Safari 一律当弹窗拦截 → **点了完全没反应**(最常见原因)。 
 +2. **失败无提示**:保存失败时 `.then(d => { if(d.ok) window.open(...) })` 没有 else → 
 +   **什么提示都没有**,用户只能看到"点了没用"。 
 + 
 +**修复(`review.html`)** 
 +<code> 
 +function _reportFlow(fmt){ 
 +  // 点击瞬间同步开占位窗口(同步调用不算弹窗) 
 +  var w = window.open('about:blank','_blank'); 
 +  fetch(保存会议).then(d => { 
 +    if(!d.ok){ w.close(); toast('导出失败:'+d.error); return; }   // ← 明确报错 
 +    w.location.href = url;          // 让占位窗口跳转 
 +    // 开不出窗(微信)→ location.href 当前页兜底 
 +  }).catch(() => { w.close(); toast('导出失败(网络错误),请重试'); }); 
 +} 
 +function genReport(){ _reportFlow(''); } 
 +function genExport(fmt){ _reportFlow(fmt); }      // docx / pdf 
 +</code> 
 +- 微信环境导出 Word/PDF 会额外提示:**「若打不开请点右上角「⋯」→「在浏览器打开」」** 
 +- 网页报告 / Word / PDF 三条路径统一走 `_reportFlow()` 
 + 
 +**验证** 
 +| 项 | 结果 | 
 +|---|---| 
 +| 服务端 Web 报告 | ✅ 200,4091 字节 HTML | 
 +| 服务端 Word | ✅ 200,2.5 MB,`Microsoft Word 2007+` | 
 +| 服务端 PDF | ✅ 200,143 KB,6 页 | 
 +| 前端三种格式 | ✅ 跳转 URL 正确(`/report/<id>`、`?fmt=docx`、`?fmt=pdf`)| 
 +| 失败路径(会议不存在)| ✅ toast「导出失败:不存在」+ 占位窗口关闭 + **不误跳转** | 
 +| JS 错误 | ✅ 无 | 
 +| 真实数据 | ✅ 14 个真实会议完好;临时验证会议已删 | 
 + 
 +⚠️ **同源教训**:凡"点击 → 异步请求 → 再打开窗口"的流程,`window.open` 必须**在点击事件内同步调用**,否则必被拦截。 
 + 
 +====== 4.70 评审会:去签名 + 修「照片没保存/导出不能用」根因(2026-10-22) ====== 
 + 
 +用户反馈三症状:**上传的现场照片没保存住**、**三种报告都导不出**、签名功能要去掉。 
 + 
 +===== ★ 根因:`/review/` 页面没有禁缓存头(一处修好,前两个症状全解决) ===== 
 +<code> 
 +修复前 /review/  →  cache-control: no-cache  + ETag + Last-Modified 
 +修复后 /review/  →  cache-control: no-store, no-cache, must-revalidate, max-age=0 
 +                    pragma: no-cache | expires: 0     (与 /test/ 一致) 
 +</code> 
 +`no-cache` 只是"可缓存但需校验",配合 ETag 微信会长期复用旧 HTML → 用户手里一直是**没有「会议现场照片」字段、导出还是老代码**的旧页面。 
 +所以:照片"没保存住"(旧页面根本没这个字段)+"导出不能用"(旧 window.open 逻辑)= 同一个根因。 
 + 
 +===== ① 去掉手写签名 ===== 
 +- 表单去掉「与会人员签名」行;`collectForm()` 不再提交 `signatures` 
 +- Web / Word / PDF 报告去掉 `_sig_block()` / `_sig_docx()` 签名块 
 +- **数据库 `meetings.signatures` 字段保留**(历史数据不删,不再显示) 
 + 
 +===== ② 导出加固 ===== 
 +- **微信内不再 `window.open`**(必被拦截)→ 保存成功后当前页 `location.href` 跳转 
 +- 非微信:点击时同步开占位窗口,保存成功后让该窗口跳转(避免异步弹窗被拦) 
 +- 保存失败 → 明确 toast「导出失败:原因」并关闭占位窗口 
 + 
 +===== 完整实测(临时会议,全流程真实操作) ===== 
 +| 步骤 | 结果 | 
 +|---|---| 
 +| 表单含「会议现场照片」 | ✅ | 
 +| 签名行已消失 | ✅ | 
 +| 真实 file input 上传照片 | ✅ `CUR.photos` 有值、缩略图渲染 1 张 | 
 +| 点保存 | ✅ toast「✅ 已保存」,5 个写请求 0 错误 | 
 +| 回读数据库 | ✅ **photos 已落库**、议题/概述/意见均已保存 | 
 +| 三种报告 | ✅ Web 1854B / Word 35KB / PDF 38KB,全部 200 | 
 +| 真实会议 #3(库里有旧签名)| ✅ 三格式 200,报告页面「签名」出现 **0 次** | 
 +| JS 错误 | ✅ 无 | 
 +| 收尾 | 临时会议 #28 + 测试图片已删除,14 个真实会议完好 | 
 + 
 +⚠️ **教训**:新增功能后如果用户"看不到/用不了",**先查页面缓存头**(`curl -sI` 看 cache-control)。 
 +`/test/` `/rd/` `/store/` `/family/` `/extracur/` 都有 no-store,唯独 `/review/` 漏了 → 已补齐。 
 + 
 +====== 4.71 评审会导出:彻底去掉弹窗(修 Windows「获取打开此'about'链接的应用」)(2026-10-22) ====== 
 + 
 +**现象**(用户截图):**电脑**上点「网页报告/Word版/PDF版」→ Windows 弹出 
 +> 获取打开此'about'链接的应用 —— 你的电脑没有可打开此链接的应用。请尝试在 Microsoft Store 中查找兼容应用。 
 + 
 +**原因**:4.69 为了绕开"异步弹窗被拦",改成点击时先 `window.open('about:blank')` 占位。 
 +`about:blank` 在部分 Windows 浏览器里会被当外部协议交给系统处理 → 弹上述对话框 ❌ 
 + 
 +**定稿方案:完全不用弹窗** 
 +<code> 
 +// 保存会议 → 成功后当前页跳转 
 +fetch(保存).then(d => { if(!d.ok){toast('导出失败:'+d.error);return;} location.href = url; }); 
 +</code> 
 +| 格式 | 行为 | 
 +|---|---| 
 +| 网页报告(text/html)| 当前页打开报告,按返回键回编辑页 | 
 +| Word/PDF(`Content-Disposition: attachment`)| **只触发下载,页面不跳走** | 
 + 
 +- 微信环境保留提示:导 Word/PDF 若无下载 →「点右上角「⋯」→「在浏览器打开」」 
 +- 缩略图的 `window.open(this.src)`(看大图)是 http 地址,不受影响,保留 
 + 
 +**实测** 
 +| 项 | 结果 | 
 +|---|---| 
 +| 网页报告 | ✅ `window.open` 调用 **0 次**,页面成功跳到 `/api/review/report/29`,标题「…评审报告」正确 | 
 +| Word | ✅ `window.open` 0 次、下载触发、页面停留原地址 | 
 +| PDF | ✅ 同上 | 
 +| 失败路径 | ✅ toast「导出失败:原因」| 
 +| JS 错误 | ✅ 无 | 
 + 
 +⚠️ **教训**:不要再为了"防弹窗拦截"用 `window.open('about:blank')` 占位 —— 
 +Windows 会当外部链接处理。**同页跳转(`location.href`)+ 服务端 attachment 头** 是跨平台最稳的做法。 
 + 
 +====== 4.72 ★评审会「四个按钮全部失效」根因(我引入的 bug)+ 三处加固(2026-10-23) ====== 
 + 
 +**现象**:会议里 💾保存 / 📄网页报告 / 📝Word版 / 📕PDF版 **全部点了没反应**; 
 +手机端更是「没有任何提示,就是不生成报告」。 
 + 
 +**★ 真根因(自查出的自造 bug)** 
 +4.70 去掉「记录人员」输入框时,`collectPoints()` 里写成了: 
 +<code> 
 +p_user:(el.querySelector('.p_user')?el.querySelector('.p_user').value:(p.p_user||'')) 
 +//                                              ↑ 这个 p 在 forEach(function(el){}) 里根本不存在 
 +</code> 
 +→ 点任一按钮 → `Uncaught ReferenceError: p is not defined` → 
 +**连 fetch 都没发出、toast 也没弹** → 四个按钮静默全废(保存/三种报告都走 collectPoints)。 
 + 
 +**修复** 
 +<code> 
 +document.querySelectorAll('#pointsBox .point').forEach(function(el,i){ 
 +  var _pu=(CUR_POINTS[i]&&CUR_POINTS[i].p_user)||'';   // 按索引沿用库里的记录人员,数据不丢 
 +  ... 
 +}); 
 +</code> 
 + 
 +**为什么上一轮测试没抓到**:测试用的是**空会议**(要点 0 条)→ `forEach` 根本没执行 → 没报错。 
 +教训:**测保存/导出必须用"有现存要点记录"的会议**(本次用「继承真实会议 #3」的临时会议复现,3 条要点)。 
 + 
 +**顺带修的三处** 
 +1. `new_meeting`(继承会议)500:`p.get("p_status")` → `sqlite3.Row` 没有 `.get` → 改 `p["p_status"] or "未解决"` 
 +2. 页面顶部显示版本号 **v2026-10-23** → 一眼确认手机是否加载新版本 
 +3. 新增**全局 error 监听** → JS 报错时明确 toast「⚠️ 页面脚本错误:…」,不再静默失效 
 + 
 +**实测(继承真实会议 #3 的临时会议,3 条真实要点,真点击按钮)** 
 +| 按钮 | 结果 | 
 +|---|---| 
 +| 💾 保存 | ✅ toast「✅ 已保存」,`POST /api/review/meeting/30` → 200 | 
 +| 📝 Word版 | ✅ toast「📝 正在生成 Word…」+ 200,页面停留(下载)| 
 +| 📕 PDF版 | ✅ toast「📕 正在生成 PDF…」+ 200,页面停留 | 
 +| 📄 网页报告 | ✅ 当前页跳到 `/api/review/report/30`,标题「…评审报告」正确 | 
 +| JS 错误 | ✅ 0 个 | 
 +| 数据 | ✅ 3 条要点内容与库内记录人员全部保留 | 
 +| 真实会议 #3(只读检查)| ✅ 版本标记 v2026-10-23、collectPoints/collectForm 均正常、无报错 | 
 + 
 +⚠️ **手机端 Word/PDF 说明**:微信内置浏览器**不支持下载 docx/pdf**(这是微信限制,不是系统 bug)。 
 +请在微信里点右上角「⋯」→「在浏览器打开」,再导出即可;网页报告在微信用可以直接看。 
 + 
 +====== 5. 钉钉请假同步(2026-08-31) ======
 - leave.json(date/name/type/start/end/status);/api/leave?date= 查询 - leave.json(date/name/type/start/end/status);/api/leave?date= 查询
 - 人员筛选时名字后显示当天请假标记;任务行负责人标签后同样显示 - 人员筛选时名字后显示当天请假标记;任务行负责人标签后同样显示
研发任务管理系统h5/冷启动文件.1789643682.txt.gz · Last modified: by 172.17.0.1