从一份民政部标准 PDF,到一套双端老年人能力评估系统

本文记录一次完整的小型业务系统开发:从解读行业标准开始,到 Web 管理端与手机端 H5 落地,再到适老化改造、备份恢复、Word 导出、品牌图标,以及过程中踩过的坑。

涉及电脑端 / 移动端 App 封装、正式投用前准备的内容尚未完成,留待明日,见文末「未完成清单」。


一、需求从一个 PDF 开始

项目的起点不是"做个后台管理系统",而是一份标准:《老年人能力评估》MZ/T 039—2013(民政部行业标准)。题目要求建两套系统——Web 管理端和手机端(App / 微信公众号),并且必须实现五件事:

  1. 老年人注册或者信息导入
  2. 老年人能力定期评估
  3. 能力评估数据多条件查询
  4. 能力数据统计
  5. 能力数据导出

这五条看起来像普通的 CRUD 清单,但"能力评估"四个字背后是一整套有法律效力的判定规则。真正的工作量,一半在界面,一半在这份 PDF。


二、第一个决定:把标准写成代码,而不是写进注释

标准的核心结构我拆成了这样:

  • 4 个一级指标:日常生活活动、精神状态、感知觉与沟通、社会参与
  • 22 个二级指标(附录 B 的制式量表)
  • 日常生活活动按 表 2 求和分级;其余三项按 表 3~表 5 的组合判定——注意是组合,不是简单求和,这是最容易实现错的地方
  • 表 6(附录 C.2)综合出初步等级,优先级 0 → 3 → 2 → 1
  • 附录 C.3 等级变更条款:跌倒/噎食/自杀/走失等要提级,昏迷直接重度;其中"已经是重度就不再提级"优先级最高,多条同时触发也只提一级,且封顶 3

我把这些规则全部放进 server/domain/mzt039.js,做成纯函数:gradeAdl、gradeMental、gradeSensory、gradeSocial、gradeOverall、evaluateChangeClauses、evaluate。业务代码只负责把 22 个选项传进去,拿到分级结果。

这样做的好处后来反复兑现:单元测试能直接对标准条款写断言(比如"已重度 + 触发条款 = 仍是重度"),而不用启动服务器或点界面。标准引擎成了整个系统里最不容易坏的一块。

经验:行业系统里,"业务规则"应该是一等公民,有自己独立的、可测试的模块。把它埋在接口或前端里,等于把最容易出错、最需要被审计的部分藏进了最难验证的地方。


三、技术上最"极端"的一个选择:零第三方依赖

整个项目没有 npm install,没有 package.json,没有任何第三方包。服务用 Node.js 内置的 node:sqlite(要求 Node ≥ 22.5,实测 24.21),自己写了极简 HTTP 框架、路由、静态文件服务、Cookie 会话、scrypt 口令哈希、审计日志。

前端同样没有构建步骤:原生 ESM,一个轻量的 h() DOM 构造器,Canvas 画图表。

为什么这么选?养老机构往往没有专职运维,"一个目录 + 一条 node server/index.js 就能跑"比"装 Node、装依赖、跑构建、配进程守护"现实得多。零依赖也意味着没有供应链风险、没有版本升级带来的意外破坏。

代价是什么都要自己写。最典型的是两个 Office 文件格式:

  • XLSX:手写 OOXML + ZIP(zlib.deflateRawSync),做出带表头冻结和列宽的工作簿,并用 Python 的 openpyxl 独立验证能打开
  • DOCX:后来用户要求评估报告除了"现有版本"还要能导出 Word。.docx 本质也是 ZIP 包 + WordprocessingML,于是复用 xlsx.js 里的 zipSync,新建 docx.js 手写 [Content_Types].xml、关系、正文、样式、A4 页面设置和中文字体。没有引入 docx 这类体积庞大的库

两种格式共用同一份数据构造逻辑(buildReportData()),只是渲染器不同,所以内容永远一致。

经验:零依赖不是目的,是部署约束倒推出来的结果。当"能被一个非技术人员复制到另一台电脑上直接运行"是硬需求时,这个选择就成立了。


四、双端不是两套代码,是同一套数据的两种视角

Web 管理端 手机端 H5
入口 / /m/
主要用户 机构管理员、评估员(办公桌前) 评估员上门、老人/家属自助
特点 同屏信息密度高、表格与统计 单列、分步、大热区
路由 9 个页面 4 个 Tab(首页 / 评估 / 档案 / 我的)

