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 会导致关闭按钮失效)
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:任务/库房/测试/评审会/我的) - 会议:新建/继承(复制全部内容+议题自动续序号)/项目/开始时间/预计时长/结束时间(自动算实际)/会议人员(钉钉多选)/负责人/评审阶段(立项评审~结项)/标签 - 要点记录:序号/时间/记录人员(钉钉)/内容/优化方案/记录负责人(钉钉)/状态(☐未解决☑已解决复选框同行)/拍照+图库双传/附件 - 评审结果三方案:方案评审通过 / 方案局部修改无需再审 / 方案重大问题需二次评审 - 报告:网页版 + 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 名称。
附五:任务主页分组折叠(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 卡死无提示)
