This is an old revision of the document!
Table of Contents
家庭管理系统 H5 · 冷启动文件
版本 1.0 | 更新 2026-09-16 | 访问 `https://rd.huiflytech.com/family/`
与研发任务/库房/测试/评审会同一服务(`rd-h5.service`,Flask :13544)
4.x 果果工资模块(2026-09-17 新增,仅管理员)
需求:记录请假(类型/天数)与加班(带睡/日班),自动计算工资(参考用户提供的 Excel 工资表)。
① 数据表 `salary_month`(family.db)
| 字段 | 说明 |
| — | — |
| month | 月份(2026-08) |
| base / days_per_month / day_rate | 月工资 / 计薪天数(默认 26)/ 日工资(0=自动=base÷dpm) |
| sleep_rate | 带睡单价(默认 100 元/晚) |
| leave_items | 请假明细 JSON `[{type,days,note}]`(病假/事假/调休/其他) |
| ot_items | 加班明细 JSON `[{type,days,note}]`(带睡/日班/其他) |
| leave_days / leave_deduct | 请假合计天数 / 扣费 |
| ot_days_sleep / ot_pay_sleep | 加班天数(带睡) / 加班费(带睡) |
| ot_days_day / ot_pay_day | 加班天数(日班) / 加班费(日班) |
| total / actual / paid / note | 月总薪水(自动)/ 实际薪水(可手填覆盖)/ 是否发放 / 备注 |
② 计算规则(后端 `_calc_salary_month()` 权威计算 + 前端实时预览)
日工资 = 月工资 ÷ 计薪天数 (5000 ÷ 26 = 192.31) 请假扣费 = 请假天数 × 日工资 (2 天 × 192.31 = 384.62) 加班费(带睡) = 带睡晚数 × 带睡单价 (3 晚 × 100 = 300) 加班费(日班) = 日班天数 × 日工资 (1 天 × 192.31 = 192.31) 月总薪水 = 月工资 + 加班费 − 请假扣费 实际薪水 = 手填(留空则取月总薪水)★ 最终发放金额以手填为准
③ 接口(`family_api.py`) - `GET /api/family/salary_month` → 月度列表(按月份倒序) - `POST /api/family/salary_month` → 新增/更新(带 id 即更新);仅管理员(`_is_admin_user()`) - `DELETE /api/family/salary_month` → 删除 - ⚠️ 权限守卫是全局 `@bp.before_request def _guard()`(登录校验);新增管理员判定函数命名为 `_is_admin_user`(★ 不能叫 `_guard`,会覆盖全局守卫)
④ 前端(`family.html`) - 新 tab「💰 果果工资」→ 仅管理员(加入 `ALL_TABS`,不入 `USER_TABS`) - 列表:月份 | 月工资 | 请假(天) | 扣费 | 加班费 | 实发 | 操作(编辑/删)+ 备注行 - 表单:月份 / 月工资 / 计薪天数(自动显示日工资)/ 带睡单价 + 请假明细(可增删行) + 加班明细(可增删行,单位自动切换 晚/天) + 实际薪水(留空=自动)+ 备注 + 实时计算预览框 - ★ 关键实现细节:`salPaint()` 只更新“日工资/单位/计算框”,绝不重建输入行(否则用户输入到一半的值会被清空 —— 曾出现此 bug,拆出 `salRows()` 专责渲染明细行);增删行前用 `salSync()` 记住当前表单其它字段
⑤ 权限判定(2026-09-17 修复踩坑) - ❌ 现象:管理员(武燕青)新增工资 → 提示「仅管理员可操作」 - 根因:后端只按 `d.get(“user”)`(requests body 字段)判定,而前端 `post()` 只注入了 `phone`,未传 `user` → 判定为空 → 403 - ✅ 修复:新增 `_is_admin_req(d)` —— 优先按登录态 `cur_user(conn).role == 'admin'`(phone 是权威登录凭证)判定;其次才兼容传入的 user 名(accounts.json admin 列表);`DELETE` 同样加校验;前端 `salSave` 的 body 也补上 `user/phone`(双保险) - 验证:管理员保存 → 成功(实发自动计算)✅|普通用户(果果 13780562913)→ 403 ✅
⑥ 验证 - 与用户图片逐项核对:日工资 192.31 ✅|3月 请假2天 → 扣 384.62 ✅|带睡 2 晚 = 200 ✅|4月 日班 1 天 = 192.31 ✅ - 前端实测:日工资自动显示 ✅|请假 2 天 → 扣 384.62 ✅|带睡 3 晚 = 300 ✅|月总薪水 = 5000+300−384.62 = 4915.38 ✅|输入过程中值不丢失 ✅ - 非管理员访问 → 403 / 不可见 tab ✅
1. 系统概览
| 项 | 值 |
| — | — |
| 后端 | `/home/ubuntu/.hermes/scripts/family_api.py`(Blueprint `bp`,前缀 `/api/family`) |
| 前端 | `/home/ubuntu/.hermes/scripts/family.html`(页面 `/family/`) |
| 数据 | `/home/ubuntu/docs/family/family.db`(附件目录 `attachments/`) |
| 管理员白名单 | `ADMIN_PHONES = {“18932573927”}`(武燕青) |
| 现有用户 | 18932573927 武燕青(admin) | 16603395188 倾心(user) | 13780562913 果果(user) |
| 启动 | `sudo systemctl restart rd-h5.service`(改代码后必须重启) |
2. 模块与权限
全部 tab:🏠家务 | ⭐积分 | 💬语录 | 🎂生日 | 🍳菜谱 | 💰缴费 | ⚠️预警 | 📋信息 | 📜日志 | 👤我的
普通用户可见(`USER_TABS`):仅 `house / points / warn / me`
权限网关 `_guard()`(`@bp.before_request`): - 未登录 → 401 - `role == 'admin'` → 全部放行 - 普通用户 → 仅白名单路径(tasks GET/PATCH、tasks/refresh、points、warns、me、log、user/*),其余 403 - ★ 新增的管理员模块路径不在白名单 → 天然仅管理员可用
3. 信息模块(2026-09-16 新增,仅管理员)
用途:家庭档案 / 学籍等敏感信息集中存储与展示。
表结构
CREATE TABLE IF NOT EXISTS info( key TEXT PRIMARY KEY, val TEXT DEFAULT '', updated TEXT DEFAULT '');
(`val` 存 JSON 字符串)
API
| 方法 | 路径 | 说明 |
| — | — | — |
| GET | `/api/family/info` | 返回 `{ok, data:{key: {…}}}`;未登录 401 / 普通用户 403 |
| POST | `/api/family/info` | body `{data:{key:{…}}}`,`INSERT OR REPLACE` 保存 |
身份传递:`cur_user()` 从 `phone` 参数读取(query / JSON body / form)——测试时可直接 `?phone=18932573927`
前端:`ALL_TABS` 含 `['info','📋 信息']`,不入 `USER_TABS` → 普通用户看不到入口;`renderInfo()` 渲染卡片(主体信息 `INFO_FIELDS` + `__lists` 描述的成员列表)
数据结构(学籍示例)
{
"xueji_xizien": {
"__title": "席子恩 学籍信息",
"姓名": "…", "性别": "…", "身份证件号": "…", "出生日期": "…",
"__lists": [{"key": "监护人", "title": "监护人信息", "fields": ["姓名","关系","是否监护人","民族","身份证件类型","身份证件号","工作单位","户口所在地行政编码","户口所在地具体地址","联系电话"]}],
"监护人": [{"姓名": "…", "关系": "母亲", "联系电话": "…"}, {"姓名": "…", "关系": "父亲", "联系电话": "…"}]
}
}
已录入:
| key | 内容 | 说明 |
| — | — | — |
| `xueji_xizien` | 席子恩学籍(学生 19 字段 + 2 位监护人各 10 字段) | 卡片折叠显示(点标题展开) |
| `id_cards` | 身份证记录(7 条:姓名 + 身份证号) | 卡片折叠显示;支持新增/删除(表格下方「+ 新增」输入姓名+号码) |
前端渲染规则(`renderInfo()`) - 每个 key 渲染为 `<details class=“card”>`(默认收起,summary 前缀 ▸/▾ 随展开状态切换) - `type === 'idlist'` → 渲染为「姓名 | 身份证号 | ✕删除」表格 + 新增输入行(`addIdRec` / `delIdRec`) - 其他 → 按 `INFO_FIELDS` 渲染主体字段,按 `lists` 渲染成员列表(如监护人) - 保存:读取当前节点 → 修改 → 整节点 `POST`(`saveInfoKey`)
⚠️ 文档中不记录具体证件号码(数据仅存本地 `docs/family/family.db`,站点 HTTPS,接口仅管理员)
4. 学籍字段清单
学生(19 项):姓名 | 曾用名 | 性别 | 国籍/地区 | 港澳台侨外 | 民族 | 身份证件类型 | 身份证件号 | 出生日期 | 政治面貌 | 现居住地行政编码 | 现居住地址详细地址 | 户口所在地行政编码 | 户口所在地具体地址 | 是否寄宿生 | 是否残疾人 | 是否农村留守儿童 | 残疾类型 | 特教学生就读类型
监护人(10 项/人):姓名 | 关系 | 是否监护人 | 民族 | 身份证件类型 | 身份证件号 | 工作单位 | 户口所在地行政编码 | 户口所在地具体地址 | 联系电话
4.y 积分管理「扣分」人员下拉为空修复(2026-09-17)
现象:积分管理 → 「-扣分」弹窗里人员下拉是空的。
根因(原有 bug,自扣分功能上线起即存在)
Promise.all([api('.../points/members'), api('.../points/rules')]).then(function(a){
var mem = a.members || []; // ❌ a 是数组,a.members 恒为 undefined → mem 始终为空
CACHE.deductRules = (a[1].rules||[])...
→ 接口其实正常返回人员(席子初/席子恩),只是前端取错了返回结构。
修复
var mem = (a[0]||{}).members || [];
CACHE.deductRules = ((a[1]||{}).rules||[]).filter(r => (r.kind==='扣分'));
验证:扣分弹窗人员下拉 = 席子初 / 席子恩 ✅|加分弹窗同样正常 ✅(加分/兑换的写法本就正确 `a[0]/a[1]`,仅扣分这处写错)|无 JS 错误 ✅
★ 教训:`Promise.all([…])` 的回调参数是数组,必须用 `a[0] / a[1]` 取,不能按对象字段取。
4.z 家务计划:去重 + 永久有效(2026-09-17)
用户需求:① 删除重复标题的任务;② 去掉截止日期(默认永久有效);③ 去掉「永久有效」选项。
① 数据去重(`family.db` → `tasks` 表,删前已备份 `/tmp/family_before_dedup.db`)
| 重复标题 | 份数 | 处理 |
| — | — | — |
| 加湿器加满水 | 14 | 保留 #24,删除 13 条 |
| 小恩课后作业的辅导与完成 | 19 | 保留 #26,删除 18 条 |
- 共删除 31 条 → 任务数 49 → 18 - 规则:同标题保留 id 最小(最早条目),其余删除
② 截止日期 → 永久有效 - 全部 18 条 `due_date` 统一置为 `永久` - 后端 `_overdue()` 已有 try/except 兜底(`fromisoformat('永久')` 抛异常 → 返回 0),无需改动 ✅
③ 前端移除(`family.html`) - 列表:去掉「截止 XXX」显示(`_dueTxt` 置空) - 编辑表单 / 新增表单:移除「截止日期」输入框 与「永久有效」复选框(及 `foreverChg/etForeverChg` 函数),改为一行提示「(家务任务均为永久有效,无需设置截止日期)」 - 提交时 `due_date` 固定传 `'永久'` - 残留变量(`et_due/t_due/et_forever/t_forever`)已清理,无 JS 报错 ✅
验证:家务页面显示 18 条 ✅|列表无「截止」字样 ✅|新增/编辑表单无截止日期与永久有效 ✅|无 JS 错误 ✅
📌 待确认:现有两条语义相近但标题不同 —— #7「小恩课后作业辅导与完成」与 #26「小恩课后作业的辅导与完成」(仅多一个「的」),是否也需要合并?
4.w 果果工资:全勤/实发跟随修正(2026-09-17)
用户反馈:工资计算不对 —— 全勤就是 5000 的工资。
现象:2026-06、2026-09 两个月请假 0 天(全勤),但「实发」显示 4807.69(少了 192.31)❌ - 根因:`actual`(实际薪水)字段被旧的手填值占用 —— 用户曾填过 1 天请假(自动算出 4807.69),后来把请假改成 0 天,但 `actual` 仍是旧值 4807.69 ❌
修复
| 位置 | 改动 |
| — | — |
| 数据 | 2026-06、2026-09 的 `actual` 修正为 = `total`(5000)✅ |
| 前端 | 新增 `_actualPrefill(m)`:若已存 `actual` 与当时 `total` 相同(说明是自动值)→ 编辑时不预填,跟随新的自动计算结果;只有用户确实手填过(两者不同)才保留预填 ✅ |
当前数据(已核对)
| 月份 | 月工资 | 请假 | 扣费 | 加班费 | 实发 |
| — | — | — | — | — | — |
| 2026-09 | 5000 | 0天 | -0.00 | +0.00 | 5000.00 ✅ 全勤 |
| 2026-07 | 5000 | 1天 | -192.31 | +0.00 | 4807.69 |
| 2026-06 | 5000 | 0天 | -0.00 | +0.00 | 5000.00 ✅ 全勤 |
| 2026-05 | 5000 | 2.5天 | -480.77 | +492.31 | 5011.54 |
| 2026-04 | 5000 | 2天 | -384.62 | +100.00 | 4715.38 |
| 2026-03 | 5000 | 11天 | -2115.38 | +200.00 | 3084.62 |
⚠️ 待用户确认:2026-03 请假 11 天 是否为误填?(对照用户提供的 Excel 图,3月应为病假 2 天、扣 384.62、实发 5000)—— 可能是把“11 号请假”填成了“11 天”。
4.v 生日模块:普通用户不能保存 + tab 不可见(2026-09-25)
现象:点「+ 新增生日」→ 保存不了(普通用户)。
根因(两处,都在权限层) 1. 后端全局守卫 `@bp.before_request _guard()` 的普通用户白名单里没有 `/api/family/birthdays` → 普通用户 POST 被 403「无权限(普通用户仅可使用家务/预警/积分)」 2. 前端 `USER_TABS` 只有 `[“house”,“points”,“warn”,“me”]` → 普通用户连「🎂 生日」tab 都看不到
修复
| 位置 | 改动 |
| — | — |
| `family_api.py` `_guard()` | 白名单加 `if path.startswith(“/api/family/birthdays”) and m in (“GET”,“POST”,“DELETE”,“PATCH”): return None`;错误文案同步为「…家务/预警/积分/生日」 |
| `family.html` `USER_TABS` | 改为 `[“house”,“points”,“warn”,“bday”,“me”]`(生日对家人开放) |
验证 - 管理员保存 ✅(本来正常) - 普通用户(果果 13780562913)保存 → HTTP 200 ok ✅(修复前 403) - 浏览器实测(果果身份):可见 tab 含「🎂 生日」✅ → 新增 → 弹窗关闭 + 列表出现新记录 ✅|无 JS 错误 ✅
同时补充的数据 - 杨创伟(堂弟)→ 农历八月十八 ✅ - 果果(家庭老师)→ 生日仍缺 ❌(待用户提供)
4.51 家务定期任务:启用/禁用开关(2026-10-10)
需求:家务模块的定期任务增加开关按钮 —— - 启用:该任务按设置的定期时间自动列入未完成家务 - 禁用:不按定期时间自动列入 - 按钮点一次切换一次(启用 → 显示「禁用」;禁用 → 显示「启用」)
后端(`family_api.py` + `family.db`)
| 位置 | 改动 |
| — | — |
| `tasks` 表 | 新增字段 `enabled INTEGER DEFAULT 1`(1=启用 / 0=禁用) |
| `/api/family/tasks/refresh` 定期重启查询 | 加 `AND COALESCE(enabled,1)=1` |
| `/api/family/tasks/refresh` 自动生成查询 | 加 `AND COALESCE(enabled,1)=1` |
| 重复生成查重查询 | 加 `AND COALESCE(enabled,1)=1` |
| `PATCH /api/family/tasks/<id>` 白名单 | 加 `“enabled”`(★普通用户也可切换,`allow_keys` 非管理员分支放行 `enabled`) |
前端(`family.html`)
- 状态标记:定期任务的标题行显示 `▶ 启用中`(绿底)或 `⏸ 已禁用`(灰底) - 切换按钮:定期任务的按钮行新增 `⏸ 禁用` / `▶ 启用`(临时任务不显示) - 新增 `toggleTaskEnabled(id, to)`:PATCH 提交 `enabled`,成功后 toast 提示并 `renderPage()`
验证
| 项 | 结果 |
| — | — |
| SQL 逻辑 | 定期重启查询 & 自动生成查询:启用入选 / 禁用排除 ✅;对照旧查询(无条件)两者都命中,证明过滤生效 ✅ |
| 前端渲染 | 34 个任务(定期 16 / 临时 18);16 个定期任务全部有按钮与标记 ✅;临时任务 0 个误加 ✅ |
| 页面截图 | 定期任务显示「定期 · 每1天 · ▶ 启用中」+「⏸ 禁用」按钮;临时任务无该按钮 ✅ |
| 数据 | 现有 16 个定期任务默认全部 `enabled=1`(启用中);测试任务已清理 ✅ |
| 回归 | 其他模块未受影响 ✅;无 JS 错误 ✅ |
说明:禁用仅停止自动列入/重启,已存在的任务实例不受影响(可手动完成或删除)。
4.52 家务启用/禁用功能仅管理员可见(2026-10-10)
需求:家务的启用/禁用功能,普通用户不可见。
前端(`family.html`) - 管理员判定改为运行时函数(原用加载时常量 → USER 是异步设置,导致管理员也判为 false 的坑):
function isAdminNow(){ try{ return !!(USER && USER.role==='admin'); }catch(e){ return false; } }
- 定期任务的 `⏸ 禁用` / `▶ 启用` 按钮 与 `▶ 启用中 / ⏸ 已禁用` 状态标记 均加 `&& isAdminNow()` 条件 → 普通用户完全看不到
后端(`family_api.py`) - `PATCH /api/family/tasks/<id>` 的 `allow_keys` 非管理员分支恢复为 `(“status”,)` → 收回普通用户的 `enabled` 修改权限(防绕过前端)
验证(★关键:管理员与普通用户分别实测)
| 身份 | 结果 |
| — | — |
| 管理员 武燕青(role=admin) | 页面出现「⏸ 禁用」按钮 + 「▶ 启用中」标记 ✅(定期任务有,临时任务没有) |
| 普通用户 果果(role=user) | 「⏸ 禁用」按钮 0 个、启用/禁用标记 0 个、`toggleTaskEnabled` 0 处 ✅;家务功能正常(34 张卡片、定期标签、状态按钮都在)✅ |
★ 踩坑记录:管理员判定不能用页面加载时常量。`USER` 由异步请求设置,加载瞬间为空 → `IS_ADMIN` 恒为 false → 管理员也看不到按钮。改为每次渲染时调用 `isAdminNow()` 后正常。
4.53 家务:操作已完成任务后不自动折叠(2026-10-10)
需求:在操作/修改已完成任务时,操作完成后不要自动折叠(否则刚改的任务立刻被收进折叠区,看不到)。
实现(`family.html`) - 新增全局标志 `window.keepOpen` - 置位点(4 个操作入口): | 函数 | 场景 | |—|—| | `setTask(id,st)` | 点「未开始/进行中/已完成」状态按钮 | | `saveTaskEdit(id)` | 编辑并保存任务 | | `saveTask()` | 新增/保存任务 | | `toggleTaskEnabled(id,to)` | 定期任务启用/禁用 | - 消费点:`renderHouse()` 渲染「📦 已完成」区时 <code> var _doneDisp=(window.keepOpen?'':'none'); window.__keepOpen=false;
h+='… <div style="display:'+_doneDisp+'">';
</code>
→ 刚操作过则**展开**,随后立即重置标志(下次进入恢复默认折叠)
验证
| 场景 | 结果 |
| — | — |
| 进入页面(未操作) | 已完成区 `display:none` → 默认折叠 ✅(原行为不变) |
| 置位后触发渲染 | 已完成区 `display:““` → 保持展开 ✅ |
| 渲染后 | `__keepOpen` 自动重置为 false ✅ |
4.54 家务:棕色按钮「启用」文案改为「安排」(2026-10-10)
需求:家务中把棕色的启用按钮的”启用”改成“安排“。
定位:`family.html` 任务卡片按钮行中,已完成任务显示的那个棕色按钮(`background:#6d4c41`):
<button class="btn sm" style="background:#6d4c41" onclick="setTask(id,'未开始')">🔄 安排</button>
(原为 `🔄 启用` —— 点它把已完成任务重新排回「未开始」)
改动
| 位置 | 改动 |
| — | — |
| 棕色按钮文案 | `🔄 启用` → `🔄 安排` |
| 灰色切换按钮(定期任务启用/禁用) | 保持 `▶ 启用` / `⏸ 禁用`(★此前误改,已回退) |
⚠️ 踩坑:改代码时把 `// 注释` 写在了拼接表达式的续行 `+` 之前,导致续行加号被注释掉 → 表达式断裂 → 页面卡在「加载中…」(接口全 200 但渲染中断)。
★ 教训:JS 字符串拼接中的行尾注释必须写在 `+` 之后(或直接不加注释)。
验证
| 项 | 结果 |
| — | — |
| 「🔄 安排」按钮 | 29 个(= 已完成任务数)✅ |
| 「🔄 启用」残留 | 0 个 ✅ |
| 灰色切换按钮 | `▶ 启用` 3 个 / `⏸ 禁用` 14 个(不受影响)✅ |
| 页面加载 | ✅ 正常(不再卡「加载中」) |
4.55 家庭管理:登录态永久保持(localStorage + cookie 双写)(2026-10-10)
需求:家庭管理 18932573927 的登录状态永久保持(不用反复登录)。
背景:原登录态只存 `localStorage.family_user`(本身无过期),但微信内置浏览器会不定期清理 localStorage → 用户被迫重新登录。
实现(`family.html`) 新增统一登录态管理(cookie 有效期 10 年,`SameSite=Lax`,`path=/`):
| 函数 | 作用 |
| — | — |
| `saveUser(u)` | 写入:`localStorage` + cookie 双份 |
| `loadUser()` | 启动恢复:localStorage 优先 → cookie 兜底(若从 cookie 恢复,顺带回写 localStorage) |
| `clearUser()` | 退出登录:双清(两处都要清,防残留) |
| `_ckSet/_ckGet/_ckDel` | cookie 读写删(带过期时间) |
接入点 - 登录写入:3 处(验证码登录 / 密码登录 / 切换账号)→ 统一改 `saveUser(d.user)` - 启动恢复:`(function(){ USER=loadUser(); … })()` - 退出登录:`logout()` → `clearUser()`
验证
| 场景 | 结果 |
| — | — |
| 登录后 | localStorage ✅ + cookie ✅(cookie 含 10 年过期) |
| 清除 localStorage(模拟微信清理) | cookie 仍在 ✅,`loadUser()` 成功恢复为「武燕青」✅ |
| 退出登录(含 cookie 清除) | `loadUser()` 返回 null ✅(需重新登录,符合预期) |
说明:cookie 与 localStorage 同域,属于同一浏览器范围内持久化;只要不手动「退出登录」、不清除浏览器数据,即可长期免登录。
5. 排障速查
| 现象 | 原因 | 处理 |
| — | — | — |
| API 返回 401 | 未登录(请求未带 `phone`) | 带 `?phone=<手机号>` |
| API 返回 403 | 普通用户访问管理员模块 | 正常(预期行为) |
| `info` tab 不显示 | 当前用户非管理员 | 正常 |
| `/family/` 打开后显示原始 JSON | ★ 插入代码时把 `@bp.route(”/family/”)` 装饰器与 `def family_page()` 分开 → 装饰器错位挂到了新函数上 | 确保装饰器紧贴其函数(新增函数时要插在注释块之后,别插在装饰器与函数之间) |
| 服务起不来(activating/失败) | 路由用了 `@app.route` | 必须用 `@bp.route`(family_api 用 Blueprint) |
| 页面改动不生效 | 服务未重启 | `sudo systemctl restart rd-h5.service` |
6. 安全注意
- 信息模块含身份证号、住址、电话等敏感信息 → 仅管理员可见(前后端双重限制:前端 tab 不显示 + 后端 `_guard` 403) - 文档中不记录具体证件号码;数据仅存本地 SQLite(`docs/family/family.db`,权限 600) - 站点为 HTTPS(`rd.huiflytech.com`)
