title: 从模拟数据到接口化 — heart-rate.vue 到 physical.vue 的逐项差异与迁移细节
本文件对比并总结了 src/pages/patient/health/details/heart-rate.vue(当前仍使用模拟数据)与 src/pages/patient/health/details/physical.vue(已改为调用后端接口)的改动点,力求捕捉每一个显性与隐性差异,包括结构、数据流、交互、边界检查、UI 细节与工程实践层面的改进。
目的:
说明:文中引用的文件基于工作区当前文件内容对比生成,所有路径均以项目根目录为基准。
数据来源:
heart-rate.vue 使用 generateMockRecords() 在前端生成随机记录;physical.vue 通过 uni.request 向 https://wx.baiyun.work/physical/list 获取记录,并对新增/删除也调用对应 API(/physical/add、/physical/delete)。认证与登录处理:
physical.vue 在请求前将 token 从 uni.getStorageSync('token') 添加到 Authorization header;对 401 响应会清除本地 token 并 uni.reLaunch 到登录页;heart-rate.vue 没有任何认证逻辑(因为使用本地数据)。时间与周期范围计算:
physical.vue 精确计算 startTime 和 endTime(结束时间设置为当天 23:59:59.999)并使用 ISO 字符串发送给后端,避免跨日/时区错误;heart-rate.vue 只在本地生成随机日期,无需与后端对齐。数据格式化与容错:
physical.vue 将后端字段 height/weight/bmi 映射为数字、处理空值或不合法数据、优先使用后端 bmi 否则在客户端计算并保留 1 位小数;heart-rate.vue 内部记录已是干净的 JS 对象。Chart 初始化与生命周期:
physical.vue 将 chart 的创建延迟,支持按 selectedMetric 切换并在变化时销毁重建;createUChart 的调用使用动态 series 配置;heart-rate.vue 在 onMounted 直接基于固定的 createUChart 配置创建单序列图表。用户输入与校验:
physical.vue 在添加记录时校验未来日期、校验必填字段、对 BMI 阈值弹窗预警,并在成功后重新拉取并重建图表;heart-rate.vue 仅在前端校验并添加本地数据,同样做了未来日期检查,但没有 API 相关的错误处理。下面按关注点罗列两个文件的差异,并尽可能抓住“看似微不足道但重要”的细节:
physical.vue 引入额外的模块:
onShow(用于页面显示时做 token 校验);useUChart 与 getCurrentInstance 的获取相似,但 physical.vue 里对 vm/chart 实例管理更小心(延迟创建、销毁、rebuild、update);heart-rate.vue 的 chart 是直接创建:
hrChart,没有在 metric 切换或不同 series 情况下销毁重建的逻辑。为什么重要:延迟创建和可重建的图表可防止 canvas/DOM 未就绪导致的异常,且支持运行时切换展示序列(例如身高/体重/BMI)。
canvasWidth、canvasHeight 并在 onMounted 使用 getWindowWidth() 计算实际大小(根据宽高比),但:
physical.vue 的默认 canvasHeight 是 320,心率是 280,均通过比例计算:physical 使用 Math.round((320 / 750) * width)。physical.vue 在 onMounted 包裹在 setTimeout(…,500) 并 await nextTick(),然后再创建 chart;heart-rate.vue 同样有延迟但较少的后续复杂性。细节说明:使用 setTimeout+nextTick 的双保险(确保样式与容器尺寸完成)在不同平台(H5/小程序)能减少 canvas 绘制错位的概率。
physical.vue 中 fetchRecords():
startTime 与 endTime;对于月视图,endTime 会被设为当月最后一天并设置时分秒为 23:59:59.999;uni.showLoading / uni.hideLoading(放在 try/finally),提升加载体验并保证隐藏调用;Authorization: Bearer ${token},并对 401 做统一处理(清 token + 跳转登录);heart-rate.vue 使用 generateMockRecords():
关键点:真实接口需要考虑网络异常、鉴权失效、后端字段缺失与类型不一致问题,并保证日期范围和时区不会导致漏取或重复取数据。
physical.vue 对后端字段做了以下细致处理:
height/weight 可能为 null 或空字符串,先检查再 Number(),默认 0;bmi 如果存在且能 parse 为 number,则取一位小数;如果不存在且 h/w 有效则本地计算;String(item.id),并使用 formatDisplayDate(new Date(item.measureTime)) 格式化日期以统一前端展示。为什么重要:后端可能返回字符串类型或数值,也可能遗漏字段,前端需要统一并容错。强制 id 为字符串避免 v-for keyed 类型不一致导致的渲染问题。
physical.vue:
confirmAdd() 会先校验输入,提示 BMI 超标(bmi >= 25)通过 uni.showModal,但无取消分支(showCancel: false);uni.request POST 到 /physical/add,携带 Authorization;uni.showToast,closeAdd()、await fetchRecords() 并 rebuildChart;confirmDeleteRecord(id) 弹窗确认后通过 /physical/delete 接口删除,处理 401 和返回 code,成功则从 records 中剔除并重建图表。heart-rate.vue:
confirmAdd() 只在本地 records.value = [item, ...records.value](若在当前周期则加入),提示并 rebuildChart;注意:physical 的实现处理了网络返回错误并保证 UI 状态(loading、弹窗、toast)与后端一致。
physical.vue 包含 initMetricFromRoute():尝试从 getCurrentPages() 的 options 中读取 metric 参数以设置初始 selectedMetric(all/height/weight/bmi),从而决定 chart 的 series 配置;
heart-rate.vue 没有 route-driven metric 切换逻辑。
细节:从 route 读取参数允许外部页面 link 到该页面并预设 metric,提高灵活性。getCurrentPages 在不同平台的可用性也被安全地封装(try/catch)。
physical.vue:
heart-rate.vue:
这说明 physical 的实现考虑了多序列、实例生命周期和潜在的异步异常。
两文件的 template/样式非常接近,但 physical.vue 在 header 的 period-controls 内额外包含了 metric-toggle(按钮组用于切换全部/身高/体重/BMI)并对按钮 click 使用 .prevent 来阻止 picker 的冒泡或默认行为;
chart-canvas 的样式在两处有相同的 margin-left: -10rpx、background-color 与 display: block,但高度与 canvasHeight 初始化不同(physical 320rpx vs heart-rate 280rpx)。
微小但关键的细节:metric-toggle 使用 @click.prevent 可避免点击时触发 picker 的默认选择行为(这在嵌套交互时会引发误操作)。
physical.vue 和 heart-rate.vue 都使用 isAfterTodayDate()、isMonthAfterToday()、isWeekAfterToday() 来阻止选择或导航到未来日期;但:
physical.vue 在 nextPeriod() 时提前检查并触发 uni.showToast 并 return,避免设置 current 为未来值并触发不必要的请求;onPickerChange() 在 physical 中会将 pickerValue 修正为当前月份(today)并显示 toast,说明更友好地处理了用户选择未来月份的行为。小细节:替换为今天月份时会同时更新 pickerValue,使 UI 和内部 state 保持一致,防止 picker 与 current 不一致的情况。
physical.vue 在多处使用 try/catch 并在控制台打印警告(console.warn / console.error),但对用户则显示 toast 或跳转登录以处理常见问题;
heart-rate.vue 较少对异常做显式捕获(因为只有本地操作),主要用 try/catch 在 chart destroy 等地方包裹。
重要性:生产环境要把异常对用户友好地提示并在控制台保留足够信息用于排查。
以下模式在 physical.vue 中体现,建议在其他页面迁移到接口时复用:
uni.showLoading(),finally 中 uni.hideLoading()。@click.prevent 阻止 picker 与父容器的冲突;修改 pickerValue 与 current 保持同步。src/api/physical.ts),方便重用与单元测试。uni.request 前检查 token 有效性(本地),并在多个页面之间共享统一的鉴权中间件。本文档在根目录 docs/ 下保存为 从模拟数据到接口化-心率到体格数据迁移详解.md,目标是为团队成员提供一份完整且可操作的迁移参考。若需要我可以将其中的建议抽象成可复用的代码片段(如 api 层、utils 或 chart 管理器),并为 heart-rate.vue 实现接口化改造。