diff --git a/pages/parent/report/report.js b/pages/parent/report/report.js index 797adca..ef10e54 100644 --- a/pages/parent/report/report.js +++ b/pages/parent/report/report.js @@ -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' }) } - }, /** diff --git a/pages/parent/report/report.wxml b/pages/parent/report/report.wxml index 4d274eb..bedcf79 100644 --- a/pages/parent/report/report.wxml +++ b/pages/parent/report/report.wxml @@ -194,6 +194,9 @@ {{mockMonthData.jumpDuration}} {{mockMonthData.jumpAvgSpeed}} + + + @@ -203,7 +206,7 @@ {{item.duration}} {{item.count}}个 - + diff --git a/pages/parent/report/report.wxss b/pages/parent/report/report.wxss index 866ede5..1f52edf 100644 --- a/pages/parent/report/report.wxss +++ b/pages/parent/report/report.wxss @@ -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); } diff --git a/pages/teacher/count/count.js b/pages/teacher/count/count.js index 7c0fad3..6f70659 100644 --- a/pages/teacher/count/count.js +++ b/pages/teacher/count/count.js @@ -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('刷新成功') }, /** diff --git a/pages/teacher/count/count.wxml b/pages/teacher/count/count.wxml index f4b7e08..e003424 100644 --- a/pages/teacher/count/count.wxml +++ b/pages/teacher/count/count.wxml @@ -75,7 +75,29 @@ + + + + + + + + + + + + + + + + + + + + + + 心率统计 @@ -85,22 +107,40 @@ + - - - 最大心率 - {{nowCourseDetail.maxHR || '- -'}} + + + + 心率范围 + {{nowCourseDetail.heartRange}} + + + + 平均心率 + {{nowCourseDetail.avgHeart}} + + + + 中高强度时长 + {{nowCourseDetail.highDuration}} + + + + 中高时间占比 + {{nowCourseDetail.highRatio}} + - - 最小心率 - {{nowCourseDetail.minHR || '- -'}} - - - 平均心率 - {{nowCourseDetail.avgHR || '- -'}} - - 学生列表 @@ -114,6 +154,7 @@ + diff --git a/pages/teacher/count/count.wxss b/pages/teacher/count/count.wxss index 2681acb..d31ab9a 100644 --- a/pages/teacher/count/count.wxss +++ b/pages/teacher/count/count.wxss @@ -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; diff --git a/subpackage/teacher/report-detail/report-detail.js b/subpackage/teacher/report-detail/report-detail.js index db0d387..5635c03 100644 --- a/subpackage/teacher/report-detail/report-detail.js +++ b/subpackage/teacher/report-detail/report-detail.js @@ -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: { diff --git a/subpackage/teacher/report-detail/report-detail.wxml b/subpackage/teacher/report-detail/report-detail.wxml index 1c04886..6f4ce41 100644 --- a/subpackage/teacher/report-detail/report-detail.wxml +++ b/subpackage/teacher/report-detail/report-detail.wxml @@ -1,7 +1,6 @@ - + - @@ -38,31 +37,29 @@ - - - - 心率统计 - - - 次/分 - - + + + 心率统计 - - - - 中等强度 {{moderateHR}}次/分 + + 次/分 + + - - - 高强度 {{highHR}}次/分 + + + + 中等强度 {{moderateHR}}次/分 + + + + 高强度 {{highHR}}次/分 + - - - - - - - 最大心率 - {{reportDetail.maxHR || '- -'}} + + + + 心率范围 + {{reportDetail.heartRange}} + + + + 平均心率 + {{reportDetail.avgHeart}} + + + + 中高强度时长 + {{reportDetail.highDuration}} + + + + 中高时间占比 + {{reportDetail.highRatio}} + - - 最小心率 - {{reportDetail.minHR || '- -'}} + + + 跳绳记录 - - 平均心率 - {{reportDetail.avgHR || '- -'}} + + + 个数 + + 时长 + (分钟) + + + 平均速度 + (个/分钟) + + + + {{reportDetail.jumpTotal}} + {{reportDetail.jumpDuration}} + {{reportDetail.jumpAvgSpeed}} + - - - - 跳绳记录 - - - - - - - - - - - {{item.jumpType}} + + + + + + + + {{item.jumpType}} + + {{item.totalDuration}}s + {{item.totalCount}}个 + - {{item.totalDuration}}s {{item.totalCount}}个 - - - 暂无记录~ - + 暂无记录~ \ No newline at end of file diff --git a/subpackage/teacher/report-detail/report-detail.wxss b/subpackage/teacher/report-detail/report-detail.wxss index c62061d..a25330a 100644 --- a/subpackage/teacher/report-detail/report-detail.wxss +++ b/subpackage/teacher/report-detail/report-detail.wxss @@ -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; } \ No newline at end of file diff --git a/subpackage/teacher/student-fitness-detail/student-fitness-detail.js b/subpackage/teacher/student-fitness-detail/student-fitness-detail.js index 706b18f..0c7fe99 100644 --- a/subpackage/teacher/student-fitness-detail/student-fitness-detail.js +++ b/subpackage/teacher/student-fitness-detail/student-fitness-detail.js @@ -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() {}, +// }) \ No newline at end of file diff --git a/subpackage/teacher/student-fitness-detail/student-fitness-detail.wxml b/subpackage/teacher/student-fitness-detail/student-fitness-detail.wxml index dde0789..76edb19 100644 --- a/subpackage/teacher/student-fitness-detail/student-fitness-detail.wxml +++ b/subpackage/teacher/student-fitness-detail/student-fitness-detail.wxml @@ -1,41 +1,156 @@ - + - - - 体测成绩对比 - - - + + + 课堂报告 + 体测报告 - - - - - {{item.name}} - {{item.lastScore}} {{item.unit}} + + + + + 月 + 季度 - - - 暂无体测数据 + + + + {{selectedYear}}年{{selectedMonth}}月 + ▾ + + 心率统计 + + + + 心率范围 + {{monthData.heartRange}} + + + + 平均心率 + {{monthData.avgHeart}} + + + + 中高强度时长 + {{monthData.highDuration}} + + + + 中高时间占比 + {{monthData.highRatio}} + + + 跳绳统计 + 训练记录统计:仅智能跳绳,不含普通跳绳 + + + 个数 + 时长(分钟) + 平均速度(个/分钟) + + + {{monthData.jumpTotal}} + {{monthData.jumpDuration}} + {{monthData.jumpAvgSpeed}} + + + + + + + + + {{item.type}} + {{item.duration}} {{item.count}}个 + + + + + + + + + {{selectedYear}}年 第{{selectedQuarter}}季度 + ▾ + + 心率统计 + + + + 心率范围 + {{quarterData.heartRange}} + + + + 平均心率 + {{quarterData.avgHeart}} + + + + 中高强度时长 + {{quarterData.highDuration}} + + + + 中高时间占比 + {{quarterData.highRatio}} + + + 跳绳统计 + 训练记录统计:仅智能跳绳,不含普通跳绳 + + + 个数 + 时长(分钟) + 平均速度(个/分钟) + + + {{quarterData.jumpTotal}} + {{quarterData.jumpDuration}} + {{quarterData.jumpAvgSpeed}} + + + + + + + + + {{item.type}} + {{item.duration}} {{item.count}}个 + + + + + + + + + + 体测成绩对比 + + + + + + + + + {{item.name}} + {{item.lastScore}} {{item.unit}} + + + + 暂无体测数据 + - @@ -43,7 +158,6 @@ {{selectedFitnessItem.name}} ✕ - 本次成绩 @@ -53,17 +167,50 @@ 上次成绩 {{selectedFitnessItem.prevScore}} {{selectedFitnessItem.unit}} - - 查看详细趋势 - + + + + + + 取消 + 选择月份 + + + + ‹ + {{pickerYear}}年 + › + + + + {{item}}月 + + + + + + + + + 取消 + 选择季度 + + + + ‹ + {{pickerYear}}年 + › + + + + {{item.label}} + {{item.months}} + + + + diff --git a/subpackage/teacher/student-fitness-detail/student-fitness-detail.wxss b/subpackage/teacher/student-fitness-detail/student-fitness-detail.wxss index 8ba3821..658f6d8 100644 --- a/subpackage/teacher/student-fitness-detail/student-fitness-detail.wxss +++ b/subpackage/teacher/student-fitness-detail/student-fitness-detail.wxss @@ -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; +}