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

152 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 「探索」Tab 与 Reformer 器械图鉴
用户界面采用正式内容呈现,不显示审阅状态或未开放功能占位。审核与来源记录仅作内部内容管理,移除标识不会自动补写或改变教练审核结论。器械示意及无实测阻力参数的说明继续保留。
## 本地查看
```sh
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 运行时。
开发时也可启动静态服务:
```sh
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 状态;该内部状态不显示在用户页面上。
```sh
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):
```nginx
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 使用可重新验证的缓存策略。
5. 在微信公众平台确认当前主体具备 `web-view` 能力,按[微信官方 web-view 文档](https://developers.weixin.qq.com/miniprogram/dev/component/web-view.html)配置 `focus.richarjiang.com` 为**业务域名**并放置校验文件。容器的 HEAD 检查也需要该域名在 request 合法域名中;默认与现有 API 共用域名。已有的 request 合法域名不等于业务域名;开发者工具的“不校验”选项不能代替生产配置。
6. 确认证书、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,服务进程与重启次数保持不变。新的原生首页已构建,尚未上传小程序。
参考来源:
- [Balanced Body Studio Reformer 产品资料](https://www.pilates.com/products/pilates-studio-reformer):木质框架、五弹簧配置(3 红/1 蓝/1 绿)、滚轮与肩靠配置。
- [Balanced Body Reformer 指南](https://www.pilates.com/ultimate-guide-to-the-pilates-reformer/):床板、滑轨、脚踏杆、绳索、滑轮、弹簧的作用,以及阻力和辅助的区别。
- [Studio Reformer / Tower 厂家装配手册](https://balancedbody.eu/wp-content/uploads/2022/01/Studio-Reformer-with-tower-Unpacking-and-Assembly.pdf):供教练逐项对照实机。此手册应由教练完整复核,本次联网检索仅能读取其摘要。
- [Anatomy Skill](https://github.com/wheresryan22/anatomy):按 Figure 规模使用其 SVG 实体、投影和四色阶能力;来源版本和许可保存在 vendor 的 NOTICE 中。
这不是所有品牌的通用实机复刻。当前绘图尺寸、归一化行程、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 人体模型。