ltx 647bc39d27 feat: 完成体质报告小程序入口与教练资源播放优化
教练端在学生报告页录入体质数据并生成报告,新增报告查看页;学员端从报告页查看最新评估结果、体测指标与运动建议。请求统一走现有 QA 后端并携带登录令牌。

教学资源改用后台音频管理器,支持进度拖动、曲目切换、时长探测及网络异常时下载恢复;修正跳绳和开合跳动作提示文案。

验证:相关 JS 语法、页面 JSON 解析和 git diff --cached --check 均通过。
2026-10-23 16:48:58 +08:00

151 lines
5.7 KiB
JavaScript

import { generatePostureReport } from '../../../utils/serve/posture';
const bodyFields = [
{ key: 'heightCm', label: '身高', unit: 'cm' },
{ key: 'weightKg', label: '体重', unit: 'kg' },
{ key: 'waistCm', label: '腰围', unit: 'cm' },
{ key: 'bodyFatPercent', label: '体脂率', unit: '%' },
{ key: 'muscleMassKg', label: '肌肉量', unit: 'kg' },
];
const testFields = [
{ key: 'sprint50mSeconds', label: '50米跑', unit: '秒' },
{ key: 'standingLongJumpCm', label: '立定跳远', unit: 'cm' },
{ key: 'plankSeconds', label: '平板支撑', unit: '秒' },
{ key: 'gripStrengthKg', label: '握力', unit: 'kg' },
{ key: 'ropeSkipCount', label: '一分钟跳绳', unit: '个' },
{ key: 'ropeSkip30SecCount', label: '30秒跳绳', unit: '个' },
{ key: 'vitalCapacityMl', label: '肺活量', unit: 'ml' },
{ key: 'restingHeartRateBpm', label: '静息心率', unit: '次/分' },
{ key: 'stepTestHeartRateBpm', label: '台阶测试恢复心率', unit: '次/分' },
{ key: 'obstacleRun15mSeconds', label: '15米绕障碍跑', unit: '秒' },
{ key: 'quadrantJumpSeconds', label: '十字象限跳', unit: '秒' },
{ key: 'reactionTimeSeconds', label: '选择反应时', unit: '秒' },
{ key: 'balanceBeamSeconds', label: '走平衡木', unit: '秒' },
{ key: 'singleLegStandSeconds', label: '闭眼单脚站立', unit: '秒' },
{ key: 'sitReachCm', label: '坐位体前屈', unit: 'cm' },
];
const allFields = bodyFields.concat(testFields);
const today = () => {
const date = new Date();
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
};
function normalizeSex(value) {
const text = String(value || '').toLowerCase();
if (text === '1' || text === '男' || text === 'male') return 0;
if (text === '2' || text === '女' || text === 'female') return 1;
return -1;
}
Page({
data: {
studentNo: '',
studentName: '',
className: '',
birthDate: '',
testDate: today(),
sexIndex: -1,
sexOptions: ['男', '女'],
gradeIndex: -1,
gradeOptions: ['一年级', '二年级', '三年级', '四年级', '五年级', '六年级', '七年级', '八年级', '九年级', '高一', '高二', '高三'],
bodyFields,
testFields,
values: {},
submitting: false,
},
onLoad(options) {
const gradeId = Number(decodeURIComponent(options.gradeId || ''));
this.setData({
studentNo: decodeURIComponent(options.studentNo || ''),
studentName: decodeURIComponent(options.studentName || ''),
className: decodeURIComponent(options.className || ''),
sexIndex: normalizeSex(decodeURIComponent(options.sex || '')),
gradeIndex: Number.isInteger(gradeId) && gradeId >= 1 && gradeId <= 12 ? gradeId - 1 : -1,
});
},
onBirthDateChange(e) { this.setData({ birthDate: e.detail.value }); },
onTestDateChange(e) { this.setData({ testDate: e.detail.value }); },
onSexChange(e) { this.setData({ sexIndex: Number(e.detail.value) }); },
onGradeChange(e) { this.setData({ gradeIndex: Number(e.detail.value) }); },
onNumberInput(e) {
const key = e.currentTarget.dataset.key;
this.setData({ [`values.${key}`]: e.detail.value });
},
buildPayload() {
const { studentNo, studentName, className, birthDate, testDate, sexIndex, gradeIndex, values } = this.data;
if (!studentNo || !studentName || !birthDate || !testDate || sexIndex < 0 || gradeIndex < 0) {
throw new Error('请补齐出生日期、测试日期、性别和年级');
}
if (testDate < birthDate) throw new Error('测试日期不能早于出生日期');
const numbers = {};
allFields.forEach(field => {
const raw = values[field.key];
if (raw === undefined || String(raw).trim() === '') return;
const value = Number(raw);
if (!Number.isFinite(value) || (field.key !== 'sitReachCm' && value <= 0)) {
throw new Error(`请检查${field.label}的数值`);
}
numbers[field.key] = value;
});
if (!numbers.heightCm || !numbers.weightKg) throw new Error('请填写身高和体重');
const tests = {};
testFields.forEach(field => {
if (numbers[field.key] !== undefined) tests[field.key] = numbers[field.key];
});
return {
assessmentType: 'student',
studentName,
studentId: studentNo,
birthDate,
testDate,
sex: sexIndex === 0 ? 'male' : 'female',
grade: String(gradeIndex + 1),
className,
heightCm: numbers.heightCm,
weightKg: numbers.weightKg,
...(numbers.waistCm !== undefined ? { waistCm: numbers.waistCm } : {}),
bodyComposition: {
...(numbers.bodyFatPercent !== undefined ? { bodyFatPercent: numbers.bodyFatPercent } : {}),
...(numbers.muscleMassKg !== undefined ? { muscleMassKg: numbers.muscleMassKg } : {}),
},
tests,
};
},
async submit() {
if (this.data.submitting) return;
let payload;
try {
payload = this.buildPayload();
} catch (e) {
wx.showToast({ title: e.message, icon: 'none' });
return;
}
this.setData({ submitting: true });
wx.showLoading({ title: '生成报告中', mask: true });
try {
const response = await generatePostureReport(this.data.studentNo, payload);
if (!response.data || !response.data.success || !response.data.result) {
throw new Error('评价服务未返回有效报告');
}
wx.hideLoading();
wx.redirectTo({
url: '/subpackage/parent/posture-report/posture-report?mode=teacher&studentNo=' + encodeURIComponent(this.data.studentNo),
});
} catch (e) {
wx.hideLoading();
wx.showModal({ title: '生成失败', content: e.message || '请稍后重试', showCancel: false });
} finally {
this.setData({ submitting: false });
}
},
});