两端共用同一个后端、同一套 API、同一份 SQLite 数据库。手机端刻意做成 PWA 风格的 H5 而非原生 App,是为了先跑通业务闭环;把它封装成真正的 App / 微信公众号是下一步待办,不在本次范围内。

角色分为 admin / assessor / org / elder 四级,机构账号在查询与导出时由服务端强制追加 org_id 过滤——这类约束必须放在服务端,前端隐藏按钮不算数。


五、适老化:不是"字大一点"这么简单

第二轮需求是系统性的适老化优化。我按视觉层 / 交互层 / 内容层三层拆解,每一条都写成「原问题 → 优化方案 → 预期效果」,并给出了可运行的单文件 HTML 原型和设计规范表。

几条硬指标:

  • 正文 ≥ 18px,行高 ≥ 1.6,对比度 ≥ 4.5:1
  • 所有可点击热区 ≥ 48×48px(移动主操作 56px)
  • 主色只有 2 种;四级能力等级色属于语义状态色,与红绿灯同理,不算"装饰主色"
  • 图标必须有文字标签、标签常驻、错误信息紧贴字段且写明"原因 + 怎么改"
  • 分步指示器 + 上一步/下一步、关键提交二次确认、明确的状态反馈与撤销

22 项量表原来一屏铺开,改成按标准自带的 4 个一级指标分成 5 步向导。这里有个原则:结构可以改,但一项都不能删,且改动理由要写明——标准是 4 个一级指标,分步正是按它自己的分组来的。

配色上还走过一段弯路。初版是"深空蓝侧栏 + 亮蓝渐变",用户一句"AI 感太重了"点醒了:这套配色像通用后台模板,不像民政/养老机构的实务系统。后来全部重做成暖石白 + 暖灰文字 + 沉稳靛蓝仅用于可点击元素的自然暖调,语义色也换成苔绿 / 土黄 / 赭石 / 砖红。对比度逐组实测,全部达标。


六、几个不得不补的"生产必需"功能

题目只要求五项功能,但真要交付给机构用,还缺东西。我只补了本地单机部署下能真正跑起来的:

数据备份与恢复(最重要)。老人能力评估记录属于长期法律档案,丢了无法重建。用 SQLite VACUUM INTO 生成一致性快照,不停服;恢复前自动再备份一次当前状态;必须手动输入 RESTORE 才执行;仅管理员可用;全程记审计日志。

待复核状态色:评估员录完不等于定稿,机构负责人还要复核签字,所以补了一个与四个能力等级区分开的琥珀色。

图表跟随主题:把 Canvas 里 60 多处硬编码颜色改成从 CSS token 读取,改主题时不会再出现"表格暖白、图表冷蓝"的割裂。

至于短信/公众号提醒、电子签名、上级平台对接、字段级脱敏、移动端离线评估——都需要外部账号或硬件条件,在纯本地部署下做了也是空壳。我列进文档但没做,理由写清楚。


七、一次真实的数据事故,以及它教会我的事

这是本文最该诚实写下的部分。

备份功能初版有个严重缺陷:恢复时只覆盖了主 .db 文件,没处理 WAL 模式下的 -wal / -shm。结果新旧数据错位,读出 malformed database schema——测试时把数据库写坏了。

处置过程:

  1. 立刻停下,不试图"悄悄修好"
  2. 从测试前的备份完整恢复(24 位老人 / 60 条评估 / 5 个用户 / 3 个机构)
  3. 修复根因:改为"关闭连接 → 等待句柄释放 → 删除 -wal/-shm → 覆盖主文件 → 重开并校验",删除侧文件带重试(Windows 下 EBUSY 很常见)
  4. 删掉那个会真实覆盖数据的测试,改写 verify-restore-isolated.js:在临时目录的副本上跑完整流程,绝不碰生产库
  5. 全链路加安全阀:路径穿越防护、RESTORE 确认词、恢复前自动快照

后来另一次回归里,导入测试从 36/36 掉到 31/36,查下来是测试反复运行留下的 11 条脏数据("测试甲/乙/丙"身份证已存在导致重复判定),与功能改动无关。备份数据库后精确清理,重跑恢复 36/36。

经验一:能操作生产数据的测试,本身就是风险源。测试要用副本。
经验二:出现"程序能滚、用户滚不动""程序能跑、实际是坏的"这类现象时,先怀疑环境状态(WAL、句柄、残留数据),再怀疑逻辑。
经验三:先备份、小步验证、验证不过就回退——这套流程看着慢,实际比"一把梭之后回滚"便宜得多。


八、后面几轮迭代,都是"用户看出来的"

