Files
mp-pilates/docs/reformer-education.md
2026-10-18 17:26:16 +08:00

14 KiB
Raw Blame History

「探索」Tab 与 Reformer 器械图鉴

用户界面采用正式内容呈现,不显示审阅状态或未开放功能占位。审核与来源记录仅作内部内容管理,移除标识不会自动补写或改变教练审核结论。器械示意及无实测阻力参数的说明继续保留。

本地查看

pnpm build:education
open packages/server/public/learn/reformer/index.html

index.html 是独立页面:内嵌 Vanilla JS、CSS 和 SVG,不请求 CDN、字体、图片、API 或 WebGL,可直接通过 file:// 打开,也可部署到任意 HTTPS 静态站点。当前约 202 KiB,gzip 约 47 KiB,以构建日志为准。源码用 TypeScript 编写,由 esbuild 输出浏览器 JavaScript;没有 Vue/React 运行时。

开发时也可启动静态服务:

python3 -m http.server 4178 --directory packages/server/public
# 浏览器打开 http://localhost:4178/learn/reformer/

浏览器本地预览不需要微信业务域名配置。

小程序入口

底部导航新增「探索」:

  • pages/learn/index:原生内容目录首页,采用统一封面卡片与简短导语。内容目录位于 packages/app/src/content/explore.ts;后续专题按条目追加,首页不绑定某一期内容。
  • pages/learn/reformer:微信 web-view 容器,加载 https://focus.richarjiang.com/learn/reformer/。
  • pages/learn/reformer-static:不依赖网页与网络的完整中文部件说明;网页错误回调也会提供此入口。容器先通过 HEAD 检查页面状态,未部署造成的 404 或网络失败会直接进入原生回退,避免显示服务器错误页面。

探索首页只保留导语和内容卡片。新增专题时在 content/explore.ts 追加标题、摘要、分类、形式、封面与页面路径,并确保目标页面已在 pages.json 注册。目录随小程序版本发布,已有 H5 正文可独立更新;没有为尚未上线的内容显示占位卡片。

网址统一在 packages/app/src/config/education.ts 配置,不接受外部 query 指定任意 URL。web-view 使用微信原生导航栏,其它页面沿用现有 CustomNavBar。分享入口回到探索首页。

交互

点击器械部件或横向部件列表,可以高亮并阅读 9 组部件说明。组装/分解按钮可随时反向切换,分解仅表达层次,不是实际拆装步骤。

「床板位置」滑块控制归一化的示意行程;「动作进度」控制完整的推出与回程。推出段占进度 0–50%,回程段占 50–100%,因此进度 100% 时床板回到起点。暂停保持当前位置,续播从当前进度继续,回放从起点开始。首次进入有一次缓慢演示,任何操作都会取消自动演示。浏览器隐藏或器械离屏时停止播放,回来后需手动继续。手机端器械卡片在控制区域滚动时保持可见。

支持逐根挂接及 3 个组合示例,至少保留一根连接。未连接弹簧的自由端随床板收纳,长度保持不变;已连接弹簧的固定端保持在弹簧杆,另一端随床板移动。绳索展示拉环收纳状态;组装状态的示意绳路保持绘图长度,松弛向框架外侧展开,绕滑轮外缘转向。分解时绳路只用于看清归属,不保持真实约束,不模拟具体人体动作、负载或传动比。

触摸与鼠标都可使用原生 range 控件;键盘方向键、Home/End 可调节滑块,Tab/Enter 可操作全部部件及按钮。有焦点提示和中文辅助说明。prefers-reduced-motion 下关闭播放与自动演示,位置和视图即时响应。关闭 JavaScript 时,图、全部部件文案和来源仍可阅读;「查看静态图鉴」还能主动切换阅读模式。

源码与构建

文件 作用
packages/app/src/content/reformer.ts 共用部件文案、参考来源、五弹簧配置、内部审核记录和版本
education/reformer/src/model.ts 纯运动模型、动画时序、实测标定接口;不包含 SVG/DOM
education/reformer/src/geometry.ts Anatomy 等轴测实体、材料、绳路与弹簧端点;全部尺寸为绘图单位
education/reformer/src/controller.ts Vanilla DOM 交互、单个 rAF 循环、减少动态与媒体接口
education/reformer/src/page.html 页面结构与无脚本阅读内容
education/reformer/src/page.css 375–430px 移动布局及桌面布局
education/reformer/src/illustration.css SVG 四色阶材料与高亮样式
education/reformer/build.mjs 生成完整独立 HTML 和独立 SVG
education/reformer/vendor/anatomy/ 原版 Anatomy 绘图库、MIT 许可、上游版本说明
packages/server/public/learn/reformer/index.html 可直接部署的完整页面

