2026-04-29 14:56:33 +08:00

524 lines
17 KiB
JavaScript
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.

// subpackage/teacher/student-fitness-detail/student-fitness-detail.js
import uCharts from '../../../utils/chart/u-charts';
import { getReport } from '../../../utils/serve/parent';
import { getStudentClassRoomReport } from '../../../utils/serve/teacher';
var uChartsInstance = {};
const UNIT_MAP = {
'BMI': 'kg/m²',
'肺活量': 'ml',
'坐位体前屈': 'cm',
'立定跳远': 'cm',
'一分钟跳绳': '个',
'一分钟仰卧起坐': '个',
};
Page({
data: {
fitnessItems: [],
fitnessReport: [],
radarCWidth: 300,
radarCHeight: 300,
pixelRatio: 2,
showFitnessPopup: false,
selectedFitnessItem: null,
studentNo: '',
readyFired: false,
// Tab
activeTab: 0, // 0=课堂报告 1=体测报告
classroomSubTab: 0, // 0=月 1=季度
// 时间选择
selectedYear: 2026,
selectedMonth: 1,
selectedQuarter: 1,
showMonthPicker: false,
showQuarterPicker: false,
pickerYear: 2026,
nowYear: 0,
nowMonth: 0,
nowQuarter: 0,
monthList: [1,2,3,4,5,6,7,8,9,10,11,12],
quarterList: [
{ value: 1, label: '第一季度', months: '1-3月' },
{ value: 2, label: '第二季度', months: '4-6月' },
{ value: 3, label: '第三季度', months: '7-9月' },
{ value: 4, label: '第四季度', months: '10-12月' },
],
monthData: {},
quarterData: {},
classroomLoading: false,
},
async onLoad(options) {
const now = new Date()
const nowYear = now.getFullYear()
const nowMonth = now.getMonth() + 1
const nowQuarter = Math.ceil(nowMonth / 3)
this.setData({
studentNo: options.studentNo || '',
nowYear, nowMonth, nowQuarter,
selectedYear: nowYear,
selectedMonth: nowMonth,
selectedQuarter: nowQuarter,
pickerYear: nowYear,
})
wx.showLoading({ title: '加载中' });
await Promise.all([
this._loadFitnessReport(options.studentNo),
this.fetchMonthReport(),
])
wx.hideLoading();
},
async _loadFitnessReport(studentNo) {
try {
const res = await getReport({ studentNo: studentNo || '' });
const rawList = (res.data && res.data.categoryScoreList) || [];
const list = rawList.filter(item => !(item.name && item.name.includes('米') && item.name.includes('跑')));
const gradeId = list.length > 0 ? list[0].gradeId : null;
let radarList = list;
if (gradeId !== null && gradeId !== undefined) {
if (gradeId <= 2) {
radarList = list.filter(item => item.name !== '一分钟仰卧起坐' && item.name !== '立定跳远');
} else if (gradeId > 2 && gradeId <= 6) {
radarList = list.filter(item => item.name !== '立定跳远');
} else if (gradeId > 6) {
radarList = list.filter(item => item.name !== '一分钟跳绳');
}
}
const fitnessItems = list.map(item => ({
...item,
unit: UNIT_MAP[item.name] || '',
}));
this.setData({ fitnessReport: radarList, fitnessItems }, () => {
if (this.data.readyFired && this.data.activeTab === 1) {
this.drawRadarChart(this);
}
});
} catch (e) {
this.setData({ fitnessItems: [] });
}
},
onReady() {
const info = wx.getWindowInfo();
const pixelRatio = info.pixelRatio;
const rpxRatio = info.windowWidth / 750;
const radarCWidth = Math.floor(info.windowWidth - 2 * 30 * rpxRatio);
const radarCHeight = Math.floor(radarCWidth * 1.05);
this.setData({ radarCWidth, radarCHeight, pixelRatio, readyFired: true }, () => {
if (this.data.activeTab === 1 && this.data.fitnessReport && this.data.fitnessReport.length > 0) {
this.drawRadarChart(this);
}
});
},
// 切换主 Tab
switchTab(e) {
const tab = Number(e.currentTarget.dataset.tab)
if (tab === this.data.activeTab) return
this.setData({ activeTab: tab }, () => {
if (tab === 1 && this.data.readyFired && this.data.fitnessReport.length > 0) {
this.drawRadarChart(this)
}
})
},
// 切换课堂子Tab (0=月 1=季度)
switchClassroomSubTab(e) {
const tab = Number(e.currentTarget.dataset.tab)
if (tab === this.data.classroomSubTab) return
this.setData({ classroomSubTab: tab }, () => {
if (tab === 0) this.fetchMonthReport()
else if (tab === 1) this.fetchQuarterReport()
})
},
// 月份选择器
openMonthPicker() {
this.setData({ showMonthPicker: true, pickerYear: this.data.selectedYear })
},
closeMonthPicker() {
this.setData({ showMonthPicker: false })
},
selectMonth(e) {
const month = Number(e.currentTarget.dataset.month)
const { pickerYear, nowYear, nowMonth } = this.data
if (pickerYear > nowYear || (pickerYear === nowYear && month > nowMonth)) return
this.setData({ selectedMonth: month, selectedYear: pickerYear, showMonthPicker: false }, () => {
this.fetchMonthReport()
})
},
// 季度选择器
openQuarterPicker() {
this.setData({ showQuarterPicker: true, pickerYear: this.data.selectedYear })
},
closeQuarterPicker() {
this.setData({ showQuarterPicker: false })
},
selectQuarter(e) {
const quarter = Number(e.currentTarget.dataset.quarter)
const { pickerYear, nowYear, nowQuarter } = this.data
if (pickerYear > nowYear || (pickerYear === nowYear && quarter > nowQuarter)) return
this.setData({ selectedQuarter: quarter, selectedYear: pickerYear, showQuarterPicker: false }, () => {
this.fetchQuarterReport()
})
},
// 年份切换(月/季度共用)
prevPickerYear() {
this.setData({ pickerYear: this.data.pickerYear - 1 })
},
nextPickerYear() {
if (this.data.pickerYear >= this.data.nowYear) return
this.setData({ pickerYear: this.data.pickerYear + 1 })
},
_getMonthRange(year, month) {
const startTime = `${year}-${String(month).padStart(2, '0')}-01`
let endYear = year, endMonth = month + 1
if (endMonth > 12) { endYear++; endMonth = 1 }
const endTime = `${endYear}-${String(endMonth).padStart(2, '0')}-01`
return { startTime, endTime }
},
_getQuarterRange(year, quarter) {
const startMonth = (quarter - 1) * 3 + 1
let endYear = year, endMonth = quarter * 3 + 1
if (endMonth > 12) { endYear++; endMonth = 1 }
const startTime = `${year}-${String(startMonth).padStart(2, '0')}-01`
const endTime = `${endYear}-${String(endMonth).padStart(2, '0')}-01`
return { startTime, endTime }
},
_buildPeriodData(apiData) {
const d = apiData || {}
return {
heartRange: `${d.minHR || 0}-${d.maxHR || 0}次/分`,
avgHeart: d.avgHR || 0,
highDuration: `${d.highIntensity || 0}分钟`,
highRatio: `${d.highIntensityDurationRatio || 0}%`,
jumpTotal: d.totalCount || 0,
jumpDuration: d.totalDuration || 0,
jumpAvgSpeed: d.avgSpeed || 0,
jumpRecords: (d.jumpRecordList || []).map(item => ({
type: item.jumpType,
duration: `${item.totalDuration}s`,
count: item.totalCount,
})),
}
},
async fetchMonthReport() {
const { selectedYear, selectedMonth, studentNo } = this.data
const { startTime, endTime } = this._getMonthRange(selectedYear, selectedMonth)
this.setData({ classroomLoading: true })
try {
const res = await getStudentClassRoomReport({ type: 2, startTime, endTime, studentNo })
this.setData({ monthData: this._buildPeriodData(res.data) })
} catch (e) {
this.setData({ monthData: {} })
}
this.setData({ classroomLoading: false })
},
async fetchQuarterReport() {
const { selectedYear, selectedQuarter, studentNo } = this.data
const { startTime, endTime } = this._getQuarterRange(selectedYear, selectedQuarter)
this.setData({ classroomLoading: true })
try {
const res = await getStudentClassRoomReport({ type: 2, startTime, endTime, studentNo })
this.setData({ quarterData: this._buildPeriodData(res.data) })
} catch (e) {
this.setData({ quarterData: {} })
}
this.setData({ classroomLoading: false })
},
drawRadarChart(that) {
const list = that.data.fitnessReport || [];
const categories = list.map(i => {
const name = i.name || '';
if (name.length > 5) {
const mid = Math.ceil(name.length / 2);
return name.slice(0, mid) + '\n' + name.slice(mid);
}
return name;
});
const prevData = list.map(i => i.prevScore || 0);
const currentData = list.map(i => i.lastScore || 0);
const currentDataLabels = list.map(i => {
const score = i.lastScore != null ? i.lastScore : '';
const rank = i.rank != null ? i.rank : '';
return rank ? score + '\n' + rank : String(score);
});
const query = wx.createSelectorQuery().in(that);
query.select('#radarCanvas').fields({ node: true, size: true }).exec(res => {
if (!res[0]) return;
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
canvas.width = (res[0].width + 20) * that.data.pixelRatio;
canvas.height = (res[0].height + 20) * that.data.pixelRatio;
uChartsInstance['radarCanvas'] = new uCharts({
type: 'radar',
context: ctx,
width: that.data.radarCWidth * that.data.pixelRatio,
height: that.data.radarCHeight * that.data.pixelRatio,
categories,
series: [
{ name: '上次成绩', data: prevData, color: '#4A90E2', dataLabel: false },
{ name: '本次成绩', data: currentData, color: '#E74C3C', dataLabels: currentDataLabels, textColor: '#E74C3C', textSize: 11 },
],
pixelRatio: that.data.pixelRatio,
animation: true,
tapLegend: true,
background: 'rgba(0,0,0,0)',
color: ['#4A90E2', '#E74C3C'],
padding: [0, 0, 0, 15],
legend: {
show: true,
position: 'bottom',
float: 'center',
fontSize: 12,
lineHeight: 11,
margin: 0,
itemGap: 20,
},
extra: {
radar: {
gridType: that.data.fitnessReport.length === 4 ? 'circle' : 'radar',
gridColor: '#CCCCCC',
gridCount: 4,
opacity: 0.15,
gridEvenShow: true,
gridOddShow: true,
labelShow: true,
labelColor: '#333',
fontSize: 13,
labelMargin: 30,
labelPointShow: false,
max: 100,
},
},
});
});
},
showFitnessScore(e) {
const item = e.currentTarget.dataset.item;
this.setData({ selectedFitnessItem: item, showFitnessPopup: true });
},
closeFitnessPopup() {
this.setData({ showFitnessPopup: false, selectedFitnessItem: null });
},
goFitnessDetail() {
const item = this.data.selectedFitnessItem;
if (!item) return;
this.setData({ showFitnessPopup: false });
wx.setStorageSync('fitnessDetailItem', item);
wx.navigateTo({
url: '/subpackage/parent/fitness-detail/fitness-detail',
});
},
onShow() {},
onHide() {},
onUnload() {},
async onPullDownRefresh() {
if (this.data.activeTab === 0) {
if (this.data.classroomSubTab === 0) await this.fetchMonthReport()
else await this.fetchQuarterReport()
} else {
await this._loadFitnessReport(this.data.studentNo)
}
wx.stopPullDownRefresh()
wx.showToast({ title: '刷新成功', icon: 'none' })
},
onReachBottom() {},
onShareAppMessage() {},
})
// var uChartsInstance = {};
// const UNIT_MAP = {
// 'BMI': 'kg/m²',
// '肺活量': 'ml',
// '坐位体前屈': 'cm',
// '立定跳远': 'cm',
// '一分钟跳绳': '个',
// '一分钟仰卧起坐': '个',
// };
// Page({
// data: {
// fitnessItems: [],
// fitnessReport: [],
// radarCWidth: 300,
// radarCHeight: 300,
// pixelRatio: 2,
// showFitnessPopup: false,
// selectedFitnessItem: null,
// studentNo: '',
// readyFired: false,
// },
// async onLoad(options) {
// wx.showLoading({ title: '加载中' });
// this.setData({ studentNo: options.studentNo || '' });
// try {
// const res = await getReport({ studentNo: options.studentNo });
// const rawList = (res.data && res.data.categoryScoreList) || [];
// const list = rawList.filter(item => !(item.name && item.name.includes('米') && item.name.includes('跑')));
// // 雷达图根据 gradeId 过滤不适用的项目
// const gradeId = list.length > 0 ? list[0].gradeId : null;
// let radarList = list;
// if (gradeId !== null && gradeId !== undefined) {
// if (gradeId <= 2) {
// radarList = list.filter(item => item.name !== '一分钟仰卧起坐' && item.name !== '立定跳远');
// } else if (gradeId > 2 && gradeId <= 6) {
// radarList = list.filter(item => item.name !== '立定跳远');
// } else if (gradeId > 6) {
// radarList = list.filter(item => item.name !== '一分钟跳绳');
// }
// }
// const fitnessItems = list.map(item => ({
// ...item,
// unit: UNIT_MAP[item.name] || '',
// }));
// this.setData({
// fitnessReport: radarList,
// fitnessItems,
// }, () => {
// // 如果 onReady 已经触发,数据到位后立即绘图
// if (this.data.readyFired) {
// this.drawRadarChart(this);
// }
// });
// } catch (e) {
// this.setData({ fitnessItems: [] });
// }
// wx.hideLoading();
// },
// onReady() {
// const info = wx.getWindowInfo();
// const pixelRatio = info.pixelRatio;
// const rpxRatio = info.windowWidth / 750;
// const radarCWidth = Math.floor(info.windowWidth - 2 * 30 * rpxRatio);
// const radarCHeight = Math.floor(radarCWidth * 1.05);
// this.setData({ radarCWidth, radarCHeight, pixelRatio, readyFired: true }, () => {
// // 如果数据已经加载完,直接绘图;否则等 onLoad 回调里触发
// if (this.data.fitnessReport && this.data.fitnessReport.length > 0) {
// this.drawRadarChart(this);
// }
// });
// },
// drawRadarChart(that) {
// const list = that.data.fitnessReport || [];
// // 长文本自动换行(超过5字在中间插入换行)
// const categories = list.map(i => {
// const name = i.name || '';
// if (name.length > 5) {
// const mid = Math.ceil(name.length / 2);
// return name.slice(0, mid) + '\n' + name.slice(mid);
// }
// return name;
// });
// const prevData = list.map(i => i.prevScore || 0);
// const currentData = list.map(i => i.lastScore || 0);
// // 本次成绩数据标签:显示分数 + 等级
// const currentDataLabels = list.map(i => {
// const score = i.lastScore != null ? i.lastScore : '';
// const rank = i.rank != null ? i.rank : '';
// return rank ? score + '\n' + rank : String(score);
// });
// const query = wx.createSelectorQuery().in(that);
// query.select('#radarCanvas').fields({ node: true, size: true }).exec(res => {
// if (!res[0]) return;
// const canvas = res[0].node;
// const ctx = canvas.getContext('2d');
// canvas.width = (res[0].width + 20) * that.data.pixelRatio;
// canvas.height = (res[0].height + 20) * that.data.pixelRatio;
// uChartsInstance['radarCanvas'] = new uCharts({
// type: 'radar',
// context: ctx,
// width: that.data.radarCWidth * that.data.pixelRatio,
// height: that.data.radarCHeight * that.data.pixelRatio,
// categories,
// series: [
// { name: '上次成绩', data: prevData, color: '#4A90E2', dataLabel: false },
// { name: '本次成绩', data: currentData, color: '#E74C3C', dataLabels: currentDataLabels, textColor: '#E74C3C', textSize: 11 },
// ],
// pixelRatio: that.data.pixelRatio,
// animation: true,
// tapLegend: true,
// background: 'rgba(0,0,0,0)',
// color: ['#4A90E2', '#E74C3C'],
// padding: [0, 0, 0, 15],
// legend: {
// show: true,
// position: 'bottom',
// float: 'center',
// fontSize: 12,
// lineHeight: 11,
// margin: 0,
// itemGap: 20,
// },
// extra: {
// radar: {
// gridType: that.data.fitnessReport.length === 4 ? 'circle' : 'radar',
// gridColor: '#CCCCCC',
// gridCount: 4,
// opacity: 0.15,
// gridEvenShow: true,
// gridOddShow: true,
// labelShow: true,
// labelColor: '#333',
// fontSize: 13,
// labelMargin: 30,
// labelPointShow: false,
// max: 100,
// },
// },
// });
// });
// },
// showFitnessScore(e) {
// const item = e.currentTarget.dataset.item;
// this.setData({ selectedFitnessItem: item, showFitnessPopup: true });
// },
// closeFitnessPopup() {
// this.setData({ showFitnessPopup: false, selectedFitnessItem: null });
// },
// goFitnessDetail() {
// const item = this.data.selectedFitnessItem;
// if (!item) return;
// this.setData({ showFitnessPopup: false });
// wx.setStorageSync('fitnessDetailItem', item);
// wx.navigateTo({
// url: '/subpackage/parent/fitness-detail/fitness-detail',
// });
// },
// onShow() {},
// onHide() {},
// onUnload() {},
// onPullDownRefresh() {},
// onReachBottom() {},
// onShareAppMessage() {},
// })