自动化测试和我的自检都通过了,但真正的问题往往是用户一句话点出来的:

  • 「主界面没有下拉,下面的内容看不到」:不是缺功能,是 main.js 里 document.body.style.overflow = 'hidden' 把滚动锁死了。按 CSS 规范,当 html 为 visible 时,body 的 overflow:hidden 会传播到视口。去掉锁,侧栏和顶栏靠 position:sticky 固定即可。修完真实滚轮能滚到 3224px,底部内容可见。
  • 「右侧内容被遮挡,显示不完整」:三个页面表格列多,加上 .nowrap 强制不换行,把表格撑到 1305~1657px(容器只有 1142px),右侧列藏在横向滚动区外。改成允许换行 + 明显的横向滚动条,表格精确收回 1142px,最后一列完整可见。
  • 「报告要能导出 Word」:于是有了前面说的 docx.js,前端每条搜索结果给两个并列按钮——「现有版本(Excel)」和「Word 文件」。
  • 「图标别只用一个评字」:画了一个 SVG 图形标志——深靛蓝徽章、苔绿环抱弧线(照护与 6 个月评估周期)、暖白人形、暖金对勾(等级已判定)。Web / 手机 / 单文件原型三处统一替换,原型用内联 SVG 保持零外链。
  • 「手机端 UI 也优化一下」:手机端还留着深蓝顶栏和蓝色渐变,与 Web 端割裂。统一成暖石白 + 靛蓝竖条,输入框与小按钮热区提到 56px,并补上一直缺失的「显示与阅读」设置——老人可以自己切三档字号和两档对比度,即时生效、刷新保持。实测"特大字号"把正文从 18px 放大到 23px。

九、测试:把"我觉得没问题"变成"数字说没问题"

没有用测试框架,写了几组 Node 脚本 + 一个基于 CDP 的极简浏览器驱动(原生 WebSocket + 无头 Edge)。最终状态:

检查 结果
单元测试(标准引擎 / XLSX / 导入解析) 72 / 72
Web 端到端 40 / 40
移动端端到端 38 / 38
分步评估流程 25 / 25
导入流程 36 / 36
备份功能 20 / 20
恢复隔离验证 13 / 13
滚动专项 11 / 11
右侧遮挡 + 双格式导出 16 / 16
品牌图形标志 15 / 15
手机端 UI 优化 18 / 18
单文件原型 30 / 30
色板对比度 33 / 33

DOCX 还额外用 Python 的 zipfile + ElementTree 独立验证过:7 个部件齐全、CRC 通过、全部 XML 可解析、8 张表格 / 73 段落、无 <text> 元素。


十、回顾:几个我下次还会这么做的决定

  1. 标准先变成纯函数,再写界面。业务规则独立、可测、可审计。
  2. 零依赖,因为部署对象是没运维的机构。
  3. 设计令牌集中在 tokens.css,Canvas 通过 tk() 读同一个变量,主题不会割裂。
  4. 所有危险操作都有"后路":备份、确认词、隔离目录测试、改动前快照(带 meta.json 说明来源与原因)。
  5. 如实记录事故,不掩饰、不悄悄修。
  6. 用户反馈优先于自检:自动化测试能证明"没坏",但"好不好用"得让人看。

未完成清单(明日继续)

以下两项本次未做,不是遗漏,是按计划留到下一步:

1. 电脑端与移动端 App 的开发封装

  • 目前电脑端是"浏览器访问 Web",手机端是 H5,都还不是可分发的安装包 / 公众号
  • 待办:
    • 手机端封装为可安装 App(PWA 或 WebView 壳),处理离线、推送、版本更新
    • 微信公众号接入(JS-SDK、授权、菜单)
    • 电脑端桌面封装(快捷方式 / 本地服务守护 / 开机自启)
    • 安装包、图标、启动页、版本号与升级策略

2. 正式投入使用之前的准备

  • 目前是演示/开发环境,不能直接上生产
  • 待办:
    • 演示数据清理、真实机构与账号初始化
    • HTTPS / 域名 / 反向代理 / 防火墙
    • 定时自动备份与异地留存、灾难恢复演练
    • 权限复核与敏感字段脱敏规则确认
    • 操作日志留存周期、等保与隐私合规
    • 用户培训材料、运维手册、验收清单
    • 灰度试运行与回退预案

这两部分会单独成文。本文先记录已经完成的开发过程。


记录时间:2026-11-04 · 项目:老年人能力评估系统(MZ/T 039—2013)

posted @ 2026-11-04 23:05  CCidy  阅读(3)  评论(0)    收藏  举报