update:运动馆教师端
This commit is contained in:
parent
b2be523c5c
commit
1f17d4736d
@ -5,7 +5,7 @@ import {
|
||||
getReport,
|
||||
getPhysicalFitnessReport
|
||||
} from '../../../utils/serve/parent'
|
||||
import { getClassRoomRecord } from '../../../utils/serve/teacher';
|
||||
import { getClassRoomRecord, getStudentClassRoomReport } from '../../../utils/serve/teacher';
|
||||
import { getWeekdayWithValidation } from '../../../utils/utils';
|
||||
|
||||
var uChartsInstance = {};
|
||||
@ -59,34 +59,8 @@ Page({
|
||||
{ value: 3, label: '第三季度', months: '7-9月' },
|
||||
{ value: 4, label: '第四季度', months: '10-12月' },
|
||||
],
|
||||
mockMonthData: {
|
||||
heartRange: '80-116次/分',
|
||||
avgHeart: 80,
|
||||
highDuration: '80%',
|
||||
highRatio: '60%',
|
||||
jumpTotal: 706,
|
||||
jumpDuration: 3,
|
||||
jumpAvgSpeed: 235,
|
||||
jumpRecords: [
|
||||
{ type: '智能跳绳', duration: '60s', count: 248 },
|
||||
{ type: '智能跳绳', duration: '60s', count: 248 },
|
||||
{ type: '智能跳绳', duration: '60s', count: 248 },
|
||||
],
|
||||
},
|
||||
mockQuarterData: {
|
||||
heartRange: '75-122次/分',
|
||||
avgHeart: 85,
|
||||
highDuration: '72%',
|
||||
highRatio: '52%',
|
||||
jumpTotal: 2048,
|
||||
jumpDuration: 9,
|
||||
jumpAvgSpeed: 228,
|
||||
jumpRecords: [
|
||||
{ type: '智能跳绳', duration: '60s', count: 248 },
|
||||
{ type: '智能跳绳', duration: '60s', count: 700 },
|
||||
{ type: '智能跳绳', duration: '60s', count: 600 },
|
||||
],
|
||||
},
|
||||
mockMonthData: {},
|
||||
mockQuarterData: {},
|
||||
},
|
||||
|
||||
/**
|
||||
@ -156,7 +130,10 @@ Page({
|
||||
switchClassroomSubTab(e) {
|
||||
const tab = Number(e.currentTarget.dataset.tab)
|
||||
if (tab === this.data.classroomSubTab) return
|
||||
this.setData({ classroomSubTab: tab })
|
||||
this.setData({ classroomSubTab: tab }, () => {
|
||||
if (tab === 1) this.fetchMonthReport()
|
||||
else if (tab === 2) this.fetchQuarterReport()
|
||||
})
|
||||
},
|
||||
|
||||
// 月份选择器
|
||||
@ -170,7 +147,9 @@ Page({
|
||||
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.setData({ selectedMonth: month, selectedYear: pickerYear, showMonthPicker: false }, () => {
|
||||
this.fetchMonthReport()
|
||||
})
|
||||
},
|
||||
|
||||
// 季度选择器
|
||||
@ -184,7 +163,9 @@ Page({
|
||||
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.setData({ selectedQuarter: quarter, selectedYear: pickerYear, showQuarterPicker: false }, () => {
|
||||
this.fetchQuarterReport()
|
||||
})
|
||||
},
|
||||
|
||||
// 选择器年份切换
|
||||
@ -197,6 +178,68 @@ Page({
|
||||
},
|
||||
|
||||
|
||||
// 构建时间范围:月
|
||||
_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()
|
||||
@ -402,28 +445,40 @@ Page({
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
async onPullDownRefresh() {
|
||||
if(this.data.isTrainee){
|
||||
this.setData({
|
||||
pageIndex:1,
|
||||
hasMore:true
|
||||
},async ()=>{
|
||||
this.getTrainClass(true)
|
||||
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{
|
||||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||||
}
|
||||
} else {
|
||||
await this.getStudentReport()
|
||||
wx.stopPullDownRefresh()
|
||||
wx.showToast({
|
||||
title: '刷新成功!',
|
||||
icon: 'none'
|
||||
})
|
||||
wx.showToast({ title: '刷新成功!', icon: 'none' })
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -194,6 +194,9 @@
|
||||
<view class="jump-col">{{mockMonthData.jumpDuration}}</view>
|
||||
<view class="jump-col">{{mockMonthData.jumpAvgSpeed}}</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="jump-summary-wrap" style="margin-top:30rpx">
|
||||
<view class="jump-record-item" wx:for="{{mockMonthData.jumpRecords}}" wx:key="index">
|
||||
<view class="jump-record-icon-wrap">
|
||||
<image src="/static/student/train.png" class="jump-record-icon" mode="aspectFit" />
|
||||
@ -203,7 +206,7 @@
|
||||
<view class="jump-record-detail">{{item.duration}} {{item.count}}个</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 季度报告 -->
|
||||
<block wx:elif="{{classroomSubTab === 2}}">
|
||||
|
||||
@ -577,11 +577,13 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 32rpx;
|
||||
justify-content: space-evenly;
|
||||
margin-top: 42rpx;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
width: calc(50% - 8rpx);
|
||||
width: calc(50% - 20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 30rpx 26rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
@ -745,6 +747,7 @@
|
||||
padding: 0 0 60rpx;
|
||||
z-index: 201;
|
||||
transform: translateY(100%);
|
||||
padding-bottom: 220rpx;
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: 0 -8rpx 40rpx rgba(73,165,252,0.18);
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ Page({
|
||||
nowFitnessClass: {}, // 当前体测班级
|
||||
fitnessStudentList: [], // 体测学生列表
|
||||
fitnessLoading: false,
|
||||
courseLoading: false,
|
||||
},
|
||||
|
||||
|
||||
@ -66,7 +67,12 @@ Page({
|
||||
// 切换 Tab
|
||||
switchTab(e) {
|
||||
const tab = e.currentTarget.dataset.tab
|
||||
if(tab === this.data.activeTab) return
|
||||
console.log('切换tab', tab)
|
||||
this.setData({ activeTab: tab })
|
||||
if(tab == 0 && !!this.data.nowCourse.id){
|
||||
this.drawCharts(this)
|
||||
}
|
||||
},
|
||||
|
||||
// 获取体测班级列表
|
||||
@ -105,6 +111,20 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 计算课堂详情展示字段
|
||||
_buildCourseDetail(data) {
|
||||
const d = data || {}
|
||||
const age = d.age || 0
|
||||
const hrMax = 220 - age
|
||||
const moderateHR = Math.round(hrMax * 0.6)
|
||||
const highHR = Math.round(hrMax * 0.75)
|
||||
const heartRange = `${d.minHR || 0}-${d.maxHR || 0}次/分`
|
||||
const avgHeart = d.avgHR || 0
|
||||
const highDuration = `${d.highIntensity || 0}分钟`
|
||||
const highRatio = `${d.highIntensityDurationRatio || 0}%`
|
||||
return { ...d, heartRange, avgHeart, highDuration, highRatio, _moderateHR: moderateHR, _highHR: highHR }
|
||||
},
|
||||
|
||||
clickCouse(e) {
|
||||
this.setData({
|
||||
clickCouse: e.currentTarget.dataset.item
|
||||
@ -114,19 +134,17 @@ Page({
|
||||
async sureClickCouse() {
|
||||
const that = this
|
||||
if (!this.data.clickCouse.id) return app.showToast('请选择课程后再确认哦~')
|
||||
wx.showLoading({
|
||||
title: '加载中',
|
||||
})
|
||||
this.setData({ showDialog: false, courseLoading: true })
|
||||
const res = await getClassRoomReportDetails(this.data.clickCouse.id)
|
||||
const detail = this._buildCourseDetail(res.data)
|
||||
that.setData({
|
||||
nowCourse: this.data.clickCouse,
|
||||
clickCouse: {},
|
||||
showDialog: false,
|
||||
nowCourseDetail: res.data
|
||||
nowCourseDetail: detail,
|
||||
courseLoading: false,
|
||||
},()=>{
|
||||
this.drawCharts(this)
|
||||
})
|
||||
wx.hideLoading()
|
||||
},
|
||||
|
||||
showDialog() {
|
||||
@ -160,17 +178,21 @@ Page({
|
||||
console.log(isRefresh)
|
||||
console.log('aaaaaaaaaaa',refresh)
|
||||
if (!this.data.hasMore && !refresh) return //非刷新且没有
|
||||
if (refresh) this.setData({ courseLoading: true })
|
||||
const res = await getClassRoomRecord({
|
||||
pageIndex: this.data.page,
|
||||
PageSize: 20,
|
||||
})
|
||||
if((!!this.data.nowCourse && !this.data.nowCourse.id) && res.data.datas.length > 0){
|
||||
const detail = await getClassRoomReportDetails(res.data.datas[0].id)
|
||||
if(res.data.datas.length > 0){
|
||||
const detail = await getClassRoomReportDetails(this.data.nowCourse.id ? this.data.nowCourse.id : res.data.datas[0].id)
|
||||
this.setData({
|
||||
nowCourseDetail:this.data.nowCourse.id ? this.data.nowCourseDetail: detail.data
|
||||
nowCourseDetail: this.data.nowCourse.id ? this.data.nowCourseDetail: this._buildCourseDetail(detail.data),
|
||||
courseLoading: false,
|
||||
},()=>{
|
||||
that.drawCharts(that)
|
||||
})
|
||||
} else {
|
||||
if (refresh) this.setData({ courseLoading: false })
|
||||
}
|
||||
this.setData({
|
||||
courseList: refresh ? res.data.datas : [...this.data.courseList, ...res.data.datas],
|
||||
@ -256,8 +278,9 @@ Page({
|
||||
animation: true,
|
||||
background: "#FFFFFF",
|
||||
color: that.data.chartType == 1 ? ["#FFA30C"] : that.data.chartType == 2 ? ["#6CB7FB"] : ["#FF4949"],
|
||||
padding: [15, 15, 10, 0],
|
||||
padding: [15, 15, 10, 15],
|
||||
enableScroll: true,
|
||||
enableMarkLine: true,
|
||||
// touchMoveLimit: 24,
|
||||
legend: {
|
||||
show: false,
|
||||
@ -284,6 +307,34 @@ Page({
|
||||
// }]
|
||||
},
|
||||
extra: {
|
||||
markLine: {
|
||||
type: 'dash',
|
||||
dashLength: 6,
|
||||
data: [
|
||||
{
|
||||
value: that.data.nowCourseDetail._moderateHR,
|
||||
lineColor: '#FFA30C',
|
||||
showLabel: true,
|
||||
labelText: '中强度',
|
||||
labelFontColor: '#FFA30C',
|
||||
labelBgColor: '#FFF8EC',
|
||||
labelBgOpacity: 0.9,
|
||||
labelAlign: 'left',
|
||||
labelFontSize: 11
|
||||
},
|
||||
{
|
||||
value: that.data.nowCourseDetail._highHR,
|
||||
lineColor: '#FF4949',
|
||||
showLabel: true,
|
||||
labelText: '高强度',
|
||||
labelFontColor: '#FF4949',
|
||||
labelBgColor: '#FFF0F0',
|
||||
labelBgOpacity: 0.9,
|
||||
labelAlign: 'left',
|
||||
labelFontSize: 11
|
||||
}
|
||||
]
|
||||
},
|
||||
line: {
|
||||
type: "curve",
|
||||
width: 2,
|
||||
@ -372,24 +423,19 @@ Page({
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
this.setData({
|
||||
page:1,
|
||||
hasMore:true
|
||||
},()=>{
|
||||
if(this.data.isTrainer){
|
||||
if(this.data.activeTab === 0){
|
||||
this.getTrainClass(true)
|
||||
}else if(this.data.activeTab === 1){
|
||||
this.getFitnessStudents()
|
||||
}
|
||||
}else{
|
||||
this.getClass()
|
||||
async onPullDownRefresh() {
|
||||
this.setData({ page: 1, hasMore: true })
|
||||
if (this.data.isTrainer) {
|
||||
if (this.data.activeTab === 0) {
|
||||
await this.getTrainClass(true)
|
||||
} else if (this.data.activeTab === 1) {
|
||||
await this.getFitnessStudents()
|
||||
}
|
||||
wx.stopPullDownRefresh()
|
||||
app.showToast('刷新成功')
|
||||
})
|
||||
|
||||
} else {
|
||||
await this.getClass()
|
||||
}
|
||||
wx.stopPullDownRefresh()
|
||||
app.showToast('刷新成功')
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@ -75,7 +75,29 @@
|
||||
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/downn-b.png" mode="" />
|
||||
</view>
|
||||
|
||||
<!-- 骨架屏 -->
|
||||
<block wx:if="{{courseLoading}}">
|
||||
<view class="course-skeleton">
|
||||
<view class="cs-chart-wrap">
|
||||
<view class="cs-chart-label"></view>
|
||||
<view class="cs-chart"></view>
|
||||
</view>
|
||||
<view class="cs-grid">
|
||||
<view class="cs-card"></view>
|
||||
<view class="cs-card"></view>
|
||||
<view class="cs-card"></view>
|
||||
<view class="cs-card"></view>
|
||||
</view>
|
||||
<view class="cs-list-title"></view>
|
||||
<view class="cs-row"></view>
|
||||
<view class="cs-row"></view>
|
||||
<view class="cs-row"></view>
|
||||
<view class="cs-row"></view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 正式内容 -->
|
||||
<block wx:else>
|
||||
<view class="title">
|
||||
<view class="title-before"></view>心率统计
|
||||
</view>
|
||||
@ -85,22 +107,40 @@
|
||||
<canvas canvas-id="mycanvas" type="2d" id="mycanvas" style="width: {{cWidth}}px;height: {{cHeight}}px;" bindtouchstart="touchstart" bindtouchmove="touchmove" bindtouchend="touchend" />
|
||||
</view>
|
||||
|
||||
<!-- <view class="hr-legend">
|
||||
<view class="hr-legend-item">
|
||||
<view class="hr-legend-line hr-legend-moderate"></view>
|
||||
<text>中等强度 {{nowCourseDetail._moderateHR}}次/分</text>
|
||||
</view>
|
||||
<view class="hr-legend-item">
|
||||
<view class="hr-legend-line hr-legend-high"></view>
|
||||
<text>高强度 {{nowCourseDetail._highHR}}次/分</text>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<view class="heart-box">
|
||||
<view class="heart-item ">
|
||||
<view>最大心率</view>
|
||||
<view class="heart-text3">{{nowCourseDetail.maxHR || '- -'}}</view>
|
||||
<view class="stat-grid">
|
||||
<view class="stat-card stat-card-1">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">心率范围</text>
|
||||
<text class="stat-card-value">{{nowCourseDetail.heartRange}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-2">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">平均心率</text>
|
||||
<text class="stat-card-value">{{nowCourseDetail.avgHeart}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-3">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">中高强度时长</text>
|
||||
<text class="stat-card-value">{{nowCourseDetail.highDuration}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-4">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">中高时间占比</text>
|
||||
<text class="stat-card-value">{{nowCourseDetail.highRatio}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="heart-item heart-min">
|
||||
<view>最小心率</view>
|
||||
<view class="heart-text2">{{nowCourseDetail.minHR || '- -'}}</view>
|
||||
</view>
|
||||
<view class="heart-item heart-max">
|
||||
<view>平均心率</view>
|
||||
<view class="heart-text1">{{nowCourseDetail.avgHR || '- -'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="title">
|
||||
<view class="title-before"></view>学生列表
|
||||
@ -114,6 +154,7 @@
|
||||
</view>
|
||||
<mp-icon icon="arrow" size="{{13}}" color="#8c8c8c"></mp-icon>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<mp-half-screen-dialog bindbuttontap="buttontap" show="{{showDialog}}" title="课堂记录">
|
||||
<view slot="desc">
|
||||
|
||||
@ -29,6 +29,74 @@
|
||||
box-shadow: 0 4rpx 16rpx rgba(38, 165, 246, 0.35);
|
||||
}
|
||||
|
||||
/* ===== 心率图例 ===== */
|
||||
.hr-legend {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 30rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
.hr-legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
.hr-legend-line {
|
||||
width: 36rpx;
|
||||
height: 3rpx;
|
||||
margin-right: 10rpx;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
.hr-legend-moderate { background-color: #FFA30C; }
|
||||
.hr-legend-high { background-color: #FF4949; }
|
||||
|
||||
/* ===== 心率统计卡片 ===== */
|
||||
.stat-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
justify-content: space-evenly;
|
||||
margin: 24rpx 30rpx;
|
||||
}
|
||||
.stat-card {
|
||||
width: calc(50% - 20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 30rpx 26rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 28rpx rgba(0,0,0,0.13);
|
||||
}
|
||||
.stat-card-1 { background: linear-gradient(135deg, #ff5f6d 0%, #ffc371 100%); }
|
||||
.stat-card-2 { background: linear-gradient(135deg, #0fd850 0%, #00c3a9 100%); }
|
||||
.stat-card-3 { background: linear-gradient(135deg, #2980b9 0%, #6dd5fa 100%); }
|
||||
.stat-card-4 { background: linear-gradient(135deg, #7f7fd5 0%, #86a8e7 100%); }
|
||||
.stat-card-bar {
|
||||
position: absolute;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.13);
|
||||
bottom: -50rpx;
|
||||
right: -40rpx;
|
||||
}
|
||||
.stat-card-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255,255,255,0.88);
|
||||
}
|
||||
.stat-card-value {
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
word-break: break-all;
|
||||
line-height: 1.2;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
/* ===== 体测报告 header ===== */
|
||||
.fitness-header {
|
||||
display: flex;
|
||||
@ -150,6 +218,79 @@
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
/* ===== 课堂报告骨架屏 ===== */
|
||||
@keyframes cs-shimmer {
|
||||
0% { background-position: -750rpx 0; }
|
||||
100% { background-position: 750rpx 0; }
|
||||
}
|
||||
|
||||
.course-skeleton {
|
||||
padding: 0 30rpx 40rpx;
|
||||
margin-top: 45rpx;
|
||||
}
|
||||
|
||||
.cs-chart-wrap {
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 24rpx 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.cs-chart-label {
|
||||
width: 80rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
|
||||
background-size: 750rpx 100%;
|
||||
animation: cs-shimmer 1.4s infinite linear;
|
||||
}
|
||||
|
||||
.cs-chart {
|
||||
height: 280rpx;
|
||||
border-radius: 16rpx;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
|
||||
background-size: 750rpx 100%;
|
||||
animation: cs-shimmer 1.4s infinite linear;
|
||||
}
|
||||
|
||||
.cs-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
justify-content: space-evenly;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.cs-card {
|
||||
width: calc(50% - 20rpx);
|
||||
height: 140rpx;
|
||||
border-radius: 28rpx;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
|
||||
background-size: 750rpx 100%;
|
||||
animation: cs-shimmer 1.4s infinite linear;
|
||||
}
|
||||
|
||||
.cs-list-title {
|
||||
width: 160rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 24rpx;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
|
||||
background-size: 750rpx 100%;
|
||||
animation: cs-shimmer 1.4s infinite linear;
|
||||
}
|
||||
|
||||
.cs-row {
|
||||
height: 100rpx;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
|
||||
background-size: 750rpx 100%;
|
||||
animation: cs-shimmer 1.4s 0.1s infinite linear;
|
||||
}
|
||||
|
||||
/* ===== 加载中 ===== */
|
||||
.fitness-loading {
|
||||
display: flex;
|
||||
|
||||
@ -38,8 +38,16 @@ Page({
|
||||
const hrMax = 220 - age
|
||||
const moderateHR = Math.round(hrMax * 0.6) // 中等强度下限
|
||||
const highHR = Math.round(hrMax * 0.75) // 高强度下限
|
||||
const d = res.data || {}
|
||||
const heartRange = `${d.minHR || 0}-${d.maxHR || 0}次/分`
|
||||
const avgHeart = d.avgHR || 0
|
||||
const highDuration = `${d.highIntensity || 0}分钟`
|
||||
const highRatio = `${d.highIntensityDurationRatio || 0}%`
|
||||
const jumpTotal = d.totalCount || 0
|
||||
const jumpDuration = d.totalDuration || 0
|
||||
const jumpAvgSpeed = d.avgSpeed || 0
|
||||
this.setData({
|
||||
reportDetail: res.data,
|
||||
reportDetail: { ...d, heartRange, avgHeart, highDuration, highRatio, jumpTotal, jumpDuration, jumpAvgSpeed },
|
||||
moderateHR,
|
||||
highHR,
|
||||
loading: false
|
||||
@ -86,7 +94,7 @@ Page({
|
||||
enableMarkLine: true,
|
||||
background: "#FFFFFF",
|
||||
color: that.data.chartType == 1 ? ["#FFA30C"] : that.data.chartType == 2 ? ["#6CB7FB"] : ["#FF4949"],
|
||||
padding: [15, 15, 10, 0],
|
||||
padding: [15, 15, 10, 15],
|
||||
enableScroll: true,
|
||||
// touchMoveLimit: 24,
|
||||
legend: {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<!--subpackage/teacher/report-detail/report-detail.wxml-->
|
||||
<!-- subpackage/teacher/report-detail/report-detail.wxml -->
|
||||
<view class="page-content">
|
||||
<mp-bar background="linear-gradient(190deg, #49a5fc 0%, #f7f7f7 800rpx, #f7f7f7 100%)" title="统计" color="#fff"></mp-bar>
|
||||
|
||||
<!-- 骨架屏 -->
|
||||
<view wx:if="{{loading}}" class="skeleton">
|
||||
<view class="skeleton-title">
|
||||
@ -38,31 +37,29 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 正式内容 -->
|
||||
<block wx:if="{{!loading}}">
|
||||
|
||||
<view class="title">
|
||||
<view class="title-before"></view>心率统计
|
||||
</view>
|
||||
<view class="card canvasCard">
|
||||
<view>次/分</view>
|
||||
<view class="charts" style="width: {{cWidth}}px;height: {{cHeight}}px;">
|
||||
<canvas canvas-id="mycanvas" type="2d" id="mycanvas" style="width: {{cWidth}}px;height: {{cHeight}}px;" bindtouchstart="touchstart" bindtouchmove="touchmove" bindtouchend="touchend" />
|
||||
<view class="title">
|
||||
<view class="title-before"></view>
|
||||
心率统计
|
||||
</view>
|
||||
<view class="hr-legend">
|
||||
<view class="hr-legend-item">
|
||||
<view class="hr-legend-line hr-legend-moderate"></view>
|
||||
<text>中等强度 {{moderateHR}}次/分</text>
|
||||
<view class="card canvasCard">
|
||||
<view>次/分</view>
|
||||
<view class="charts" style="width: {{cWidth}}px;height: {{cHeight}}px;">
|
||||
<canvas canvas-id="mycanvas" type="2d" id="mycanvas" style="width: {{cWidth}}px;height: {{cHeight}}px;" bindtouchstart="touchstart" bindtouchmove="touchmove" bindtouchend="touchend" />
|
||||
</view>
|
||||
<view class="hr-legend-item">
|
||||
<view class="hr-legend-line hr-legend-high"></view>
|
||||
<text>高强度 {{highHR}}次/分</text>
|
||||
<view class="hr-legend">
|
||||
<view class="hr-legend-item">
|
||||
<view class="hr-legend-line hr-legend-moderate"></view>
|
||||
<text>中等强度 {{moderateHR}}次/分</text>
|
||||
</view>
|
||||
<view class="hr-legend-item">
|
||||
<view class="hr-legend-line hr-legend-high"></view>
|
||||
<text>高强度 {{highHR}}次/分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="tp">
|
||||
<!-- <view class="tp">
|
||||
<view class="table-t">
|
||||
<view class="no-b no-r">最大心率</view>
|
||||
<view class="no-b no-r">最小心率</view>
|
||||
@ -74,43 +71,66 @@
|
||||
<view >{{reportDetail.avgHR || '- -'}}</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<view class="heart-box">
|
||||
<view class="heart-item ">
|
||||
<view>最大心率</view>
|
||||
<view class="heart-text3">{{reportDetail.maxHR || '- -'}}</view>
|
||||
<view class="stat-grid">
|
||||
<view class="stat-card stat-card-1">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">心率范围</text>
|
||||
<text class="stat-card-value">{{reportDetail.heartRange}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-2">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">平均心率</text>
|
||||
<text class="stat-card-value">{{reportDetail.avgHeart}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-3">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">中高强度时长</text>
|
||||
<text class="stat-card-value">{{reportDetail.highDuration}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-4">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">中高时间占比</text>
|
||||
<text class="stat-card-value">{{reportDetail.highRatio}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="heart-item heart-min">
|
||||
<view>最小心率</view>
|
||||
<view class="heart-text2">{{reportDetail.minHR || '- -'}}</view>
|
||||
<view class="title">
|
||||
<view class="title-before"></view>
|
||||
跳绳记录
|
||||
</view>
|
||||
<view class="heart-item heart-max">
|
||||
<view>平均心率</view>
|
||||
<view class="heart-text1">{{reportDetail.avgHR || '- -'}}</view>
|
||||
<view class="jump-summary-wrap">
|
||||
<view class="jump-summary-header">
|
||||
<view class="jump-col">个数</view>
|
||||
<view class="jump-col">
|
||||
时长
|
||||
<text class="jump-unit">(分钟)</text>
|
||||
</view>
|
||||
<view class="jump-col">
|
||||
平均速度
|
||||
<text class="jump-unit">(个/分钟)</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="jump-summary-data">
|
||||
<view class="jump-col">{{reportDetail.jumpTotal}}</view>
|
||||
<view class="jump-col">{{reportDetail.jumpDuration}}</view>
|
||||
<view class="jump-col">{{reportDetail.jumpAvgSpeed}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="title">
|
||||
<view class="title-before"></view>跳绳记录
|
||||
</view>
|
||||
|
||||
<view class="homework-item" wx:for="{{reportDetail.jumpRecordList}}" wx:key="index">
|
||||
<view class="top">
|
||||
<view class="left">
|
||||
<view class="img-box">
|
||||
<image class="jump-img" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/icon/Rope-Skipping.png" mode="" />
|
||||
</view>
|
||||
<view class="desc">
|
||||
<view class="statu">
|
||||
{{item.jumpType}}
|
||||
<view class="homework-item" wx:for="{{reportDetail.jumpRecordList}}" wx:key="index">
|
||||
<view class="top">
|
||||
<view class="left">
|
||||
<view class="img-box">
|
||||
<image class="jump-img" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/icon/Rope-Skipping.png" mode="" />
|
||||
</view>
|
||||
<view class="desc">
|
||||
<view class="statu">{{item.jumpType}}</view>
|
||||
<view>
|
||||
{{item.totalDuration}}s
|
||||
<text style="margin-left: 30rpx;">{{item.totalCount}}个</text>
|
||||
</view>
|
||||
</view>
|
||||
<view>{{item.totalDuration}}s <text style="margin-left: 30rpx;">{{item.totalCount}}个</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{reportDetail.jumpRecordList.length <= 0}}" class="no">暂无记录~</view>
|
||||
|
||||
<view wx:if="{{reportDetail.jumpRecordList.length <= 0}}" class="no">暂无记录~</view>
|
||||
</block>
|
||||
</view>
|
||||
@ -539,4 +539,166 @@
|
||||
.skeleton-list-line-short {
|
||||
width: 50%;
|
||||
height: 26rpx;
|
||||
}
|
||||
|
||||
/* ===== 统计指标网格(2列)===== */
|
||||
.stat-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin: 0 30rpx;
|
||||
margin-bottom: 32rpx;
|
||||
justify-content: space-evenly;
|
||||
margin-top: 42rpx;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
width: calc(50% - 20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 30rpx 26rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 28rpx rgba(0,0,0,0.13);
|
||||
}
|
||||
|
||||
.stat-card-1 { background: linear-gradient(135deg, #ff5f6d 0%, #ffc371 100%); }
|
||||
.stat-card-2 { background: linear-gradient(135deg, #0fd850 0%, #00c3a9 100%); }
|
||||
.stat-card-3 { background: linear-gradient(135deg, #2980b9 0%, #6dd5fa 100%); }
|
||||
.stat-card-4 { background: linear-gradient(135deg, #7f7fd5 0%, #86a8e7 100%); }
|
||||
|
||||
/* 装饰圆 */
|
||||
.stat-card-bar {
|
||||
position: absolute;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.13);
|
||||
bottom: -50rpx;
|
||||
right: -40rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-card-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255,255,255,0.88);
|
||||
font-weight: 400;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stat-card-value {
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
padding: 0;
|
||||
word-break: break-all;
|
||||
line-height: 1.2;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
.stat-card-1 .stat-card-value,
|
||||
.stat-card-2 .stat-card-value,
|
||||
.stat-card-3 .stat-card-value,
|
||||
.stat-card-4 .stat-card-value { color: #fff; }
|
||||
|
||||
|
||||
/* ===== 跳绳统计 ===== */
|
||||
.jump-tip {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.jump-summary-wrap {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(73,165,252,0.1);
|
||||
overflow: hidden;
|
||||
margin:20rpx 30rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.jump-summary-header {
|
||||
display: flex;
|
||||
background: #1d78e8;
|
||||
padding: 18rpx 0;
|
||||
}
|
||||
|
||||
.jump-summary-data {
|
||||
display: flex;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #f0f4f8;
|
||||
}
|
||||
|
||||
.jump-col {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.jump-summary-data .jump-col {
|
||||
color: #18394D;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.jump-unit {
|
||||
font-size: 20rpx;
|
||||
color: rgba(255,255,255,0.85);
|
||||
}
|
||||
|
||||
.jump-record-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 24rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f4f8;
|
||||
}
|
||||
|
||||
.jump-record-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.jump-record-icon-wrap {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #5ab4ff, #1d78e8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.jump-record-icon {
|
||||
width: 54rpx;
|
||||
height: 54rpx;
|
||||
filter: brightness(10);
|
||||
}
|
||||
|
||||
.jump-record-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.jump-record-tag {
|
||||
display: inline-block;
|
||||
background: #1d78e8;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 20rpx;
|
||||
border-radius: 30rpx;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.jump-record-detail {
|
||||
font-size: 26rpx;
|
||||
color: #18394D;
|
||||
padding-left: 4rpx;
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
// 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 = {};
|
||||
|
||||
@ -24,17 +25,59 @@ Page({
|
||||
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: '加载中' });
|
||||
this.setData({ studentNo: options.studentNo || '' });
|
||||
await Promise.all([
|
||||
this._loadFitnessReport(options.studentNo),
|
||||
this.fetchMonthReport(),
|
||||
])
|
||||
wx.hideLoading();
|
||||
},
|
||||
|
||||
async _loadFitnessReport(studentNo) {
|
||||
try {
|
||||
const res = await getReport({ studentNo: options.studentNo });
|
||||
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('跑')));
|
||||
|
||||
// 雷达图根据 gradeId 过滤不适用的项目
|
||||
const gradeId = list.length > 0 ? list[0].gradeId : null;
|
||||
let radarList = list;
|
||||
if (gradeId !== null && gradeId !== undefined) {
|
||||
@ -51,19 +94,14 @@ Page({
|
||||
...item,
|
||||
unit: UNIT_MAP[item.name] || '',
|
||||
}));
|
||||
this.setData({
|
||||
fitnessReport: radarList,
|
||||
fitnessItems,
|
||||
}, () => {
|
||||
// 如果 onReady 已经触发,数据到位后立即绘图
|
||||
if (this.data.readyFired) {
|
||||
this.setData({ fitnessReport: radarList, fitnessItems }, () => {
|
||||
if (this.data.readyFired && this.data.activeTab === 1) {
|
||||
this.drawRadarChart(this);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
this.setData({ fitnessItems: [] });
|
||||
}
|
||||
wx.hideLoading();
|
||||
},
|
||||
|
||||
onReady() {
|
||||
@ -73,16 +111,137 @@ Page({
|
||||
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) {
|
||||
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 || [];
|
||||
// 长文本自动换行(超过5字在中间插入换行)
|
||||
const categories = list.map(i => {
|
||||
const name = i.name || '';
|
||||
if (name.length > 5) {
|
||||
@ -93,7 +252,6 @@ Page({
|
||||
});
|
||||
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 : '';
|
||||
@ -174,7 +332,193 @@ Page({
|
||||
onShow() {},
|
||||
onHide() {},
|
||||
onUnload() {},
|
||||
onPullDownRefresh() {},
|
||||
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() {},
|
||||
// })
|
||||
@ -1,41 +1,156 @@
|
||||
<!--subpackage/teacher/student-fitness-detail/student-fitness-detail.wxml-->
|
||||
<view class="page-wrap" style="padding: 0 30rpx; padding-bottom: 300rpx; box-sizing: border-box;">
|
||||
<mp-bar background="linear-gradient(190deg, #49a5fc 0%, #f5f8ff 700rpx, #f5f8ff 100%)" title="体测报告" color="#fff" back="{{true}}"></mp-bar>
|
||||
<mp-bar background="linear-gradient(190deg, #49a5fc 0%, #f5f8ff 700rpx, #f5f8ff 100%)" title="学生报告" color="#fff" back="{{true}}"></mp-bar>
|
||||
|
||||
<!-- 体测报告主体 -->
|
||||
<view class="fitness-card">
|
||||
<view class="fitness-chart-title">体测成绩对比</view>
|
||||
<view class="radar-wrap" style="width:{{radarCWidth}}px;height:{{radarCHeight}}px;">
|
||||
<canvas
|
||||
type="2d"
|
||||
id="radarCanvas"
|
||||
canvas-id="radarCanvas"
|
||||
style="width:{{radarCWidth}}px;height:{{radarCHeight}}px;"
|
||||
/>
|
||||
</view>
|
||||
<!-- 主 Tab 切换 -->
|
||||
<view class="tab-switcher">
|
||||
<view class="tab-btn {{activeTab === 0 ? 'tab-btn-active' : ''}}" data-tab="0" bind:tap="switchTab">课堂报告</view>
|
||||
<view class="tab-btn {{activeTab === 1 ? 'tab-btn-active' : ''}}" data-tab="1" bind:tap="switchTab">体测报告</view>
|
||||
</view>
|
||||
|
||||
<!-- 六项指标卡片 -->
|
||||
<view class="fitness-grid">
|
||||
<view
|
||||
class="fitness-item"
|
||||
wx:for="{{fitnessItems}}"
|
||||
wx:key="categoryValue"
|
||||
data-item="{{item}}"
|
||||
bind:tap="showFitnessScore"
|
||||
>
|
||||
<image src="{{item.imgUrl}}" class="fitness-icon" mode="aspectFit" />
|
||||
<view class="fitness-name">{{item.name}}</view>
|
||||
<view class="fitness-score">{{item.lastScore}}<text class="fitness-unit"> {{item.unit}}</text></view>
|
||||
<!-- 课堂报告 -->
|
||||
<block wx:if="{{activeTab === 0}}">
|
||||
<!-- 子Tab: 月 / 季度 -->
|
||||
<view class="sub-tab-switcher">
|
||||
<view class="sub-tab-btn {{classroomSubTab === 0 ? 'sub-tab-active' : ''}}" data-tab="0" bind:tap="switchClassroomSubTab">月</view>
|
||||
<view class="sub-tab-btn {{classroomSubTab === 1 ? 'sub-tab-active' : ''}}" data-tab="1" bind:tap="switchClassroomSubTab">季度</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view wx:if="{{fitnessItems.length === 0}}" class="empty-tip">暂无体测数据</view>
|
||||
<!-- 月报告 -->
|
||||
<block wx:if="{{classroomSubTab === 0}}">
|
||||
<view class="period-selector" bind:tap="openMonthPicker">
|
||||
<text class="period-text">{{selectedYear}}年{{selectedMonth}}月</text>
|
||||
<text class="period-caret">▾</text>
|
||||
</view>
|
||||
<view class="stat-section-header-wrap"><view class="stat-section-header">心率统计</view></view>
|
||||
<view class="stat-grid">
|
||||
<view class="stat-card stat-card-1">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">心率范围</text>
|
||||
<text class="stat-card-value">{{monthData.heartRange}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-2">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">平均心率</text>
|
||||
<text class="stat-card-value">{{monthData.avgHeart}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-3">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">中高强度时长</text>
|
||||
<text class="stat-card-value">{{monthData.highDuration}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-4">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">中高时间占比</text>
|
||||
<text class="stat-card-value">{{monthData.highRatio}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-section-header-wrap"><view class="stat-section-header">跳绳统计</view></view>
|
||||
<view class="jump-tip">训练记录统计:仅智能跳绳,不含普通跳绳</view>
|
||||
<view class="jump-summary-wrap">
|
||||
<view class="jump-summary-header">
|
||||
<view class="jump-col">个数</view>
|
||||
<view class="jump-col">时长<text class="jump-unit">(分钟)</text></view>
|
||||
<view class="jump-col">平均速度<text class="jump-unit">(个/分钟)</text></view>
|
||||
</view>
|
||||
<view class="jump-summary-data">
|
||||
<view class="jump-col">{{monthData.jumpTotal}}</view>
|
||||
<view class="jump-col">{{monthData.jumpDuration}}</view>
|
||||
<view class="jump-col">{{monthData.jumpAvgSpeed}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="jump-summary-wrap" style="margin-top:30rpx">
|
||||
<view class="jump-record-item" wx:for="{{monthData.jumpRecords}}" wx:key="index">
|
||||
<view class="jump-record-icon-wrap">
|
||||
<image src="/static/student/train.png" class="jump-record-icon" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="jump-record-main">
|
||||
<view class="jump-record-tag">{{item.type}}</view>
|
||||
<view class="jump-record-detail">{{item.duration}} {{item.count}}个</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<!-- 季度报告 -->
|
||||
<block wx:elif="{{classroomSubTab === 1}}">
|
||||
<view class="period-selector" bind:tap="openQuarterPicker">
|
||||
<text class="period-text">{{selectedYear}}年 第{{selectedQuarter}}季度</text>
|
||||
<text class="period-caret">▾</text>
|
||||
</view>
|
||||
<view class="stat-section-header-wrap"><view class="stat-section-header">心率统计</view></view>
|
||||
<view class="stat-grid">
|
||||
<view class="stat-card stat-card-1">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">心率范围</text>
|
||||
<text class="stat-card-value">{{quarterData.heartRange}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-2">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">平均心率</text>
|
||||
<text class="stat-card-value">{{quarterData.avgHeart}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-3">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">中高强度时长</text>
|
||||
<text class="stat-card-value">{{quarterData.highDuration}}</text>
|
||||
</view>
|
||||
<view class="stat-card stat-card-4">
|
||||
<view class="stat-card-bar"></view>
|
||||
<text class="stat-card-label">中高时间占比</text>
|
||||
<text class="stat-card-value">{{quarterData.highRatio}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-section-header-wrap"><view class="stat-section-header">跳绳统计</view></view>
|
||||
<view class="jump-tip">训练记录统计:仅智能跳绳,不含普通跳绳</view>
|
||||
<view class="jump-summary-wrap">
|
||||
<view class="jump-summary-header">
|
||||
<view class="jump-col">个数</view>
|
||||
<view class="jump-col">时长<text class="jump-unit">(分钟)</text></view>
|
||||
<view class="jump-col">平均速度<text class="jump-unit">(个/分钟)</text></view>
|
||||
</view>
|
||||
<view class="jump-summary-data">
|
||||
<view class="jump-col">{{quarterData.jumpTotal}}</view>
|
||||
<view class="jump-col">{{quarterData.jumpDuration}}</view>
|
||||
<view class="jump-col">{{quarterData.jumpAvgSpeed}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="jump-summary-wrap" style="margin-top:30rpx">
|
||||
<view class="jump-record-item" wx:for="{{quarterData.jumpRecords}}" wx:key="index">
|
||||
<view class="jump-record-icon-wrap">
|
||||
<image src="/static/student/train.png" class="jump-record-icon" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="jump-record-main">
|
||||
<view class="jump-record-tag">{{item.type}}</view>
|
||||
<view class="jump-record-detail">{{item.duration}} {{item.count}}个</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</block>
|
||||
|
||||
<!-- 体测报告 -->
|
||||
<block wx:elif="{{activeTab === 1}}">
|
||||
<view class="fitness-card">
|
||||
<view class="fitness-chart-title">体测成绩对比</view>
|
||||
<view class="radar-wrap" style="width:{{radarCWidth}}px;height:{{radarCHeight}}px;">
|
||||
<canvas type="2d" id="radarCanvas" canvas-id="radarCanvas" style="width:{{radarCWidth}}px;height:{{radarCHeight}}px;" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="fitness-grid">
|
||||
<view class="fitness-item" wx:for="{{fitnessItems}}" wx:key="categoryValue" data-item="{{item}}" bind:tap="showFitnessScore">
|
||||
<image src="{{item.imgUrl}}" class="fitness-icon" mode="aspectFit" />
|
||||
<view class="fitness-name">{{item.name}}</view>
|
||||
<view class="fitness-score">{{item.lastScore}}<text class="fitness-unit"> {{item.unit}}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{fitnessItems.length === 0}}" class="empty-tip">暂无体测数据</view>
|
||||
</block>
|
||||
|
||||
<!-- 成绩弹窗遮罩 -->
|
||||
<view class="popup-mask" wx:if="{{showFitnessPopup}}" bind:tap="closeFitnessPopup"></view>
|
||||
|
||||
<!-- 成绩弹窗 -->
|
||||
<view class="score-popup {{showFitnessPopup ? 'popup-show' : ''}}">
|
||||
<view class="popup-header">
|
||||
@ -43,7 +158,6 @@
|
||||
<text class="popup-title">{{selectedFitnessItem.name}}</text>
|
||||
<view class="popup-close" bind:tap="closeFitnessPopup">✕</view>
|
||||
</view>
|
||||
|
||||
<view class="score-rows">
|
||||
<view class="score-row">
|
||||
<view class="score-label">本次成绩</view>
|
||||
@ -53,17 +167,50 @@
|
||||
<view class="score-label">上次成绩</view>
|
||||
<view class="score-value">{{selectedFitnessItem.prevScore}}<text class="score-unit"> {{selectedFitnessItem.unit}}</text></view>
|
||||
</view>
|
||||
<!-- <view class="score-divider"></view>
|
||||
<view class="score-row">
|
||||
<view class="score-label">班级平均</view>
|
||||
<view class="score-value">{{selectedFitnessItem.classAvgScore}}<text class="score-unit"> {{selectedFitnessItem.unit}}</text></view>
|
||||
</view>
|
||||
<view class="score-row">
|
||||
<view class="score-label">机构平均</view>
|
||||
<view class="score-value">{{selectedFitnessItem.schoolAvgScore}}<text class="score-unit"> {{selectedFitnessItem.unit}}</text></view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
<view class="popup-btn" bind:tap="goFitnessDetail">查看详细趋势</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 月份选择弹窗 -->
|
||||
<view class="picker-mask" wx:if="{{showMonthPicker}}" bind:tap="closeMonthPicker"></view>
|
||||
<view class="picker-sheet {{showMonthPicker ? 'picker-sheet-show' : ''}}">
|
||||
<view class="picker-header">
|
||||
<text class="picker-cancel" bind:tap="closeMonthPicker">取消</text>
|
||||
<text class="picker-title">选择月份</text>
|
||||
<text class="picker-confirm" style="visibility:hidden">确定</text>
|
||||
</view>
|
||||
<view class="picker-year-row">
|
||||
<view class="picker-year-arrow" bind:tap="prevPickerYear">‹</view>
|
||||
<text class="picker-year-text">{{pickerYear}}年</text>
|
||||
<view class="picker-year-arrow {{pickerYear >= nowYear ? 'picker-year-arrow-disabled' : ''}}" bind:tap="nextPickerYear">›</view>
|
||||
</view>
|
||||
<view class="picker-month-grid">
|
||||
<view class="picker-month-item {{(item === selectedMonth && pickerYear === selectedYear) ? 'picker-item-active' : ''}} {{(pickerYear === nowYear && item > nowMonth) ? 'picker-item-disabled' : ''}}"
|
||||
wx:for="{{monthList}}" wx:key="*this" data-month="{{item}}" bind:tap="selectMonth">
|
||||
{{item}}月
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 季度选择弹窗 -->
|
||||
<view class="picker-mask" wx:if="{{showQuarterPicker}}" bind:tap="closeQuarterPicker"></view>
|
||||
<view class="picker-sheet {{showQuarterPicker ? 'picker-sheet-show' : ''}}">
|
||||
<view class="picker-header">
|
||||
<text class="picker-cancel" bind:tap="closeQuarterPicker">取消</text>
|
||||
<text class="picker-title">选择季度</text>
|
||||
<text class="picker-confirm" style="visibility:hidden">确定</text>
|
||||
</view>
|
||||
<view class="picker-year-row">
|
||||
<view class="picker-year-arrow" bind:tap="prevPickerYear">‹</view>
|
||||
<text class="picker-year-text">{{pickerYear}}年</text>
|
||||
<view class="picker-year-arrow {{pickerYear >= nowYear ? 'picker-year-arrow-disabled' : ''}}" bind:tap="nextPickerYear">›</view>
|
||||
</view>
|
||||
<view class="picker-quarter-list">
|
||||
<view class="picker-quarter-item {{(item.value === selectedQuarter && pickerYear === selectedYear) ? 'picker-item-active' : ''}} {{(pickerYear === nowYear && item.value > nowQuarter) ? 'picker-item-disabled' : ''}}"
|
||||
wx:for="{{quarterList}}" wx:key="value" data-quarter="{{item.value}}" bind:tap="selectQuarter">
|
||||
<text class="picker-quarter-label">{{item.label}}</text>
|
||||
<text class="picker-quarter-months">{{item.months}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -375,3 +375,394 @@
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
/* ===== 主 Tab 切换 ===== */
|
||||
.tab-switcher {
|
||||
display: flex;
|
||||
background: #e8f0fe;
|
||||
border-radius: 60rpx;
|
||||
padding: 6rpx;
|
||||
margin: 24rpx 0 0;
|
||||
}
|
||||
.tab-btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
border-radius: 54rpx;
|
||||
font-size: 28rpx;
|
||||
color: #26A5F6;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
.tab-btn-active {
|
||||
background: linear-gradient(135deg, #26A5F6 0%, #1478D4 100%);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 16rpx rgba(38,165,246,0.35);
|
||||
}
|
||||
|
||||
/* ===== 课堂报告子Tab ===== */
|
||||
.sub-tab-switcher {
|
||||
display: flex;
|
||||
background: #e8f0fe;
|
||||
border-radius: 20rpx;
|
||||
padding: 1rpx;
|
||||
margin: 30rpx 200rpx 32rpx;
|
||||
}
|
||||
.sub-tab-btn {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
border-radius: 16rpx;
|
||||
font-size: 27rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
.sub-tab-active {
|
||||
background: linear-gradient(135deg, #1d78e8, #49a5fc);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 16rpx rgba(29,120,232,0.35);
|
||||
}
|
||||
|
||||
/* ===== 周期选择器入口 ===== */
|
||||
.period-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f0f7ff;
|
||||
border-radius: 50rpx;
|
||||
padding: 14rpx 40rpx;
|
||||
margin: 0 auto 28rpx;
|
||||
border: 1rpx solid #c8dff8;
|
||||
width: fit-content;
|
||||
}
|
||||
.period-text {
|
||||
font-size: 28rpx;
|
||||
color: #1d78e8;
|
||||
font-weight: 600;
|
||||
}
|
||||
.period-caret {
|
||||
font-size: 24rpx;
|
||||
color: #1d78e8;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
/* ===== 统计区块标题 ===== */
|
||||
.stat-section-header-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 16rpx 0 20rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.stat-section-header-wrap::before,
|
||||
.stat-section-header-wrap::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1rpx;
|
||||
background: linear-gradient(90deg, transparent, #cce0f8);
|
||||
}
|
||||
.stat-section-header-wrap::after {
|
||||
background: linear-gradient(90deg, #cce0f8, transparent);
|
||||
}
|
||||
.stat-section-header {
|
||||
color: #1d78e8;
|
||||
font-size: 26rpx;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ===== 统计指标网格 ===== */
|
||||
.stat-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 32rpx;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
.stat-card {
|
||||
width: calc(50% - 20rpx);
|
||||
border-radius: 28rpx;
|
||||
padding: 30rpx 26rpx 28rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8rpx 28rpx rgba(0,0,0,0.13);
|
||||
}
|
||||
.stat-card-1 { background: linear-gradient(135deg, #ff5f6d 0%, #ffc371 100%); }
|
||||
.stat-card-2 { background: linear-gradient(135deg, #0fd850 0%, #00c3a9 100%); }
|
||||
.stat-card-3 { background: linear-gradient(135deg, #2980b9 0%, #6dd5fa 100%); }
|
||||
.stat-card-4 { background: linear-gradient(135deg, #7f7fd5 0%, #86a8e7 100%); }
|
||||
.stat-card-bar {
|
||||
position: absolute;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.13);
|
||||
bottom: -50rpx;
|
||||
right: -40rpx;
|
||||
}
|
||||
.stat-card-label {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255,255,255,0.88);
|
||||
}
|
||||
.stat-card-value {
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
word-break: break-all;
|
||||
line-height: 1.2;
|
||||
text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
/* ===== 跳绳统计 ===== */
|
||||
.jump-tip {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
.jump-summary-wrap {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(73,165,252,0.1);
|
||||
overflow: hidden;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.jump-summary-header {
|
||||
display: flex;
|
||||
background: #1d78e8;
|
||||
padding: 18rpx 0;
|
||||
}
|
||||
.jump-summary-data {
|
||||
display: flex;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
.jump-col {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
.jump-summary-data .jump-col {
|
||||
color: #18394D;
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
.jump-unit {
|
||||
font-size: 20rpx;
|
||||
color: rgba(255,255,255,0.85);
|
||||
}
|
||||
.jump-record-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f4f8;
|
||||
}
|
||||
.jump-record-item:last-child { border-bottom: none; }
|
||||
.jump-record-icon-wrap {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
background: linear-gradient(135deg, #49a5fc, #1d78e8);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 24rpx;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.jump-record-icon {
|
||||
width: 54rpx;
|
||||
height: 54rpx;
|
||||
filter: brightness(10);
|
||||
}
|
||||
.jump-record-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
.jump-record-tag {
|
||||
display: inline-block;
|
||||
background: #1d78e8;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 20rpx;
|
||||
border-radius: 30rpx;
|
||||
width: fit-content;
|
||||
}
|
||||
.jump-record-detail {
|
||||
font-size: 26rpx;
|
||||
color: #18394D;
|
||||
padding-left: 4rpx;
|
||||
}
|
||||
|
||||
/* ===== 月份/季度选择弹窗 ===== */
|
||||
.picker-mask {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.45);
|
||||
z-index: 200;
|
||||
}
|
||||
.picker-sheet {
|
||||
position: fixed;
|
||||
left: 0; right: 0; bottom: 0;
|
||||
background: #fff;
|
||||
border-radius: 36rpx 36rpx 0 0;
|
||||
padding-bottom: 220rpx;
|
||||
z-index: 201;
|
||||
transform: translateY(100%);
|
||||
transition: transform 0.3s ease;
|
||||
box-shadow: 0 -8rpx 40rpx rgba(73,165,252,0.18);
|
||||
}
|
||||
.picker-sheet-show { transform: translateY(0); }
|
||||
.picker-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 28rpx 40rpx 20rpx;
|
||||
border-bottom: 1rpx solid #f0f4f8;
|
||||
}
|
||||
.picker-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #18394D;
|
||||
}
|
||||
.picker-cancel {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
.picker-confirm {
|
||||
font-size: 28rpx;
|
||||
color: #1d78e8;
|
||||
font-weight: bold;
|
||||
}
|
||||
.picker-year-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 28rpx 0 20rpx;
|
||||
gap: 48rpx;
|
||||
}
|
||||
.picker-year-text {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #18394D;
|
||||
min-width: 160rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.picker-year-arrow {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
background: #f0f6ff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 44rpx;
|
||||
color: #1d78e8;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
.picker-month-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 8rpx 30rpx 20rpx;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.picker-month-item {
|
||||
width: calc(25% - 16rpx);
|
||||
text-align: center;
|
||||
padding: 20rpx 0;
|
||||
border-radius: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
background: #f5f7fa;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.picker-item-active {
|
||||
background: linear-gradient(135deg, #49a5fc, #1d78e8);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 4rpx 12rpx rgba(73,165,252,0.4);
|
||||
}
|
||||
.picker-quarter-list {
|
||||
padding: 16rpx 40rpx 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.picker-quarter-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 26rpx 36rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.picker-quarter-item.picker-item-active {
|
||||
background: linear-gradient(135deg, #49a5fc, #1d78e8);
|
||||
}
|
||||
.picker-quarter-label {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #18394D;
|
||||
}
|
||||
.picker-quarter-item.picker-item-active .picker-quarter-label,
|
||||
.picker-quarter-item.picker-item-active .picker-quarter-months { color: #fff; }
|
||||
.picker-quarter-months {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
.picker-item-disabled { opacity: 0.3; }
|
||||
.picker-year-arrow-disabled { opacity: 0.3; color: #ccc; }
|
||||
|
||||
/* ===== 课堂报告骨架屏 ===== */
|
||||
@keyframes cr-shimmer {
|
||||
0% { background-position: -750rpx 0; }
|
||||
100% { background-position: 750rpx 0; }
|
||||
}
|
||||
|
||||
.cr-skeleton {
|
||||
padding: 0 30rpx 40rpx;
|
||||
}
|
||||
|
||||
.cr-sk-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.cr-sk-card {
|
||||
width: calc(50% - 10rpx);
|
||||
height: 140rpx;
|
||||
border-radius: 16rpx;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
|
||||
background-size: 750rpx 100%;
|
||||
animation: cr-shimmer 1.4s infinite linear;
|
||||
}
|
||||
|
||||
.cr-sk-divider {
|
||||
height: 2rpx;
|
||||
background: #f0f0f0;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.cr-sk-table {
|
||||
height: 90rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
|
||||
background-size: 750rpx 100%;
|
||||
animation: cr-shimmer 1.4s infinite linear;
|
||||
}
|
||||
|
||||
.cr-sk-row {
|
||||
height: 72rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
|
||||
background-size: 750rpx 100%;
|
||||
animation: cr-shimmer 1.4s 0.1s infinite linear;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user