524 lines
16 KiB
JavaScript
524 lines
16 KiB
JavaScript
// pages/parent/report/report.js
|
||
const tabService = require("../../../utils/tab-serve");
|
||
import uCharts from '../../../utils/chart/u-charts';
|
||
import {
|
||
getReport,
|
||
getPhysicalFitnessReport
|
||
} from '../../../utils/serve/parent'
|
||
import { getClassRoomRecord, getStudentClassRoomReport } from '../../../utils/serve/teacher';
|
||
import { getWeekdayWithValidation } from '../../../utils/utils';
|
||
|
||
var uChartsInstance = {};
|
||
|
||
// 各指标单位映射(接口不返回单位)
|
||
const UNIT_MAP = {
|
||
'BMI': 'kg/m²',
|
||
'肺活量': 'ml',
|
||
'坐位体前屈': 'cm',
|
||
'立定跳远': 'cm',
|
||
'一分钟跳绳': '个',
|
||
'一分钟仰卧起坐': '个',
|
||
};
|
||
|
||
Page({
|
||
|
||
/**
|
||
* 页面的初始数据
|
||
*/
|
||
data: {
|
||
report: {},
|
||
list: [],
|
||
isTrainee: false,
|
||
pageIndex: 1,
|
||
hasMore: true,
|
||
roomList: [],
|
||
// 体测报告
|
||
activeTab: 0, // 0=课堂报告 1=体测报告
|
||
fitnessItems: [], // 六个指标卡片
|
||
fitnessReport: null, // 接口返回的完整体测数据
|
||
radarCWidth: 300,
|
||
radarCHeight: 300,
|
||
pixelRatio: 2,
|
||
showFitnessPopup: false, // 成绩弹窗
|
||
selectedFitnessItem: null,
|
||
// 课堂报告子Tab (0=课堂 1=月 2=季度)
|
||
classroomSubTab: 0,
|
||
selectedYear: 2026,
|
||
selectedMonth: 3,
|
||
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月' },
|
||
],
|
||
mockMonthData: {},
|
||
mockQuarterData: {},
|
||
},
|
||
|
||
/**
|
||
* 生命周期函数--监听页面加载
|
||
*/
|
||
async onLoad(options) {
|
||
const now = new Date()
|
||
const nowYear = now.getFullYear()
|
||
const nowMonth = now.getMonth() + 1
|
||
const nowQuarter = Math.ceil(nowMonth / 3)
|
||
this.setData({
|
||
nowYear, nowMonth, nowQuarter,
|
||
selectedYear: nowYear,
|
||
selectedMonth: nowMonth,
|
||
selectedQuarter: nowQuarter,
|
||
pickerYear: nowYear,
|
||
})
|
||
|
||
if(wx.getStorageSync('roleId') && wx.getStorageSync('roleId') == 5 ){
|
||
this.setData({
|
||
isTrainee: true
|
||
}, () => {
|
||
this.getTrainClass(true)
|
||
this.fetchFitnessReport()
|
||
})
|
||
} else {
|
||
await this.getStudentReport()
|
||
}
|
||
},
|
||
|
||
async getStudentReport() {
|
||
const res = await getReport()
|
||
let list = []
|
||
res.data.categoryScoreList.forEach(e => {
|
||
list.push({
|
||
...e,
|
||
width: e.score >= 60 && e.score <= 80 ? (((e.score - 60) / 4)*5 + 25) + '%' : ((e.score - 60) / 0.4 + 2) + '%',
|
||
grade: e.score >= 90 ? 'grate' : e.score >= 80 ? 'good' : e.score >= 60 ? 'normal' : ''
|
||
})
|
||
})
|
||
|
||
res.data.width = res.data.totalScore >= 60 && res.data.totalScore <= 80 ? (((res.data.totalScore - 60) / 4)*5 + 25) + '%' : ((res.data.totalScore - 60) / 0.4 + 2) + '%',
|
||
res.data.grade = res.data.totalScore >= 90 ? 'grate' : res.data.totalScore >= 80 ? 'good' : res.data.totalScore >= 60 ? 'normal' : ''
|
||
res.data.totalScore = res.data.totalScore.toFixed(0)
|
||
res.data.bodyFunction = res.data.bodyFunction.toFixed(0)
|
||
res.data.bodyQuality = res.data.bodyQuality.toFixed(0)
|
||
res.data.bodyShape = res.data.bodyShape.toFixed(0)
|
||
this.setData({
|
||
report: res.data,
|
||
list
|
||
})
|
||
},
|
||
|
||
// 切换体测/课堂 Tab
|
||
switchTab(e) {
|
||
const tab = Number(e.currentTarget.dataset.tab)
|
||
if (tab === this.data.activeTab) return
|
||
this.setData({ activeTab: tab }, () => {
|
||
if (tab === 1) {
|
||
// 切到体测报告时绘制雷达图
|
||
this.drawRadarChart(this)
|
||
}
|
||
})
|
||
},
|
||
|
||
// 切换课堂报告子Tab (课堂/月/季度)
|
||
switchClassroomSubTab(e) {
|
||
const tab = Number(e.currentTarget.dataset.tab)
|
||
if (tab === this.data.classroomSubTab) return
|
||
this.setData({ classroomSubTab: tab }, () => {
|
||
if (tab === 1) this.fetchMonthReport()
|
||
else if (tab === 2) 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 } = this.data
|
||
const { startTime, endTime } = this._getMonthRange(selectedYear, selectedMonth)
|
||
try {
|
||
const res = await getStudentClassRoomReport({ type: 2, startTime, endTime })
|
||
this.setData({ mockMonthData: this._buildPeriodData(res.data) })
|
||
} catch (e) {
|
||
this.setData({ mockMonthData: {} })
|
||
}
|
||
},
|
||
|
||
// 获取季度报告
|
||
async fetchQuarterReport() {
|
||
const { selectedYear, selectedQuarter } = this.data
|
||
const { startTime, endTime } = this._getQuarterRange(selectedYear, selectedQuarter)
|
||
try {
|
||
const res = await getStudentClassRoomReport({ type: 2, startTime, endTime })
|
||
this.setData({ mockQuarterData: this._buildPeriodData(res.data) })
|
||
} catch (e) {
|
||
this.setData({ mockQuarterData: {} })
|
||
}
|
||
},
|
||
|
||
async fetchFitnessReport() {
|
||
try {
|
||
const res = await getPhysicalFitnessReport()
|
||
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,
|
||
})
|
||
} catch (e) {
|
||
this.setData({ fitnessItems: [] })
|
||
}
|
||
},
|
||
|
||
// 绘制六边形雷达图
|
||
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 ''
|
||
return rank ? score + '\n' + rank : String(score)
|
||
})
|
||
|
||
// 富文本 label:名称行 + 颜色区分的成绩行(上次/本次+等级)
|
||
const labelLines = list.map(i => {
|
||
const name = i.name || ''
|
||
const lines = []
|
||
// 名称行,超5字自动换行
|
||
if (name.length > 15) {
|
||
const mid = Math.ceil(name.length / 2)
|
||
lines.push(name.slice(0, mid))
|
||
lines.push(name.slice(mid))
|
||
} else {
|
||
lines.push(name)
|
||
}
|
||
// 成绩行:上次(蓝)/本次+等级(红)
|
||
const prevScore = i.prevScore != null ? String(i.prevScore) : '-'
|
||
const lastScore = i.lastScore != null ? String(i.lastScore) : '-'
|
||
const rank = i.rank ? i.rank : ''
|
||
const currentText = rank ? lastScore + rank : lastScore
|
||
lines.push([
|
||
{ text: prevScore, color: '#4A90E2' },
|
||
{ text: ' / ', color: '#999999' },
|
||
{ text: currentText, color: '#E74C3C' },
|
||
])
|
||
return lines
|
||
})
|
||
|
||
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', dataLabels: currentDataLabels },
|
||
{ 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, 20],
|
||
legend: {
|
||
show: true,
|
||
position: 'bottom',
|
||
float: 'center',
|
||
fontSize: 12,
|
||
lineHeight: 11,
|
||
margin: 0,
|
||
itemGap: 20,
|
||
},
|
||
extra: {
|
||
radar: {
|
||
gridType: this.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,
|
||
labelLines,
|
||
},
|
||
},
|
||
})
|
||
})
|
||
},
|
||
|
||
// 点击图标:展示成绩弹窗
|
||
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',
|
||
})
|
||
},
|
||
|
||
//获取教练课堂记录 刷新 加载更多
|
||
async getTrainClass(refresh) {
|
||
if (!this.data.hasMore && !refresh) return //非刷新且没有
|
||
const res = await getClassRoomRecord({
|
||
pageIndex: this.data.pageIndex,
|
||
PageSize: 20,
|
||
})
|
||
let newList = res.data.datas.map(item => {
|
||
return {
|
||
...item,
|
||
time: getWeekdayWithValidation(Number(item.startingEndingTime.slice(0, 4)), Number(item.startingEndingTime.slice(5, 7)), Number(item.startingEndingTime.slice(8, 10)))
|
||
}
|
||
})
|
||
this.setData({
|
||
roomList: refresh ? newList : [...this.data.roomList, ...newList],
|
||
page: this.data.page + 1,
|
||
hasMore: res.data.total > (this.data.pageIndex) * 20,
|
||
})
|
||
|
||
if(this.data.isTrainee && this.data.activeTab === 1){
|
||
this.drawRadarChart(this)
|
||
}
|
||
},
|
||
|
||
//查看报告详情
|
||
goReportDetail(e){
|
||
wx.navigateTo({
|
||
url: '/subpackage/teacher/report-detail/report-detail?id='+e.currentTarget.id ,
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 生命周期函数--监听页面初次渲染完成
|
||
*/
|
||
onReady() {
|
||
const windowInfo = wx.getWindowInfo()
|
||
const pixelRatio = windowInfo.pixelRatio
|
||
// 宽度 = 屏幕宽度 - 页面两侧 padding(30rpx×2),不计 card padding(由负 margin 抵消)
|
||
const rpxRatio = windowInfo.windowWidth / 750
|
||
const radarCWidth = Math.floor(windowInfo.windowWidth - 2 * 30 * rpxRatio)
|
||
const radarCHeight = Math.floor(radarCWidth * 1.05) // 稍高,给换行标签和 rank 留出空间
|
||
this.setData({ radarCWidth, radarCHeight, pixelRatio }, () => {
|
||
if (this.data.isTrainee && this.data.activeTab === 1) {
|
||
this.drawRadarChart(this)
|
||
}
|
||
})
|
||
},
|
||
|
||
/**
|
||
* 生命周期函数--监听页面显示
|
||
*/
|
||
onShow() {
|
||
tabService.updateIndex(this, 2)
|
||
if(wx.getStorageSync('roleId') && wx.getStorageSync('roleId') == 5){ //学员
|
||
this.setData({
|
||
isTrainee:true
|
||
})
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 生命周期函数--监听页面隐藏
|
||
*/
|
||
onHide() {
|
||
|
||
},
|
||
|
||
/**
|
||
* 生命周期函数--监听页面卸载
|
||
*/
|
||
onUnload() {
|
||
|
||
},
|
||
|
||
/**
|
||
* 页面相关事件处理函数--监听用户下拉动作
|
||
*/
|
||
async onPullDownRefresh() {
|
||
if (this.data.isTrainee) {
|
||
const { activeTab, classroomSubTab } = this.data
|
||
if (activeTab === 0) {
|
||
// 课堂报告 Tab
|
||
if (classroomSubTab === 0) {
|
||
// 课堂列表
|
||
this.setData({ pageIndex: 1, hasMore: true }, async () => {
|
||
await this.getTrainClass(true)
|
||
wx.stopPullDownRefresh()
|
||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||
})
|
||
} else if (classroomSubTab === 1) {
|
||
// 月报告
|
||
await this.fetchMonthReport()
|
||
wx.stopPullDownRefresh()
|
||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||
} else if (classroomSubTab === 2) {
|
||
// 季度报告
|
||
await this.fetchQuarterReport()
|
||
wx.stopPullDownRefresh()
|
||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||
}
|
||
} else if (activeTab === 1) {
|
||
// 体测报告 Tab
|
||
await this.fetchFitnessReport()
|
||
this.drawRadarChart(this)
|
||
wx.stopPullDownRefresh()
|
||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||
}
|
||
} else {
|
||
await this.getStudentReport()
|
||
wx.stopPullDownRefresh()
|
||
wx.showToast({ title: '刷新成功!', icon: 'none' })
|
||
}
|
||
},
|
||
|
||
/**
|
||
* 页面上拉触底事件的处理函数
|
||
*/
|
||
onReachBottom() {
|
||
this.getTrainClass()
|
||
},
|
||
|
||
/**
|
||
* 用户点击右上角分享
|
||
*/
|
||
onShareAppMessage() {
|
||
|
||
}
|
||
}) |