日工资 = 月工资 ÷ 计薪天数 (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 渲染为 `
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/
function isAdminNow(){ try{ return !!(USER && USER.role==='admin'); }catch(e){ return false; } }
- 定期任务的 **`⏸ 禁用` / `▶ 启用` 按钮** 与 **`▶ 启用中 / ⏸ 已禁用` 状态标记** 均加 `&& isAdminNow()` 条件 → 普通用户完全看不到
**后端(`family_api.py`)**
- `PATCH /api/family/tasks/
var _doneDisp=(window.__keepOpen?'':'none'); window.__keepOpen=false;
h+='… ';
→ 刚操作过则**展开**,随后立即重置标志(下次进入恢复默认折叠)
**验证**
| 场景 | 结果 |
|---|---|
| 进入页面(未操作)| 已完成区 `display:none` → **默认折叠** ✅(原行为不变)|
| 置位后触发渲染 | 已完成区 `display:""` → **保持展开** ✅ |
| 渲染后 | `__keepOpen` 自动重置为 false ✅ |
====== 4.54 家务:棕色按钮「启用」文案改为「安排」(2026-10-10) ======
**需求**:家务中把**棕色的启用按钮**的"启用"改成"**安排**"。
**定位**:`family.html` 任务卡片按钮行中,已完成任务显示的那个棕色按钮(`background:#6d4c41`):
(原为 `🔄 启用` —— 点它把已完成任务重新排回「未开始」)
**改动**
| 位置 | 改动 |
|---|---|
| 棕色按钮文案 | `🔄 启用` → **`🔄 安排`** |
| 灰色切换按钮(定期任务启用/禁用)| 保持 `▶ 启用` / `⏸ 禁用`(★此前误改,已回退)|
> ⚠️ **踩坑**:改代码时把 `// 注释` 写在了**拼接表达式的续行 `+` 之前**,导致续行加号被注释掉 → 表达式断裂 → **页面卡在「加载中…」**(接口全 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 同域,属于**同一浏览器**范围内持久化;只要不手动「退出登录」、不清除浏览器数据,即可长期免登录。
====== 4.56 登录态持久化增强:三重存储(覆盖全部账号)(2026-10-10) ======
**需求**:18932573927(武燕青)与 **16603395188(倾心)** 的登录状态永久保持。
**说明**:登录态持久化是**通用机制**(与手机号无关),任何账号登录都会写入三处,因此**所有账号同时生效**:
| 账号 | 姓名 | 角色 |
|---|---|---|
| 18932573927 | 武燕青 | admin |
| 16603395188 | 倾心 | user |
| 13780562913 | 果果 | user |
**三重存储(按可靠性递进)**
| 层 | 介质 | 说明 |
|---|---|---|
| ① | `localStorage.family_user` | 常规读写,最快 |
| ② | **cookie** `family_user`(10 年有效,SameSite=Lax)| ★ 微信清理 localStorage 后仍可恢复(**已实测有效**)|
| ③ | **IndexedDB** `family_auth/u` | 额外保险;写入采用 `onupgradeneeded` + `onsuccess` 双写,规避首次建库时序问题 |
**恢复顺序**:`localStorage → cookie(同步)`,都无则 `IndexedDB(异步兜底)` → 任一成功即自动登录并回写其它层。
**验证**
| 场景 | 结果 |
|---|---|
| 登录(如 16603395188)| localStorage ✅ + cookie ✅ |
| **清除 localStorage**(模拟微信清理)| cookie 仍在 → `loadUser()` **成功恢复**为「倾心」✅ |
| 同时清除 localStorage + cookie | 交由 IndexedDB 兜底(★ 本测试浏览器沙箱限制 IndexedDB 未生效;真实手机浏览器一般可用;**前两重已足够**)|
| 手动「退出登录」| 三层全清 ✅(需重新登录,符合预期)|
**给用户的使用建议**:微信里把家庭管理页面**加入浮窗/收藏**,之后打开即可(无需重复登录)。
====== 4.57 果果工资对普通用户开放(只读)(2026-10-10) ======
**需求**:果果工资模块**对普通用户可见**,可查看每月工资详情,但**只读、不能编辑**。
**前端(`family.html`)**
| 位置 | 改动 |
|---|---|
| `USER_TABS` | 增加 `"salary"` → 普通用户可见「💰 果果工资」tab |
| 新增按钮 | `+ 新增月份工资` 仅 `isAdminNow()` 显示;普通用户改为提示「👀 仅可查看(如需修改请联系管理员)」|
| 操作列 | 「编辑 / 删」仅管理员显示,普通用户显示 `—` |
| `salEdit()` | 增加 `isAdminNow()` 保护(防绕过前端直接调用)|
**后端(`family_api.py`)**
- 权限白名单新增:
if path.startswith("/api/family/salary_month") and m == "GET": return None
if path.startswith("/api/family/salary") and m == "GET": return None
→ 普通用户**只能读**;`POST/DELETE` 依然 403
- 403 提示文案更新为「无权限(普通用户仅可使用家务/预警/积分/生日;工资仅可查看)」
**验证(普通用户用 16603395188 倾心实测)**
| 项 | 结果 |
|---|---|
| 可见 tab | 🏠家务 / ⭐积分 / 🎂生日 / ⚠️预警 / **💰果果工资** / 👤我的 ✅ |
| 数据 | 正常显示 **8 个月**,累计实发 ¥37719.23 ✅ |
| 新增按钮 | ✅ 已隐藏(显示只读提示)|
| 编辑 / 删 链接 | ✅ 已隐藏(操作列 `—`)|
| 后端 GET | **200** ✅(返回 8 个月)|
| 后端 POST | **403 无权限** ✅ |
| 管理员 | 增删改一切正常 ✅(验证用测试记录已清理)|
====== 4.58 新增「成长记录」模块(身高/体重/脚长)(2026-10-16) ======
**需求**:家庭管理增加模块,记录 **姓名 / 时间 / 身高 / 体重 / 脚长**;每个人按**固定时间间隔**记录一次。
===== 数据表(family.db) =====
CREATE TABLE growth(id INTEGER PK, name TEXT, date TEXT,
height REAL, weight REAL, foot REAL,
note TEXT DEFAULT '', created TEXT DEFAULT '');
CREATE TABLE growth_cfg(name TEXT PRIMARY KEY, days INTEGER DEFAULT 30, created TEXT DEFAULT '');
===== 接口(family_api.py) =====
| 接口 | 方法 | 说明 |
|---|---|---|
| `/api/family/growth` | GET | 返回 `records`(按日期倒序)+ `people`(每人:间隔天数/上次日期/距今天数/下次日期/超期天数/has)+ `cfg` |
| `/api/family/growth` | POST | 新增记录;**同一人同一天重复提交 → 覆盖**(不产生重复行);传 `id` 则更新该条 |
| `/api/family/growth?id=` | DELETE | 删除一条(仅管理员)|
| `/api/family/growth/cfg` | POST | 设置某人的记录间隔天数(默认 30)|
**权限**:普通用户 **GET 放行(只读)**,POST/DELETE/cfg **403 仅管理员**(`_guard` 白名单新增一行)。
===== 前端(family.html) =====
- 新增 tab **「📏 成长」**(`ALL_TABS` 管理员 / `USER_TABS` 家人只读)
- 每人一张卡片:`每 N 天` + 状态(⏳ 还没有记录 / ✅ 下次 YYYY-MM-DD / ⚠️ 已超期 N 天)+ 上次数据(身高/体重/脚长 + N 天前)
- 管理员卡片右侧:`⚙️ 间隔`(prompt 设置天数)、`+ 记录`;右下 FAB 也可新增
- 「历史数据」区:日期 + 姓名 + 身高/体重/脚长 + 备注,管理员可 ✕ 删除
- 新增表单(底部弹出 sheet):姓名下拉(来自 members)、日期(默认今天)、身高/体重/脚长(数字,至少填一项)、备注
===== 验证 =====
| 项 | 结果 |
|---|---|
| 建表 | ✅ growth / growth_cfg 自动创建 |
| GET(空库)| ✅ people 自动从 members 生成(席子初、席子恩),间隔默认 30 |
| POST 录入 | ✅ 返回 id;同人同日重复提交覆盖 |
| 状态计算 | ✅ 录入 2026-10-16 → 「2 天前」、下次 2026-11-15(30 天档)|
| 改间隔 | ✅ 席子恩设为 90 天 → 下次变 2027-01-14 |
| 权限 | 普通用户 GET **200** / POST **403** / DELETE **403** ✅ |
| 管理员视图 | ✅ 标题/两人卡片/数据/按钮/删除全部正常,无 JS 错误 |
| 普通用户视图 | ✅ tab 可见、数据可见、**无 新增/间隔/删除** 入口 |
| 收尾 | 测试数据已清理(growth 0 条、cfg 空,回到默认 30 天)|
⚠️ 注意:`family_api.py` 是 Blueprint,新增路由必须用 `@bp.route`。
====== 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`)