This is an old revision of the document!
Table of Contents
研发任务管理系统 H5 — 冷启动文件
版本: 2.2 | 更新: 2026-09-06 | 用途: 研发任务管理系统(含库房集成)的冷启动/重建/排障手册
⚠️ 功能保全铁律(用户定稿):本系统功能只增不减,改版前先读本文件,改完逐项验证,功能不允许丢失。
⚠️ 测试铁律:测试必须用独立数据/独立进程,严禁污染生产数据(rd_tasks.json/accounts.json/store.db)。
一、系统概述
研发任务管理 + 研发库房管理 一体化系统(钉钉生态): - 任务:任务看板(跟踪任务/临时任务)、筛选(优先级/项目/状态/更新天数/搜索)、人员筛选、新增任务、更新记录(带时分+附件+可删)、子任务(进度联动)、工时统计、日报生成 - 账户体系:管理员(武燕青)全权;普通用户(钉钉人员)只看自己任务、可编辑自己任务;管理员可配置用户权限 - 登录:钉钉统一认证(JSAPI 免登 requestAuthCode / OAuth 授权码 / 工作台 authCode 三通道) - 库房集成:底部导航 3 tab(任务/库房/我的);库房 = 研发库房管理(独立子系统,蓝图挂载) - 线上地址:https://rd.huiflytech.com(Caddy HTTPS)= https://rd.huiflytech.com/rd/(任务)、/store/(库房)
二、部署架构
用户/钉钉 → https://rd.huiflytech.com (Caddy :443/:80 自动 HTTPS)
→ rd-h5.service (systemd, Flask :13544, 统一进程)
├─ /rd/* → 任务前端 + API(rd_h5.py + rd_h5.html)
├─ /store/* → 库房前端 + API(rd_store.py 蓝图 + rd_store.html)
└─ /api/* → 任务 API
- systemd 服务:`rd-h5.service`(ExecStart=/home/ubuntu/projects/ctwing-2/venv/bin/python /home/ubuntu/.hermes/scripts/rd_h5.py,端口 13544) - Caddy:`/etc/caddy/Caddyfile`(rd.huiflytech.com → 127.0.0.1:13544;80→443 自动跳转;Let's Encrypt 自动证书) - 数据目录:`/home/ubuntu/docs/`
三、数据文件
| 文件 | 内容 | 说明 |
| —— | —— | —— |
| `rd_tasks.json` | 任务数据(items/updated_at) | 主数据;normalize 兼容字段 |
| `accounts.json` | 账户体系 | roles.admin(武燕青)/roles.user/perms(用户权限)/pwds |
| `people.json` | 人员库 | 简称→全名(钉钉通讯录);注意:无 roles 字段(admin 判定在 accounts.json) |
| `ding_org.json` | 钉钉通讯录全量 | MCP 同步缓存(17 人:姓名/userid/部门);/api/ding/org 优先读此文件 |
| `leave.json` | 钉钉请假记录 | MCP OA 同步(cron 0210e63abf25 工作日 08:30) |
| `tags.json` | 标签库 | 任务标签(默认/周报汇报 + 自定义) |
| `store.db` | 库房 SQLite | samples/requests/history 表(软删除 deleted 标记) |
| `.env` | 凭证 | DINGTALK_CLIENT_ID/SECRET(慧飞任务应用 dingh2ogz…)、DINGTALK_CORP_ID=4901944860、HALO_DB_PASSWORD 等 |
四、核心功能
1. 任务看板
- 任务类型:跟踪任务([优先级](项目)内容)/ 临时小任务(⏱️ 当天) - 筛选:优先级/项目/状态/未更新天数(工作日)/搜索;人员标签筛选(点击) - 人员标签:显示所有有任务人(从任务提取);“+ 更多”打开钉钉通讯录全量(17 人) - 任务详情:状态/进度/负责人/子任务(进度联动)/更新记录(带时分+附件)/总耗时
2. 账户体系(2026-08-31)
- 管理员:武燕青(全部权限:查看/编辑/删除所有任务、账号管理、生成日报、新增) - 普通用户:只见自己任务(负责人=自己或子任务负责人=自己,简称/全名变体匹配 is_mine)、可编辑/删除自己任务(can_edit/can_delete) - 权限配置:管理员「账号管理」弹窗(view_own_only/edit_own/delete_own/manage_subtask 开关 + 设置口令) - 后端强制校验:can_edit/can_delete 在 update_task/add_note/del_note/del_task/subtask_note 接口执行(403) - ⚠️ isAdmin 判定:统一账户体系(USER_ROLE=admin 或 名字=武燕青);不依赖 people.json roles(空)
3. 钉钉统一认证(3 通道)
- JSAPI 免登(主,钉钉内):UA 检测 DingTalk → 加载 dingtalk.open.js → requestAuthCode({corpId: ding3049357e0fcfed24f5bf40eda33b7ba0}) → code → /api/ding/login(gettoken+getuserinfo)→ 自动登录 - OAuth 授权码(浏览器):/api/ding/oauth/url 生成授权 URL → 回跳 /rd/ding_cb?code → /api/ding/oauth/cb(userAccessToken+users/me)→ 登录 - 工作台 authCode:URL 带 authCode → 直接 dingLogin(失败自动降级 JSAPI) - 登录态:localStorage rd_user(同域任务/库房共享);登录用户自动退出只读模式(exitReadonly)
4. 周报生成(2026-08-31)
- 📊 生成周报按钮(btnWeek,管理员):/api/report/week 生成上一周研发工作周报 - 三部分:①研发项目推进(各项目在管/更新/新增/完成+主要进展总结式)②研发管理推进(任务系统/库房系统运行状态)③研发任务日报(正常推进) - 弹窗显示 + 📋 复制按钮;⚠️ 弹窗打开必须 classList.add('open')(style.display 会导致关闭按钮失效)
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`)
.modal{ ... z-index:1100; padding:20px 20px 78px } /* 100→1100;底部留白避开导航 */
.mbox { ... max-height:calc(100vh - 140px) } /* 88vh→按视口扣除,不越过导航栏 */
排查其他页面(确认无同类问题)
| 页面 | 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`)
.toast{ ... bottom:86px; ... z-index:1200 } /* 24→86px;200→1200 */
- 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`)
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(){ ... } // 新增
行为验证(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`)
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||'附件')):'(记录耗时)')};
...
}
- `uploadAtt()`:上传中显示「📎 文件名(上传中…)」、成功记录 `mAttUrl+mAttNameCur`、失败给 toast、用 `.catch` 兜底 - `markDone()`(标记完成)同步应用相同的等待与判定
浏览器实测(真实页面 + 真实文件上传)
| 场景 | 修复前 | 修复后 |
| — | — | — |
| 填了更新内容 + 附件 → 保存 | ✅ 正常 | ✅ 正常(未回归) |
| 只加附件、更新留空 → 保存 | ❌ 无 `/note` 请求,附件丢失 | ✅ `/note` 200,附件落库,记录文案「📎 附件:文件名」 |
| 上传中点保存 | ❌ 附件丢失 | ✅ 提示“附件上传中,完成后自动保存…”,随后自动补存 ✅ |
| 只填耗时不填文字 | ❌ 耗时丢失 | ✅ 记录「(记录耗时)」+ 耗时 ✅ |
清理:验证用的临时任务与测试附件已删除(任务数回到 106);用户自己上传的 3 个 xlsx 保留未动。
5. 钉钉请假同步(2026-08-31)
- leave.json(date/name/type/start/end/status);/api/leave?date= 查询 - 人员筛选时名字后显示当天请假标记;任务行负责人标签后同样显示 - 日报:负责人姓名后直接显示(X 请假);每日 08:30 cron 同步(请假模板 PROC-4DD795D7-E54A-4942-B91A-6743ED78AD42)
6. 任务标签(2026-09-02)
- 标签库 tags.json(预置:默认/周报汇报)+ /api/tags(GET/POST 自定义新增) - 任务详情 🏷️ 区:标签库多选 + 自定义(自动入库);新任务默认 [“默认”] - 任务行/详情标题显示非默认标签(🏷️橙色高亮),“默认”不显示在行上
7. 评审会模块(2026-09-05)
- 独立蓝图 review_api.py + 页面 /review/(底部导航 5 tab:任务/库房/测试/评审会/我的) - 会议:新建/继承(复制全部内容+议题自动续序号)/项目/开始时间/预计时长/结束时间(自动算实际)/会议人员(钉钉多选)/负责人/评审阶段/标签
- ★ 评审阶段(2026-09-16 更新,共 8 个):需求分析/可行性评估 → 立项评审 → 方案设计 → 详细设计 → 样机试制 → 测试验证 → 量产导入 → 结项评审
- 定义位置:`review.html` 的 `stageSel`(唯一来源,改这里即可)
- ★ 与会人员手写签名(2026-09-16 新增):
- 前端:编辑页「与会人员签名」区 → 与会人员一人一张卡片(未签名/已签缩略图)→ 点「✍️ 签名」弹出手写板(canvas 900×360,支持手指触摸与鼠标,可清除重写)
- 存储:`meetings.signatures`(TEXT,JSON `{人名: “data:image/png;base64,…”}`);保存接口仅当传入非空才更新(防前端未带字段时清空)
- 人员来源:`meetings.members`(分号/逗号分隔)解析
- 报告:Web 内嵌 `<img>`;Word/PDF 用 `add_picture` 插入真实签名图(原为“打印后手写签字”的空白表)
- 相关函数:`sigLoad/renderSigns/openSign/initPad/clearPad/savePad/clearSign/closeSign`;后端 `_parse_sig/_sig_one/_sig_block/_sig_docx`
- ★ 自动记录签名时间(2026-09-16):签名为 `{人名: {“img”: “data:image/png;base64,…”, “time”: “YYYY-MM-DD HH:MM”}}`(兼容旧格式纯 base64 字符串,无时间不报错);前端签名卡片显示 `🕐 时间`;报告 Web/Word/PDF 均在签名下方显示时间
- ★ 签名后自动提交:`savePad` 完成后自动调用 `saveMeeting()` 落库(避免忘记点保存导致报告无签名)
- 实测:3 人签名(2 新格式含时间 + 1 旧格式)→ Web 报告 3 张签名图 + 时间正确显示 ✅|Word 内嵌 3 张图 + 含时间 ✅|PDF 69KB ✅
- ★ 报告要点表按评审阶段动态列(2026-09-16):
- 判定:`_pt_need(s)` = 阶段含「需求分析」;`_pt_cols(s)` 返回列定义
- 需求分析/可行性评估阶段 → 精简为 4 列:序号 | 记录内容 | 日期 | 负责人(去掉 状态/优化方案/记录图片/附件)
- 其他阶段 → 保持 8 列(序号/记录内容/状态/优化方案/记录图片/附件/日期/负责人)
- 生效范围:Web 报告 + `_export_docx` + `_export_pdf`(共 3 处)
- 实测:需求分析阶段 Web/Word 表头均为 4 列 ✅;立项评审阶段对照仍为 8 列 ✅
- ★ 签名合并机制(2026-09-17 修复覆盖丢失 bug):
- 现象:#9 会议中超哥(席江锋)的手写签名消失(DB 仅剩武燕青/刘瑞红)
- 根因:前端 `SIG` 是打开页面时的快照 → 保存时整体覆盖 `signatures` 字段;多人用各自设备签名时,后保存者会把先前他人的签名冲掉
- 修复:后端改为按人名合并(`_merged = dict(old); _merged.update(new)`);传入 `{“人名”: null}` 表示删除该人签名
- 前端:新增 `SIG_DEL` 删除标记数组;`saveMeeting` 提交 `{本机已有签名…, 已清除人名: null}`
- 实测(模拟三设备并发):甲签 → {甲};乙用旧快照签 → {甲,乙} 未丢失 ✅;丙签 → {甲,乙,丙};清除乙 → {甲,丙} ✅
- ⚠️ 已丢失的签名无法恢复(无备份)→ 需当事人重新签名
- 实测:签名 PNG 落库 ✅|Web 报告含签名图 ✅|Word 内嵌 `word/media/image1.png` + drawing 引用 ✅|PDF 52KB ✅
- 要点记录:序号/时间/记录人员(钉钉)/内容/优化方案/记录负责人(钉钉)/状态(☐未解决☑已解决复选框同行)/拍照+图库双传/附件 - 评审结果三方案:方案评审通过 / 方案局部修改无需再审 / 方案重大问题需二次评审 - 报告:网页版 + Word版 + PDF版(libreoffice 转换);首部公司信息“慧飞数字科技(秦皇岛)有限公司”;要点表状态独立列;末尾签字确认区 - 数据:~/docs/review/review.db(meetings/points 两表)+ attachments/ - ⚠️ 服务运行在 ctwing-2 venv(非 huifei venv),python-docx 须装在该 venv
8. 库房集成(2026-08-31 迁移)
- rd_store.py 改 Blueprint(bp)挂载到 rd_h5 统一进程(13544);rd-store.service 已停(13545 退役) - 库房独立运行兼容:`STORE_DB=/tmp/x.db STORE_PORT=13599 python rd_store.py`(测试隔离) - 底部导航(任务/库房/我的):任务=视图切换;库房=跳 /store/;我的=账户信息 - 库房页登录校验:同域读 localStorage rd_user,未登录显示橙色提示条(不覆盖页面) - 库房 tabbar 用 div(不能用 nav——库房 CSS 有 nav 标签选择器 sticky 会覆盖 fixed)
5. 底部导航(3 tab)
[📋 任务] [📦 库房] [👤 我的]
- 我的:账户信息(姓名/身份/钉钉ID/登录方式/我的任务数/权限)+ 账号管理(管理员)+ 返回首页 + 退出登录 - 普通用户无「切换身份」「账号管理」
五、API 列表
| API | 方法 | 说明 |
| —– | —— | —— |
| /api/tasks | GET | 任务列表(?user= 过滤:普通用户只看自己;admin 全量) |
| /api/tasks | POST | 新增任务(记录 creator) |
| /api/tasks/<tid> | POST | 更新任务(can_edit 校验) |
| /api/tasks/<tid> | DELETE | 删除任务(can_delete 校验) |
| /api/tasks/<tid>/note | POST | 添加更新记录(can_edit) |
| /api/tasks/<tid>/note/<idx> | DELETE | 删除记录(can_edit) |
| /api/tasks/<tid>/subtask/<idx>/note | POST | 子任务更新(can_edit) |
| /api/me | GET | 当前用户信息(role/perms) |
| /api/accounts | GET/POST | 账户列表(管理员)/配置权限(管理员) |
| /api/login_pwd | POST | 口令登录(备用) |
| /api/set_pwd | POST | 设置口令(管理员) |
| /api/people | GET/POST | 人员库 |
| /api/ding/login | POST | 免登 authCode→身份 |
| /api/ding/org | GET | 钉钉通讯录(读 ding_org.json) |
| /api/ding/oauth/url | GET | OAuth 授权 URL |
| /api/ding/oauth/cb | POST | OAuth 授权码→身份 |
| /api/upload | POST | 附件上传 |
| /api/report/today | GET | 今日日报(含当日请假区) |
| /api/report/week | GET | 上周周报(项目推进/管理推进/日报) |
| /api/tags | GET/POST | 标签库(列表/自定义新增) |
| /api/leave | GET | 请假记录(?date= 按天过滤) |
| /api/store/* | * | 库房 API(samples/requests/approve/destroy/back/back_apply/ledger/history/…) |
六、关键规则(用户拍板)
1. 管理员:武燕青——查看/编辑所有任务 + 账号管理;普通用户只见/只编辑自己任务 2. 只读模式:?ro=1(共享链接);登录用户自动退出只读(exitReadonly 恢复标题/按钮) 3. 任务负责人:存简称(张/席),登录用全名——匹配用 name_variants(简称↔全名变体) 4. 普通用户不显示:切换身份、账号管理、口令登录入口(唯一登录=钉钉认证) 5. 日报:生成今日日报(按任务/工时);员工日报汇总 21:30 推送(cron 52967bf00170) 6. 钉钉通讯录:MCP 同步到 ding_org.json(cron 8f32fd550f80 每周一 06:00) 7. owner 一律全名(2026-09-02 定稿,不用简称);前端 shortOf 全名→简称映射兼容分组 8. 列表排序:同优先级内新任务在前(id 降序) 9. 删除/编辑 API 必须传 user(delTask/delNote 带 {user},否则 403) 10. 附件查看:链接加 download 属性(内置浏览器无法预览 docx,直接下载) 11. 子任务新增时间(2026-09-03 用户定稿):= 子任务名首次出现在更新记录(notes)中的日期;无记录才回退 created 字段。⚠️ 曾 bug:保存任务时旧子任务 created 被重设为当天(后端已修:同名同负责人保留旧 created);历史污染数据已按 notes 首现批量修正 12. 项目列表:PROJ_LIST 常量(拓远T1/普惠P1/金穗S1/LoRa网关/P3/P5/流量计/表压/软管泵/施肥阀/施肥机/其他) 13. 复盘 cron 已暂停(b9dbd39c3f34,2026-09-03 用户要求不再定时推送,可手动 run) 14. 测试报告导出(2026-09-06):/api/test/report/<sid>?mode=&fmt=html|docx|pdf(横向 Word 含照片)+ /api/test/report/full?fmt= 整机;前端按钮 📄网页/📝Word/📕PDF 15. 评审会导出(2026-09-06):/api/review/report/<mid>?fmt=html|docx|pdf 16. 底部导航 5 tab 三处同步(rd_h5.html / rd_store.html / test.html / review.html 各自的 tabbar)
七、部署运维
sudo systemctl restart rd-h5.service # 重启 sudo journalctl -u rd-h5.service -n 50 # 日志 sudo systemctl reload caddy # Caddy 重载 sudo vi /etc/caddy/Caddyfile # 域名配置(rd.huiflytech.com → 13544) git -C /home/ubuntu add -A && git -C /home/ubuntu commit -m "..." # 版本管理
- 证书:Caddy 自动(Let's Encrypt);阿里云手动证书包可留档备用 - 域名 DNS:rd.huiflytech.com → 82.156.85.124(huiflytech.com 已备案,80/443 不被劫持)
八、排障速查
| 现象 | 原因 | 解决 |
| —— | —— | —— |
| 钉钉打开不自动登录 | 应用类型/权限 | JSAPI 免登需钉钉 JSAPI 安全域名;应用需在钉钉后台配置 |
| 免登报“不合法的appKey” | 凭证错 | .env CLIENT_ID(ding 开头)+ SECRET 匹配 |
| 通讯录同步空 | 应用未开通通讯录权限 | 钉钉后台开通 qyapi_get_department_list/member;或 MCP 同步 ding_org.json |
| 管理员变“只读” | applyPerm 用 people.json roles(空) | isAdmin 已改账户体系判定(名字=武燕青) |
| 普通用户看到全部任务 | view_own_only 未生效 | accounts.json perms;后端 is_mine 过滤 |
| 任务匹配不上 | owner 简称 vs 全名 | name_variants 变体匹配 |
| 库房 tabbar 跑中间 | nav 标签选择器覆盖 | tabbar 用 div 类(不用 nav) |
| 库房内容空白 | 登录校验覆盖 body | 已改提示条(不覆盖) |
| 页面旧版 | 浏览器缓存 | 强刷 / 加 ?v= 参数;服务端 no-store |
附三:新增任务权限调整 + /api/people 角色来源修复(2026-09-16)
问题:刘瑞红“新建了任务但没显示” → 排查发现数据里根本没有她建的任务。
根因: 1. `accounts.json` 的 `roles.admin` 只有「武燕青」→ 刘瑞红是普通用户 → 前端 `applyPerm()` 执行 `['btnAdd','btnPeople'] 隐藏` → 她看不到「+ 新增任务」按钮,建不了任务 2. ★ 更深一层:`/api/people` 的 `roles` 读的是 people.json(其 `roles.admin` 为空),而后端权限判定用 accounts.json → 前端 ADMINS 永远为空 → 即便把某人加入 accounts.json 的 admin,前端仍按普通用户处理(双侧数据源不一致的隐蔽 bug)
修复: - `accounts.json`:`roles.admin` 增加「刘瑞红」(从 user 列表移除) - 前端 `rd_h5.html`:
- `applyPerm()` 不再隐藏 `btnAdd`(新增任务对全员放开);`btnPeople`(人员管理)仍限管理员
- `openAdd()` 负责人选择器:非管理员默认预选自己(`_defOwn=[myShort()]`),避免建完因筛选自己而看不到
- 后端 `/api/people`:`roles` 改为 `load_accounts()` 读取(与权限判定统一)
验证:`/api/people` → admin=`['武燕青','刘瑞红']` ✅|`/api/me?user=刘瑞红` → role=admin + perms 全开 ✅|叶小军 → user(edit_own/view_own_only 保持)✅|页面 JS 语法 ✅
⚠️ 教训:权限相关的数据源必须唯一(accounts.json)。前端拿角色的接口(/api/people)与后端判权的函数(is_admin)读同一份文件,否则会出现“加了管理员但前端不认”的隐蔽问题。
附四:新建人显示 + 期望完成时间 + 普通用户硬隔离(2026-09-16)
① 任务显示新建人(`creator` 字段,存全名) - 后端 `add_task` 写入 `creator`(取 `creator`/`user` 参数);`update_task` 仅在库中为空时补写(不覆盖) - 前端:任务卡片(跟踪 + 临时)显示 `👤 新建:XXX`;详情页显示 `👤 新建:XXX(创建日期)`;只读详情同样显示 - 新增任务提交时带 `creator: USER_NAME`
② 期望完成时间(`due_date`,YYYY-MM-DD) - 后端 add/update 支持该字段 - 新增任务表单:跟踪任务增加「期望完成」日期选择(可选) - 详情页:可编辑(`#mDue`),保存随 `saveTask` 提交 - 卡片显示:`🎯 期望完成:2026-09-20`;已逾期(due_date < 今天且未完成)标红并附「(已逾期)」
③ 普通用户可见性(2026-09-16 定稿)
规则演进:① 最初「自己负责 或 自己创建」→ ② 用户一度要求收紧为仅负责人 → ③ 最终定稿:创建人可见自己创建的所有任务,其他用户(既非负责人也非创建人)不可见
- 全局开关 `MY_ONLY`:非管理员登录时置 `true` - `baseFilter()` 首行强制过滤:`if(MY_ONLY && !(isMine(t) || isCreatorOf(t))) return false`(不受人员筛选/搜索影响) - `isCreatorOf(t)`:`t.creator` 与登录人全名/简称双向匹配 - 分组:`XXX 主负责` | `XXX 我创建(他人负责)`(绿色 #26a69a)| `XXX 负责子任务` - 权限:`canEditTask` = `isMine` 或 `isCreatorOf`;后端 `can_edit` 同步 = `is_mine or is_creator` - 后端 `is_creator(task,user)` 用 `name_variants` 做全名/简称匹配 - ★ 后端 `/api/tasks` 同步过滤(根本保证):`if user and not is_admin(user) and perm.view_own_only: items=[it for it in items if is_mine(it,user) or is_creator(it,user)]`
- 前端 `MY_ONLY` 是展示层过滤,后端过滤才是根本保障(改前端 JS 也无法拿到他人任务)
- 未登录 / 管理员 → 返回全量(只读看板 `?ro=1` 需要)
验证(#99 金穗S1单loraPCB板设计:owners=['张'] creator=武燕青)——后端接口实测:
| 身份 | 可见任务数 | #99 |
| — | — | — |
| 张景茹(负责人) | 35 | 可见 ✅ |
| 武燕青(管理员/创建人) | 99 | 可见 ✅ |
| 席江锋(其他普通用户) | 20 | 不可见 ✅ |
| 叶小军(无任务) | 0 | 不可见(无任务)✅ |
| 未登录(只读看板) | 99 | 可见 ✅ |
⚠️ 测试提醒:API 测试勿传空值字段(如 `name:''`)——update_task 会按字段更新导致清空;本次已发现并恢复 #99 名称。
附四补:分组判定 bug 修复(2026-09-16,用户截图报)
现象:管理员(武燕青)在页面上点选「潘保琰」筛选后,分组 “潘保琰 我创建(他人负责)” 里出现了 #99 金穗S1单loraPCB板设计(该任务实际 creator=武燕青、owners=['张'],与潘保琰无关)。
根因:`render()` 里 `made` 分组用的是 `isCreatorOf(t)` —— 它判定的是 当前登录人是否创建,而不是被筛选人(SELP)。 → 管理员筛他人时,会把管理员自己创建的任务错列到被筛人名下的“我创建(他人负责)”分组。
修复:新增 `isCreatorBy(t, who)`(按指定人员的简称/全名判定),`made` 分组改用 `isCreatorBy(t, SELP)`:
function isCreatorBy(t,who){
var c=(t.creator||'').trim(); if(!c) return false;
if(c===who) return true; // 简称相等
if(full(who)===c) return true; // 全名相等
return nameToShort(c)===who; // creator 全名 → 简称后比较
}
var made=list.filter(function(t){return liu含SELP为假 && 子任务不含SELP && isCreatorBy(t,SELP)});
验证(node 单测):筛选「潘保琰」→ #99 不进入 made 分组 ✅|筛选「武燕青」→ 进入 ✅(正确)
附五:任务主页分组折叠(2026-09-16)
需求:任务主页每个类别(分组)折叠显示。
实现(`rd_h5.html`): - 新增 `gkey/isCol/toggleGroup/gcolMap` 四个辅助函数
- `gkey(title)`:分组名转 key(非字母数字中文→`_`)
- `isCol(k)`:默认收起(未记录视为 true=收起)
- `toggleGroup(k)`:切换并写 `localStorage.rd_gcol`(`false`=展开,`true`/未记录=收起)后 `render()`
- `render()` 中每个 `.group` 的 `.ghead`:加 ▸(收起)/ ▾(展开)指示 + 点击展开/收起;组内容仅在展开时渲染 - 覆盖:各负责人分组 + ✅ 已完成分组(key `done`)
验证(只读链接 ?ro=1 免登录实测): - 首次加载:7 个组全部 `▸` 收起(刘瑞红2|宋逊文2|席江锋13|张景茹9|武燕青19|潘保琰8|✅已完成45)✅ - 点击“刘瑞红”:变 `▾` + 显示 2 项任务,其余组仍收起 ✅ - `localStorage.rd_gcol = {“刘瑞红”:false}` 状态记忆正确 ✅
九、测试隔离
- 任务:测试账号不写生产 rd_tasks.json(改前备份 git 可回滚) - 库房:`STORE_DB=/tmp/store_test.db STORE_PORT=13599 python rd_store.py` 独立实例,测完删库 - 严禁直接操作生产数据;测试数据即时清理
附:任务负责人被判只读修复(2026-09-15)
现象:张景茹(普通用户)打开自己负责的任务(如 #83),详情页显示“🔒 只读(仅管理员或任务负责人可编辑)”,无法编辑(用户报)。
根因(前端 `rd_h5.html`):
function canEditTask(t){
return (t.owner||'')===USER_NAME||(t.owners||[]).indexOf(USER_NAME)>=0; // ❌
}
- `USER_NAME` 是全名(“张景茹”),而任务 `owners` 存的是简称(“张”)→ 永不匹配 → 负责人被判只读 - ⚠️ 后端 `is_mine()` 用的是 `name_variants()`(全名+简称变体)✅ 本就正确 → 纯前端 bug
修复(新增两个函数 + 替换 3 处调用):
function nameToShort(nm){ /* OMAP 反查全名→简称,不依赖 DING_USER */ }
function isMine(t){ /* owners/owner/子任务负责 与 [全名, 简称] 双向匹配 */ }
function canEditTask(t){ ... return isMine(t); }
// 另 2 处"我的任务"统计:TASKS.filter(isMine).length
验证: - node 单元测试 5/5:`owners:['张']` + 登录“张景茹” → true ✅|武燕青任务 → false ✅|子任务负责 → true ✅|无负责人 → false ✅|全名存储兼容 → true ✅ - 后端 `name_variants` 正确;`accounts.json` 中张景茹 `edit_own:true` ✅ - 三模块 200 ✅
经验:任务负责人存简称、登录用全名——任何“是否是我的”判断都必须走 `isMine()/nameToShort()`,禁止直接拿 `USER_NAME` 与 `owners` 比较。
附二:生成日报 500 修复(2026-09-15 晚)
现象:点「📋 生成日报」弹窗卡在“正在生成…”(用户报)。
根因: 1. 后端 500:`rd_h5.py` 日报聚合里,子任务负责人“今日无更新但任务今日完成”时直接 `per[so][“done_today”] += 1`,而 `per` 中该键尚未初始化 → `KeyError: '刘'`(刘瑞红为子任务负责人) 2. 前端卡住:`genReportDo()` 的 fetch 无 `.catch()` → 接口返回 HTML(500) 时 `r.json()` 抛错,弹窗永远停在“正在生成…”
修复: - 后端:`if done_today:` 前补 `per_init(so)` - 前端:`genReportDo()` 增加 `.catch()` → 显示“⚠️ 生成失败:…”+关闭按钮
验证:`/api/report/today` 200(1096 字符,含负责人/工时/星级)|周报接口本就正常(已有 catch)|三模块 200
经验:① 聚合 per[键] 累加前一律先 `per_init` ② 所有 fetch 必须带 `.catch()`(否则失败时 UI 卡死无提示)