pnpm build:app 和 pnpm build:server 都先生成教学页面与独立 SVG,不需联网下载 Anatomy。小程序内使用已生成的 PNG,避免原生图片渲染器对 SVG/CSS 的兼容差异。修改绘图后执行 node education/reformer/check-browser.mjs /tmp/reformer-checks --write-native-fallback 更新本地 PNG,再构建小程序。独立的 pnpm build:education:release 要求审核记录完整,否则失败。日常构建允许 pending 状态;该内部状态不显示在用户页面上。

pnpm build:education
pnpm --filter @mp-pilates/app type-check
pnpm build:app
pnpm build:server
node education/reformer/check-model.mjs
node education/reformer/check-browser.mjs /tmp/reformer-checks

浏览器检查需要 Node.js 22+(原生 WebSocket)与 Chrome/Chromium;默认查找 macOS Google Chrome,可通过 CHROME_PATH 指定其它位置。它检查 375/390/430/1100px、动作关键帧的 SVG 边界、部件选择、弹簧组合、播放暂停回放、实际触摸拖动、动画反转、无脚本与减少动态模式,以及控制台错误与外部资源请求,并输出截图。

模型检查覆盖:一个完整往返、固定端/随动端、收纳弹簧不伸长、运动中几何数值有效,以及未标定、缺失单根数据、无审核记录、数据无序、超出测量区间时拒绝计算。测试中的合成数据仅用于数学验证,不会进入页面。浏览器图形边界、端点和 Anatomy 线条检查不是完整的机械碰撞证明或工程认证;最终必须由教练对照实机检查。

部署到现有服务

Nest 启动时已通过静态中间件开放 /learn/,与 /api/ 分离,不经过 JSON 包装或登录验证。__dirname/../public/learn 同时适用于现有 watch 与 dist 目录结构。部署脚本原有 rsync 会携带 packages/server/public,无需额外 Node 依赖,也无需数据库迁移。

完成教练审核后:

  1. 更新共用内容文件中的 review.status、coach、reviewedAt,修改内容同时更新 revision。审核记录应对应本次修订。
  2. 执行 pnpm build:education:release、类型检查和构建;由负责人确认页面与小程序。
  3. 部署服务端或将 public/learn/reformer/index.html 放到静态 HTTPS 站点的 /learn/reformer/index.html。
  4. 如现有 Nginx 只转发 /api/,增加 /learn/ 转发。生产配置示例(保留现有其它 location):
location ^~ /learn/ {
    proxy_pass http://127.0.0.1:3008;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    gzip on;
    gzip_types text/css application/javascript image/svg+xml;
    # Nginx 默认会压缩 text/html。
}

proxy_pass 此处不带结尾 /,确保后端收到完整 /learn/reformer/ 路径。也可用 Nginx 直接托管该目录。页面无第三方资源;服务器返回 X-Content-Type-Options: nosniff,HTML 使用可重新验证的缓存策略。

  1. 在微信公众平台确认当前主体具备 web-view 能力,按微信官方 web-view 文档配置 focus.richarjiang.com 为业务域名并放置校验文件。容器的 HEAD 检查也需要该域名在 request 合法域名中;默认与现有 API 共用域名。已有的 request 合法域名不等于业务域名;开发者工具的“不校验”选项不能代替生产配置。
  2. 确认证书、HTTPS、正式账号与体验版真机都能打开页面,测试网页加载失败时的静态入口,再上传小程序。若账号暂不能使用 web-view,原生静态图鉴仍可访问。

2026-10-18 17:11(北京时间)按用户授权完成服务端发布,线上地址为 https://focus.richarjiang.com/learn/reformer/ 。本次没有上传或发布微信小程序。首次发布时页面带有审阅标识,随后按用户要求移除用户可见标识;微信 web-view 文档本次网络读取不可用,小程序发布前应在微信后台核对当前主体、业务域名和基础库的实际要求。

