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}}
+
+
+
+ 暂无体测数据
+
-
-
+
+
+
+
+
+
+ ‹
+ {{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;
+}