equipmentNo+scanId)→ 会员小程序扫一扫 → 打开「确认开始体测」页(确认资料·缺身高则补 + 校体测次数)→ 点「确认并开始」核销 1 次并调海迪绑定 → 设备关码 → 会员上秤测量 → 海迪 push 报告 到练二 → 「我的」最近体测卡 / 体测记录 查看。
用户端(C 端) 6 个产品段 + 管理后台(B 端) 设备绑定;下表列出每段包含的页面/状态屏,便于导航定位。
| 流程段 | 包含的页面 / 状态屏 |
|---|---|
| 入口 | 我的页含「最近体测」卡 · 空态(未测过) |
| 扫码 → 开始体测 | 未登录拦截 · 确认开始(有次数)· 确认开始(无次数 → 购买)· 资料编辑弹窗 · 健康数据授权 · 监护人同意 · 授权被拒 / 撤回 |
| 购买体测次数 | 购买次数半屏 · 次数包下单确认 · 支付失败回流 · 购买成功回流 |
| 绑定与测量 | 绑定处理中 · 请上秤引导 · 测量进行中 · 测量未出数 · 绑定失败 · 会话过期 · 设备未注册 / 离线 |
| 报告 | 报告生成中 · 报告超时 + 订阅 · 体成分报告 · 体成分报告补全 · 首测无对比 · 报告错误兜底 · 指标解读弹层 · 删除报告确认 · 分享海报 |
| 记录 · 次数 · 订单 | 体测记录(趋势 + 历次)· 次数明细 / 流水 · 体测次数订单 · 体测次数订单详情 |
| 管理后台(B 端) | 体测仪-门店绑定:列表 + 筛选 + 新增/编辑 + 删除 + 启停(路由 /store/bodyDevice) |
单独一张卡,挂在「我的」页(pages/user/index.vue)私教卡区与三宫格之间,承载最近一次结论,点击进入体测记录;未测过时显示空态引导。
pages/user/bodyTest| 元素 | 字段 | 说明 |
|---|---|---|
| 健康分(环) | healthScore | 0–100 + 颜色分级 |
| 体重 | weight/weight2 | kg + ▲▼ |
| 体脂率 | pbf/pbf2 | % 越低越好 |
| 骨骼肌 | smm/smm2 | kg 越高越好 |
| 内脏脂肪 | vfr | 级 1–9 |
| 副文案 | bmi/bmr | BMI·基代 |
扫码后小程序打开「确认开始体测」页(带 equipmentNo+scanId,新页 pages/fitness/bodyTestConfirm):先校登录,再确认会员资料(性别/生日来自档案、身高首次补全)、健康数据授权(PIPL 单独同意,未成年须监护人同意),再校体测次数。
复用 pages/user/info.vue 的字段与接口,不另起一套。海迪 setUserInfo 需 性别/身高/年龄。
| 海迪需要 | 练二既有字段 | 状态 |
|---|---|---|
| sex 性别 | gender(1男/2女) | ✅ 现成 |
| age 年龄 | birthday 推算 | ✅ 现成 |
| height 身高 | —(无) | ⚠️ 唯一需新增 |
| tel 手机号 | phone | ✅ 现成 |
getUserInfo → /user/front/getUserInfosetUserInfo → /user/front/setUserInfo(userStore.saveAppUserInfo)/user/front/setUserInfo 增加 height 字段持久化;会员档案表加身高列。区分两个同名接口:练二 setUserInfo = 存档案;海迪 setUserInfo = 体测绑定。次数为 0 时引导购买(复用现有下单 + 支付,次数包商品),购买在守会话时效下闭环:半屏选包 → 下单确认 → 支付(失败可回流重试)→ 到账回流继续本次体测。
点「确认并开始」后:冻结 1 次 → 调海迪绑定(成功才落实,失败退冻)→ 设备解锁 → 会员上秤测量。覆盖绑定中/上秤/测量中/未出数/绑定失败/会话过期/设备异常等走通必需的状态。
海迪 push 报告到练二后,会员在报告页查看体成分结论。覆盖生成中/超时订阅触达、正式报告与补全(节段/目标/更多指标)、首测降级、加载兜底、指标解读与免责、删除、脱敏分享。
沉淀页:体测记录(趋势 + 历次列表)、体测次数明细/流水,以及购买次数生成的订单(落进「我的订单」新增「体测」tab,接 V0510 退款体系,详见开发文档 11.10)。
signs.vue 范式)B 端(管理后台 lianer-backend-ui)功能:门店把体测仪序列号(SN)登记并绑定到门店,列表 / 新增 / 编辑 / 删除 / 启停。一台序列号全表唯一绑定一个门店(反向唯一),一个门店可多台。此表是扫码体测链路「序列号 → 门店」的权威映射 + 白名单来源。
/store/bodyDevice · ElementUI)| # | 序列号 equipmentNo | 绑定门店 | 状态 | 备注 | 创建时间 | 操作 |
|---|---|---|---|---|---|---|
| 1 | HD11012511 | 练二(武汉店) | 启用 | 前台 | 06-05 14:20 | 编辑 · 删除 · 停用 |
| 2 | HD11012512 | 练二(武汉店) | 启用 | 二楼器械区 | 06-05 14:22 | 编辑 · 删除 · 停用 |
| 3 | HD22033100 | 练二(汉口店) | 停用 | 维修中 | 05-30 09:10 | 编辑 · 删除 · 启用 |
/user/back/* · 已建)| 功能 | 接口 |
|---|---|
| 分页列表 | POST /user/back/getBodyDeviceList |
| 新增 / 编辑 | POST /user/back/addOrUpdateBodyDevice(唯一校验 + 反填门店名) |
| 删除 | POST /user/back/delBodyDevice |
| 启用 / 停用 | POST /user/back/changeBodyDeviceStatus |
| 门店下拉 | 复用 /ground/back/getStoreList |
| 字段 | 说明 / 校验 |
|---|---|
equipmentNo 序列号 | 必填;全表唯一(DB uk_equipment_no + 保存前 selectByEquipmentNo 排重) |
storeId 门店 | 必填;据此反查 lianer_store 反填 storeName(不信任前端传名) |
deviceStatus 状态 | 1 启用 / 0 停用,默认 1 |
remark 备注 | 选填 ≤255 |
equipmentNo → selectByEquipmentNo 反查 → 查不到(未注册)/ deviceStatus=0(停用)拒绝;命中且启用 → 取 storeId 继续(创建会话 / 归属门店 / 匹配本店次数)。封装 resolveStoreByEquipmentNo 供 scanPrepare 与 webhook 共用。lianer-back(LianerBodyDevice + Mapper + BodyDeviceBackApi/Impl + 表 lianer_body_device)已编译通过、未提交(先改不提交,建表 SQL 待执行);前端 lianer-backend-ui(views/store/bodyDevice.vue + api/bodyDevice.js + 路由)已提交推送。蓝实线=接口调用,灰虚线=返回/推送,橙虚线=物理动作(扫码 / 上秤)。注意区分练二 setUserInfo(存档案)与海迪 setUserInfo(体测绑定)。
| 接口 | 方向 | 关键参数 / 说明 |
|---|---|---|
POST /openApi/getToken | 练二后端 → 海迪 | userName,appKey,timeStamp → token(24h 缓存) |
| 登入鉴权 URL | 练二 → 海迪后台配置 | 海迪注入 equipmentNo+scanId 生成二维码 |
GET /user/front/getUserInfo | 小程序 → 练二后端 | 载入会员资料(gender/birthday/height)复用现有 |
POST /user/front/setUserInfo | 小程序 → 练二后端 | 存练二档案(补身高)复用现有,后端加 height |
POST /payment/front/getRightsList | 小程序 → 练二后端 | 查体测次数(新增一种次数权益) |
| 核销体测次数 | 小程序 → 练二后端 | 仿 setPrivateAppointBySchedule 扣 1 次(新接口) |
POST /openApi/setUserInfo | 练二后端 → 海迪 | 体测绑定{equipmentNo,thirdUid,scanId,tel,sex,height,age};header token/appkey |
| 推送【体成分】webhook | 海迪 → 练二后端 | 练二提供;含 thirdUid,dataKey + 全指标 |
| 报告列表 / 详情 | 小程序 → 练二后端 | 练二自建,复用 XHR+token |
业务/技术/合规三视角去重分级:P0 不解决必出事故/资损/违规;P1 上线前必须有方案;P2 体验运营完善。
FROZEN(确认时冻结) → SETTLED(收到合格报告才真扣) → RELEASE(失败/超时自动退);后端建 measure_session 表 + 超时兜底任务。(userId,scanId) 幂等,杜绝重复扣/重复绑。equipmentNo 归属 → 同设备前后两会员的数据挂错人(隐私事故)。→ 用 scanId 反查「未关闭 session 且 thirdUid 一致」才落库,否则进异常隔离表人工核对;同设备会话串行化。requestSubscribeMessage,只靠主动打开会大量丢失,丢掉体测核心价值。→ 确认页申请一次性订阅,webhook 落库即推「报告已生成」直达报告页(需单独立项排期)。| 类别 | 考量 → 处理 |
|---|---|
| 计费 / 权益 | 体测次数做独立权益(勿复用私教次卡,会员会反弹):会员卡附赠 / 单售次数包 / 活动赠送,扣减只动该池。退款:已核销不退、未核销按单价退、附赠不折现,接现有退款引擎。 |
| 设备 / 门店 已建 | 表 lianer_body_device(已建):确认页/扫码链路 selectByEquipmentNo 反查门店再匹配本店次数,跨店策略写死;服务端白名单校验(未注册/停用直接拒绝、不冻结)。管理后台 store/bodyDevice 维护绑定。 |
| 异常容错 | 海迪错误码映射人话(「设备离线,请联系前台」)+ 一键联系门店(复用企微客服弹窗);scanId 过期给「重新出码」;后台留「已核销未收到报告」对账表。 |
| 扫码契约 | home.vue 扫码用无 try-catch 的 JSON.parse,海迪码会 parse 抛错进不了页 → 入口格式嗅探(try-catch + 海迪码特征)再分发;先与海迪确认 payload 确切格式。 |
| 首测降级 | 落库带 isFirst 标记,首测隐藏对比箭头改正向引导文案,趋势单点给空态(防 NaN)。 |
| 合规细则 | 身高 80–250cm / 年龄 4–99 范围+单位校验(防 1.75 当米);报告固定医疗免责 + BIA 禁忌人群(孕妇/起搏器)提示;正常范围只透传海迪口径不自算;数据保留期 + 删除权(「我的-体测」删单条,注销清理/解绑 thirdUid)。 |
| 技术健壮 | dataKey 幂等存 (dataKey,bodyHash):同 hash 返 200、不同 hash 版本覆盖留痕;报告先于 session 到 → pending 待认领补挂。getToken 单飞+锁防击穿,遇「会话无效」作废重取。scanId 拆两个时钟:5min 只覆盖「扫码→绑定」,测量时效在设备侧;确认页倒计时剩 <60s 禁用按钮。 |
coach/choose 带体测来源,设为核心 KPI。thirdUid 为唯一历史锚点 + 合号迁移方案;趋势以练二落库为权威自算环比,降低对 weight2 耦合。落到练二真实工程:用户端 lianer-user-app(UniApp Vue3 + TS + Pinia)、后端 lianer-back(Spring Boot / Dubbo / Shenyu)。约定:后端「先改不提交」,前端每步 commit+push。新建一个 fitness(体测)业务域承载。
equipmentNo+scanId) → 小程序扫码进 bodyTestConfirm → 后端 scanPrepare(解析门店+校设备+读资料+查次数) → 用户补身高/确认 → 点开始 → 后端 confirmStart(冻结1次 + 调海迪 setUserInfo 绑定,失败即退冻) → 海迪关码、上秤测量 → 海迪 push 报告到练二 webhook(dataKey 幂等 / scanId 反查归属) → 后端落库 + 真扣次(SETTLED) + 订阅消息推达 → 小程序「我的」最近体测卡 / 记录查看。
FROZEN 且 expire_time 已过的会话:收到过合格报告 → 转 SETTLED;否则 RELEASED 退冻。真扣只发生在收到合格体成分报告时。| 页面 / 文件 | 改动 | 复用 |
|---|---|---|
pages/user/index.vue | 加「最近体测」卡(健康分环 + 4 指标 + 对比箭头),点击跳 bodyTest;空态卡 | 现有卡片样式 |
pages/index/home.vue openPromotion | 扫码分发加格式嗅探(try-catch 包 JSON.parse)+ 海迪登入码分支 → 跳 bodyTestConfirm;未知码兜底 toast | 现有扫一扫 |
新 pages/fitness/bodyTestConfirm | 确认开始体测:设备卡 + 资料确认(性别/生日现成、身高 inline 补填)+ 次数(核销/购买)+ 倒计时 + 健康授权弹窗 + 「确认并开始」 | getUserInfo/setUserInfo、getRightsList |
新 pages/user/bodyTest | 体测记录:趋势(Canvas/uCharts)+ 历次列表(下拉分页) | signs.vue 分页范式 |
新 pages/user/bodyTestDetail | 报告详情:体成分报告(范围条/达标色/对比),保存/分享/删除 | orderDetail 范式、Poster.vue |
新 api/fitness.ts | scanPrepare / confirmStart / quota / buyQuota / reportList / reportDetail / deleteReport | XHR + token |
| 订阅消息 | 首次引入 uni.requestSubscribeMessage(在确认页申请「报告生成」模板) | — |
| 身高/单位校验 | height 80–250cm、单位提示、异常二次确认;首测 isFirst 隐藏对比箭头 | — |
| 模块 | 职责 |
|---|---|
海迪客户端 HaidiClient | 封装 getToken(单飞+分布式锁缓存 24h,遇「会话无效」作废重取)、setUserInfo(绑定,header token/appkey/timestamp) |
| 登入 URL 提供 | 提供小程序 scheme / H5 中转页模板,海迪注入 equipmentNo+scanId → 唤起 bodyTestConfirm(形态待与海迪确认) |
会话/核销 BodyTestSessionService | scanPrepare(解析门店+校设备+资料+次数)、confirmStart(冻结→绑定→失败退冻,按 (userId,scanId) 幂等)、超时兜底定时任务 |
推送 webhook BodyTestPushApi | 接体成分接口:校 appkey + dataKey 幂等(存 bodyHash) + scanId 反查归属 + 落库 + 真扣(SETTLED) + 推订阅消息;找不到 session 进 pending 待认领 |
| 体测次数权益 | 新增独立「体测次数」权益类型(不碰私教次卡),来源 会员卡附赠/单售/赠送;核销扣该池;退款规则接入 RefundBusiness |
| 设备-门店绑定 已建 | 已落地:BodyDeviceBackApi/Impl(lianer-user,/user/back/getBodyDeviceList·addOrUpdateBodyDevice·delBodyDevice·changeBodyDeviceStatus)+ 表 lianer_body_device(uk_equipment_no 反向唯一、新增唯一校验+反填门店名);扫码链路用 selectByEquipmentNo 反查 + 白名单校验(未注册/停用拒绝)。管理后台见前端 views/store/bodyDevice |
会员档案 /user/front/setUserInfo | 新增 height 字段持久化;会员表加身高列 |
| 报告读接口 + 合规 | reportList/Detail/delete;数据保留期 + 删除权(删库 + 解绑 thirdUid);健康授权记录 |
| 表 | 关键字段 |
|---|---|
lianer_body_test_session 会话/核销 | id, user_id, scan_id, equipment_no, store_id, quota_id, status(FROZEN/SETTLED/RELEASED), bind_time, settle_time, expire_time, fail_reason;唯一键 (scan_id) |
lianer_body_test_report 报告 | id, user_id, scan_id, data_key(唯一), body_hash, equipment_no, store_id, test_time, health_score, raw_json, 提取的核心指标列(weight/pbf/smm/vfr/bmi/bmr…), created_at |
lianer_body_test_quota 次数(或并入 rights 体系) | id, user_id, total, remain, source(buy/gift/card), order_no, store_scope, expire_time |
lianer_body_device 设备↔门店 已建 | id, equipment_no(唯一 uk_equipment_no), store_id, store_name(冗余), device_status(1启/0停), remark, created_time, updated_time |
lianer_health_consent 健康授权 | user_id, version, scope, third_party(haidi), agreed_at, revoked_at |
| 会员表 ALTER | 加 height(cm) 列 |
| 接口 | 方向 | 入参 → 出参 |
|---|---|---|
POST /fitness/front/scanPrepare | 小程序 → 后端 | {equipmentNo,scanId} → {storeName, needHeight, profile{gender,age,height}, quota{remain}, expireIn} |
POST /fitness/front/confirmStart | 小程序 → 后端 | {equipmentNo,scanId} → 冻结+绑定结果(成功/失败原因) |
GET /fitness/front/quota | 小程序 → 后端 | → {remain, items} |
POST /fitness/front/buyQuota | 小程序 → 后端 | {skuId} → 复用下单+支付 |
GET /fitness/front/reportList | 小程序 → 后端 | 分页 → 历次(体成分) |
GET /fitness/front/reportDetail | 小程序 → 后端 | {dataKey/sessionId} → 体成分详情 |
POST /fitness/front/deleteReport | 小程序 → 后端 | {id} → 删库 |
POST /fitness/openapi/push/body | 海迪 → 后端 | 体成分 webhook(header appkey) |
| 复用现有 | ||
/user/front/getUserInfo · setUserInfo | 小程序 → 后端 | 载入/保存资料(setUserInfo 加 height) |
/payment/front/getRightsList 等 | 小程序 → 后端 | 权益/下单/支付/退款复用 |
| 海迪 | 练二落库 | 用途 |
|---|---|---|
thirdUid | user_id(绑定时传练二 userId) | 归属 |
dataKey | data_key(唯一 + bodyHash) | 幂等 |
scanId | scan_id | 会话反查归属 |
healthScore/weight/pbf/smm/vfr/bmi/bmr/... | 提取为列 + raw_json 全量 | 卡片/报告/趋势 |
weight2/pbf2/... | 不依赖,趋势以练二历史自算环比 | 对比(降耦合) |
体测次数是付费商品,购买即生成订单,必须落进「我的订单」并接 V0510 退款体系。订单 ↔ 次数解耦:订单负责支付与退款,次数(quota)负责核销;支付成功发次数、退款扣未核销次数。
pages/order/index.vue 现有 4 个一级 tab(私教订单 / 场地卡 / 团课 / 积分订单)→ 新增第 5 类「体测次数」(orderType 新值),新建组件 order/component/fitnessItem.vue,复用 memberItem.vue 范式:二级 tab(全部 / 待支付 / 已支付 / 已退款 / 待退款 / 已关闭),卡片含 订单号 / 商品(体测 5 次卡) / 门店 / 下单时间 / 总计 / 状态 / 退款金额。订单详情复用 card/orderDetail.vue 范式(van-cell + 底部按钮)。
buyQuota 走复用的下单(memberOrderInfo 同构) + 支付(createPayOrder / trCreateMemberOrder);支付成功回调发放次数到 lianer_body_test_quota(source=buy, order_no 关联),并写订单记录;订单与次数通过 order_no 双向关联。
| 规则 | 处理 |
|---|---|
| 退款渠道 | 原路退 refundType=1(微信 / 泰荣在线支付);走 V0510 RefundBusiness 既有审批/打款流 |
| 可退金额 | 已核销次数不退、未核销按单价退(残值 = 单价 × 未核销次数);附赠/赠送次数不折现 |
| 权益扣减 | 退款完成(pay)时扣减该订单未核销的剩余次数;若有 FROZEN 占用中的次数,先结算/释放再退 |
| 区分来源 | quota 流水按 source(buy/gift/card) 分池结算,退款只动 buy 池,对账可自证 |
| 入口 | 订单详情「申请退款」/「退款详情」复用退款重做设计(order/apply、order/refundDetail) |
lianer_body_test_order(或并入既有订单表 + orderType=体测):order_no, user_id, store_id, sku(单次/5次/10次), pay_amount, discount, status, quota_id, refund_no, created_at。buyQuota(§11.6 已列) → 支付回调发次数;新增 订单列表/详情走既有 getOrderList 体系扩 orderType,或 /fitness/front/orderList · orderDetail。order/index.vue 加 tab 与 fitnessItem.vue;订单详情页(仿 card/orderDetail);退款入口接 order/apply?type=&billcode=。refundRule 文案体系。练二 · 扫码体测(海迪 HOWBODY V2.4.1)· 设计稿 + 交互时序图 + 接口契约 + 开发文档