发布记录:

  • 发布目录:`/usr/local/web/releases/reformer-20261018-1792314611961/`。
  • 发布包 SHA-256:`00a5d4cd0251903b11eda139e0b5fd4bdf66950dad64bf8a641125a4bcba1430`,上传前后相同。
  • 对比线上 204 个 server 构建文件,仅 `main.js` 与 `main.js.map` 有变化;96 个 shared 文件完全一致。本次更新两个入口文件并新增教学 HTML 与 SVG,沿用线上依赖与 Prisma Client,没有执行数据库迁移。
  • 发布前保存 `previous-build.tgz`、进程与健康状态。发布后对运行文件逐项校验,确认环境文件摘要一致。
  • 公网 HTML、SVG、健康接口与场馆信息接口均为 200,小程序容器使用的 HEAD 请求也为 200。HTML 为 206386 字节。PM2 在线,切换时重启一次,错误日志没有新增。
  • 发布目录内保留 `verification.json`、`process-before.json`、`process-after.json` 和 `rollback.sh`。如需应用回退,先检查该脚本与当前线上后续变更,再执行;它恢复旧构建、撤回本次新增教学文件并重启原服务,不修改数据库和环境文件。

真实性与教练审核

微信业务域名校验文件已于 2026-10-18 17:24(北京时间)部署:

  • 公网地址:https://focus.richarjiang.com/IYfSihR4Io.txt,HTTP 200、text/plain、32 字节,内容与用户原文件逐字节一致。
  • 文件位置:/www/wwwroot/focus.richarjiang.com/IYfSihR4Io.txt;Nginx 为该根路径配置了精确匹配,避免被默认反向代理转发到 API。
  • 配置备份和验证记录:/usr/local/web/releases/wechat-domain-20261018-1792315399093/。配置测试通过并已平滑加载,健康接口正常。微信后台仍需由用户点击保存完成平台校验。

2026-10-18 17:21(北京时间)按用户要求完成界面精简,移除首页、静态图鉴、H5 与 SVG 无障碍描述中的审阅标识;资料来源与示意说明归入详情。H5 静态更新备份位于 /usr/local/web/releases/reformer-ui-20261018-1792315235344/,公网 HTML/SVG 校验通过,健康接口 200,服务进程与重启次数保持不变。新的原生首页已构建,尚未上传小程序。

参考来源:

这不是所有品牌的通用实机复刻。当前绘图尺寸、归一化行程、8 秒播放周期、分解距离均为界面示意,不表示实机的尺寸、可用行程、训练节奏或安全限位。弹簧颜色只对应上述参考机型。教练审阅时至少核对:

  • 工作室实际品牌、型号、弹簧数量和颜色定义;是否需要替换参考配置。
  • 弹簧杆、床板下方固定点、挂钩、轮组、限位、脚踏杆锁定与支撑关系。
  • 滑轮外缘绳路、固定/调节位置、拉环收纳位置、绳索松弛表现。
  • 头枕与肩靠位置、分解图层级是否易于理解且不会被误认为拆装教学。
  • 示意行程、播放节奏和往返原理文案;全部 UI 文案与弹簧组合是否会被误解为练习处方。
  • 375–430px 真机点击、拖动、暂停、回放和低性能设备表现。

弹簧实测接口

当前 springCalibration 是空表。页面始终显示「待实测标定」,没有 kg/N 读数,也不把颜色换算成公斤数。

SpringCalibration 为后续接入预留:来源、明确实机型号、教练、日期、同一安装状态下实际床板位置(m)与实测单根拉力(N)的有序采样,采样应包含安装预拉力。forceAt() 只对完整数据在测量区间内插值并求已连接弹簧之和;任意一根缺参数、无审核记录、数据不合法或需要外推,返回 null。

这个函数尚未接到 UI:真实力值还需要同一实机的有效行程映射、相同弹簧杆档位和测量方案,经教练审核后再实现。即使计算出弹簧合力,也不等于人体所受净阻力;摩擦、绳路与姿势等仍需另行建模。不要直接给当前绘图单位赋予米或牛顿。

外部人体教学接口

浏览器提供 window.ReformerLesson.registerMedia(media),类型为 ExternalTeachingMedia,支持 video 或外部 model 页面。接入前在 controller 中配置 approvedMediaOrigins(默认空列表),并提供 HTTPS URL、来源、教练、审核日期和标题。默认不加载任何人体或外部资源。

视频使用原生 <video controls playsinline preload="none">;3D 教学页使用独立 iframe(sandbox 仅允许脚本,不开放顶级跳转)。实际模型供应方若需要其它沙箱能力,应单独评估并按微信业务域名要求配置。当前没有医学人体生成,也没有自建 WebGL 人体模型。