update:教练查看体测报告

This commit is contained in:
ltx 2026-04-24 18:56:03 +08:00
parent 4f4599ef6f
commit 6e67f91a10
8 changed files with 906 additions and 23 deletions

View File

@ -71,7 +71,8 @@
"teacher/report-detail/report-detail",
"user/apply-school/apply-school",
"user/redeem-class/redeem-class",
"parent/fitness-detail/fitness-detail"
"parent/fitness-detail/fitness-detail",
"teacher/student-fitness-detail/student-fitness-detail"
]
}
],

View File

@ -38,6 +38,12 @@ Page({
cWidth: 330,
cHeight: 280,
pixelRatio: 2,
// 体测报告
activeTab: 0, // 0-课堂报告 1-体测报告
fitnessClassList: [], // 体测班级列表
nowFitnessClass: {}, // 当前体测班级
fitnessStudentList: [], // 体测学生列表
fitnessLoading: false,
},
@ -50,12 +56,55 @@ Page({
})
if (wx.getStorageSync('roleId') && wx.getStorageSync('roleId') == 4) {
await this.getTrainClass(true)
await this.getFitnessClasses()
} else {
await this.getClass()
}
wx.hideLoading()
},
// 切换 Tab
switchTab(e) {
const tab = e.currentTarget.dataset.tab
this.setData({ activeTab: tab })
},
// 获取体测班级列表
async getFitnessClasses() {
const res = await getClassList()
if (res && res.data && res.data.length > 0) {
this.setData({
fitnessClassList: res.data,
nowFitnessClass: res.data[0]
}, () => {
this.getFitnessStudents()
})
}
},
// 体测班级切换
fitnessPicker(e) {
const nowFitnessClass = this.data.fitnessClassList[Number(e.detail.value)]
this.setData({ nowFitnessClass }, () => {
this.getFitnessStudents()
})
},
// 获取体测学生列表
async getFitnessStudents() {
if (!this.data.nowFitnessClass.classId) return
this.setData({ fitnessLoading: true })
const res = await getStudentList({
pageIndex: 1,
pageSize: 200,
classId: this.data.nowFitnessClass.classId
})
this.setData({
fitnessStudentList: (res && res.data && res.data.datas) ? res.data.datas : (res && res.data ? res.data : []),
fitnessLoading: false
})
},
clickCouse(e) {
this.setData({
clickCouse: e.currentTarget.dataset.item
@ -115,7 +164,7 @@ Page({
pageIndex: this.data.page,
PageSize: 20,
})
if(!this.data.nowCourse.id){
if(!this.data.nowCourse.id && res.data.datas.length > 0){
const detail = await getClassRoomReportDetails(res.data.datas[0].id)
this.setData({
nowCourseDetail:this.data.nowCourse.id ? this.data.nowCourseDetail: detail.data
@ -133,6 +182,7 @@ Page({
//获取项目列表
async getProject() {
if(!this.data.nowClass || !this.data.nowClass.gradeId) return
const res = await getProjectList(this.data.nowClass.gradeId)
this.setData({
projectList: res.data,
@ -142,6 +192,7 @@ Page({
//获取学生列表 刷新 加载更多
async getStudent(isRefresh) {
if (!this.data.nowClass || !this.data.nowClass.classId) return
if (!this.data.hasMore && !isRefresh) return //非刷新且没有
const res = await getTongji({
ClassId: this.data.nowClass.classId,
@ -294,9 +345,14 @@ Page({
wx.navigateTo({
url: '/subpackage/teacher/report-detail/report-detail?id='+this.data.nowCourse.id +'&studentNo='+e.currentTarget.dataset.item.studentNo,
})
// wx.navigateTo({
// url: '/subpackage/teacher/report-list/report-list?studentNo='+e.currentTarget.dataset.item.studentNo,
// })
},
// 体测报告 - 跳转学生体测详情
goFitnessDetail(e) {
const item = e.currentTarget.dataset.item
wx.navigateTo({
url: '/subpackage/teacher/student-fitness-detail/student-fitness-detail?studentNo=' + item.studentNo + '&classId=' + this.data.nowFitnessClass.classId
})
},
/**

View File

@ -59,6 +59,18 @@
</block>
<block wx:else>
<!-- Tab 切换 -->
<view class="report-tabs">
<view class="report-tab-item {{activeTab === 0 ? 'report-tab-active' : ''}}" bindtap="switchTab" data-tab="{{0}}">
<text>课堂报告</text>
</view>
<view class="report-tab-item {{activeTab === 1 ? 'report-tab-active' : ''}}" bindtap="switchTab" data-tab="{{1}}">
<text>体测报告</text>
</view>
</view>
<!-- 课堂报告 -->
<block wx:if="{{activeTab === 0}}">
<view class="tab-item pickerY" bind:tap="showDialog">{{nowCourse.startingEndingTime}}
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/downn-b.png" mode="" />
</view>
@ -90,21 +102,6 @@
</view>
</view>
<!-- <view class="tp">
<view class="table-t">
<view class="no-b no-r">最大心率</view>
<view class="no-b no-r">最小心率</view>
<view class="no-b no-r">平均心率</view>
<view class="no-b">中高强度时长(分钟)</view>
</view>
<view class="table-t">
<view class="no-r">{{nowCourseDetail.maxHR || '- -'}}</view>
<view class="no-r">{{nowCourseDetail.minHR || '- -'}}</view>
<view class="no-r">{{nowCourseDetail.avgHR || '- -'}}</view>
<view>{{nowCourseDetail.highIntensity || '- -'}}</view>
</view>
</view> -->
<view class="title">
<view class="title-before"></view>学生列表
</view>
@ -114,9 +111,7 @@
<image src="{{item.photo == '' ? 'https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/room/avater.png': item.photo}}" class="img" />
{{item.studentName}}
<image class="sex" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/report-icon/sex_{{item.sex}}.png" mode=""/>
<!-- {{item.age}}岁 -->
</view>
<mp-icon icon="arrow" size="{{13}}" color="#8c8c8c"></mp-icon>
</view>
@ -131,7 +126,6 @@
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view wx:if="{{haveMore}}" class="loading">加载中...</view>
<view wx:if="{{!haveMore}}" class="no-more">没有更多数据了</view>
</scroll-view>
@ -141,5 +135,58 @@
<button type="primary" bind:tap="sureClickCouse">确定</button>
</view>
</mp-half-screen-dialog>
</block>
<!-- 体测报告 -->
<block wx:if="{{activeTab === 1}}">
<!-- 班级选择器 -->
<view class="fitness-header">
<view class="fitness-class-picker">
<picker range="{{fitnessClassList}}" range-key="name" bindchange="fitnessPicker">
<view class="fitness-picker-btn">
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/downn-b.png" class="picker-icon" />
<text>{{nowFitnessClass.name || '请选择班级'}}</text>
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/downn-b.png" class="picker-arrow" />
</view>
</picker>
</view>
<view class="fitness-count-badge">
<text>共 {{fitnessStudentList.length}} 人</text>
</view>
</view>
<!-- 加载中 -->
<view wx:if="{{fitnessLoading}}" class="fitness-loading">
<view class="loading-dot"></view>
<text>加载中...</text>
</view>
<!-- 学生列表 -->
<view wx:elif="{{fitnessStudentList.length > 0}}">
<view class="fitness-student-card" wx:for="{{fitnessStudentList}}" wx:key="studentNo" bindtap="goFitnessDetail" data-item="{{item}}">
<view class="fsc-avatar-wrap">
<image class="fsc-avatar" src="{{item.photo || 'https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/room/avater.png'}}" mode="aspectFill" />
</view>
<view class="fsc-info">
<view class="fsc-name-row">
<text class="fsc-name">{{item.studentName || item.name}}</text>
<image wx:if="{{item.sex}}" class="fsc-sex" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/report-icon/sex_{{item.sex}}.png" mode="aspectFit" />
</view>
<view class="fsc-meta">
<text wx:if="{{item.studentNo}}" class="fsc-no">学号:{{item.studentNo}}</text>
</view>
</view>
<view class="fsc-arrow">
<mp-icon icon="arrow" size="{{14}}" color="#26A5F6"></mp-icon>
</view>
</view>
</view>
<!-- 空状态 -->
<view wx:elif="{{!fitnessLoading}}" class="fitness-empty">
<view class="fitness-empty-icon">📋</view>
<text>暂无学生数据</text>
</view>
</block>
</block>
</view>

View File

@ -1,4 +1,180 @@
/* pages/teacher/count/count.wxss */
/* ===== Tab 切换 ===== */
.report-tabs {
display: flex;
margin: 24rpx 30rpx 0;
background-color: #EAF4FF;
border-radius: 60rpx;
padding: 6rpx;
box-sizing: border-box;
}
.report-tab-item {
flex: 1;
height: 68rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 54rpx;
font-size: 28rpx;
color: #26A5F6;
transition: all 0.25s;
}
.report-tab-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);
}
/* ===== 体测报告 header ===== */
.fitness-header {
display: flex;
align-items: center;
justify-content: space-between;
margin: 30rpx 30rpx 16rpx;
}
.fitness-class-picker {
flex: 1;
margin-right: 20rpx;
}
.fitness-picker-btn {
display: flex;
align-items: center;
justify-content: center;
height: 72rpx;
background: linear-gradient(135deg, #26A5F6 0%, #1478D4 100%);
border-radius: 50rpx;
padding: 0 24rpx;
box-shadow: 0 4rpx 16rpx rgba(38, 165, 246, 0.3);
}
.fitness-picker-btn text {
color: #fff;
font-size: 26rpx;
font-weight: bold;
flex: 1;
text-align: center;
}
.picker-icon {
display: none;
}
.picker-arrow {
width: 24rpx;
height: 24rpx;
filter: brightness(10);
}
.fitness-count-badge {
background-color: #EAF4FF;
border: 2rpx solid #26A5F6;
border-radius: 30rpx;
padding: 8rpx 24rpx;
white-space: nowrap;
}
.fitness-count-badge text {
font-size: 24rpx;
color: #26A5F6;
}
/* ===== 体测学生卡片 ===== */
.fitness-student-card {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 20rpx;
margin: 18rpx 30rpx;
padding: 24rpx 28rpx;
box-shadow: 0 4rpx 20rpx rgba(38, 165, 246, 0.08);
border-left: 6rpx solid #26A5F6;
}
.fsc-avatar-wrap {
width: 88rpx;
height: 88rpx;
border-radius: 50%;
background: linear-gradient(135deg, #74C7FF 0%, #26A5F6 100%);
padding: 4rpx;
box-sizing: border-box;
flex-shrink: 0;
}
.fsc-avatar {
width: 100%;
height: 100%;
border-radius: 50%;
border: 3rpx solid #fff;
background-color: #e8f5ff;
}
.fsc-info {
flex: 1;
margin-left: 24rpx;
}
.fsc-name-row {
display: flex;
align-items: center;
}
.fsc-name {
font-size: 30rpx;
font-weight: bold;
color: #222;
}
.fsc-sex {
width: 36rpx;
height: 36rpx;
margin-left: 12rpx;
}
.fsc-meta {
margin-top: 8rpx;
}
.fsc-no {
font-size: 22rpx;
color: #999;
}
.fsc-arrow {
flex-shrink: 0;
margin-left: 12rpx;
}
/* ===== 加载中 ===== */
.fitness-loading {
display: flex;
flex-direction: column;
align-items: center;
padding: 80rpx 0;
color: #26A5F6;
font-size: 26rpx;
}
/* ===== 空状态 ===== */
.fitness-empty {
display: flex;
flex-direction: column;
align-items: center;
padding: 120rpx 0 60rpx;
color: #bbb;
font-size: 26rpx;
}
.fitness-empty-icon {
font-size: 80rpx;
margin-bottom: 20rpx;
}
.card{
background-color: #fff;
box-shadow: 0 0 10rpx rgb(223, 222, 222);

View File

@ -0,0 +1,148 @@
// subpackage/teacher/student-fitness-detail/student-fitness-detail.js
import uCharts from '../../../utils/chart/u-charts';
import { getReport } from '../../../utils/serve/parent';
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 list = (res.data && res.data.categoryScoreList) || [];
const fitnessItems = list.map(item => ({
...item,
unit: UNIT_MAP[item.name] || '',
}));
this.setData({
fitnessReport: list,
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 radarCWidth = info.windowWidth * 0.86;
const radarCHeight = info.windowWidth * 0.86;
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 || [];
const categories = list.map(i => i.name);
const prevData = list.map(i => i.prevScore || 0);
const currentData = list.map(i => i.lastScore || 0);
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' },
{ name: '本次成绩', data: currentData, color: '#E74C3C' },
],
pixelRatio: that.data.pixelRatio,
animation: true,
background: 'rgba(0,0,0,0)',
color: ['#4A90E2', '#E74C3C'],
padding: [0, 0, 0, 10],
legend: {
show: true,
position: 'bottom',
float: 'center',
fontSize: 12,
lineHeight: 11,
margin: 5,
itemGap: 20,
},
extra: {
radar: {
gridType: 'radar',
gridColor: '#CCCCCC',
gridCount: 4,
opacity: 0.15,
gridEvenShow: true,
gridOddShow: true,
labelShow: true,
labelColor: '#333',
fontSize: 13,
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() {},
})

View File

@ -0,0 +1,9 @@
{
"usingComponents": {
"mp-bar": "weui-miniprogram/navigation-bar/navigation-bar",
"mp-icon": "weui-miniprogram/icon/icon"
},
"backgroundColorContent": "#f0f7ff",
"backgroundColor": "#f0f7ff",
"navigationStyle": "custom"
}

View File

@ -0,0 +1,69 @@
<!--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>
<!-- 体测报告主体 -->
<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>
<!-- 成绩弹窗遮罩 -->
<view class="popup-mask" wx:if="{{showFitnessPopup}}" bind:tap="closeFitnessPopup"></view>
<!-- 成绩弹窗 -->
<view class="score-popup {{showFitnessPopup ? 'popup-show' : ''}}">
<view class="popup-header">
<image src="{{selectedFitnessItem.imgUrl}}" class="popup-icon" mode="aspectFit" />
<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>
<view class="score-value score-highlight">{{selectedFitnessItem.lastScore}}<text class="score-unit"> {{selectedFitnessItem.unit}}</text></view>
</view>
<view class="score-row">
<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 File

@ -0,0 +1,377 @@
/* subpackage/teacher/student-fitness-detail/student-fitness-detail.wxss */
/* ===== 成绩弹窗 ===== */
.popup-mask {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.45);
z-index: 999;
}
.score-popup {
position: fixed;
left: 0; right: 0; bottom: 0;
background: #fff;
border-radius: 36rpx 36rpx 0 0;
padding: 36rpx 40rpx 60rpx;
z-index: 9999;
transform: translateY(100%);
transition: transform 0.3s ease;
box-shadow: 0 -8rpx 40rpx rgba(73,165,252,0.18);
}
.popup-show {
transform: translateY(0);
}
.popup-header {
display: flex;
align-items: center;
margin-bottom: 36rpx;
}
.popup-icon {
width: 88rpx;
height: 88rpx;
margin-right: 20rpx;
border-radius: 50%;
/* background: linear-gradient(135deg, #8dcfff, #49a5fc); */
/* padding: 14rpx; */
box-sizing: border-box;
}
.popup-title {
flex: 1;
font-size: 34rpx;
font-weight: bold;
color: #18394D;
}
.popup-close {
width: 56rpx;
height: 56rpx;
border-radius: 50%;
background: #f0f4f8;
display: flex;
align-items: center;
justify-content: center;
font-size: 26rpx;
color: #999;
}
.score-rows {
background: #f7fbff;
border-radius: 20rpx;
padding: 8rpx 30rpx;
margin-bottom: 36rpx;
}
.score-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #eef2f8;
}
.score-row:last-child {
border-bottom: none;
}
.score-divider {
height: 2rpx;
background: linear-gradient(90deg, transparent, #d0e8ff, transparent);
margin: 4rpx 0;
}
.score-label {
font-size: 28rpx;
color: #666;
}
.score-value {
font-size: 34rpx;
font-weight: bold;
color: #333;
}
.score-highlight {
color: #1d78e8;
font-size: 40rpx;
}
.score-unit {
font-size: 22rpx;
color: #999;
font-weight: normal;
}
.popup-btn {
background: linear-gradient(135deg, #49a5fc, #1d78e8);
color: #fff;
text-align: center;
padding: 28rpx 0;
border-radius: 50rpx;
font-size: 30rpx;
font-weight: bold;
box-shadow: 0 6rpx 20rpx rgba(73,165,252,0.4);
letter-spacing: 2rpx;
}
/* ===== 体测报告卡片 ===== */
.fitness-card {
background: #fff;
border-radius: 24rpx;
padding: 30rpx 20rpx 10rpx;
box-shadow: 0 4rpx 20rpx rgba(73,165,252,0.12);
margin-bottom: 30rpx;
margin-top: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.fitness-chart-title {
font-size: 30rpx;
font-weight: bold;
color: #18394D;
margin-bottom: 16rpx;
position: relative;
padding: 0 30rpx;
}
.fitness-chart-title::before,
.fitness-chart-title::after {
content: '';
display: inline-block;
width: 60rpx;
height: 4rpx;
background: linear-gradient(90deg, transparent, #49a5fc);
vertical-align: middle;
margin-right: 16rpx;
border-radius: 2rpx;
}
.fitness-chart-title::after {
background: linear-gradient(90deg, #49a5fc, transparent);
margin-right: 0;
margin-left: 16rpx;
}
.radar-wrap {
overflow: hidden;
}
/* ===== 六项指标网格 ===== */
.fitness-grid {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
margin-bottom: 10rpx;
}
.fitness-item {
width: calc(33.33% - 14rpx);
background: #fff;
border-radius: 20rpx;
box-shadow: 0 4rpx 16rpx rgba(73,165,252,0.12);
display: flex;
flex-direction: column;
align-items: center;
padding: 30rpx 10rpx 24rpx;
box-sizing: border-box;
active-opacity: 0.7;
}
.fitness-item:active {
transform: scale(0.96);
}
.fitness-icon {
width: 88rpx;
height: 88rpx;
}
.fitness-name {
font-size: 24rpx;
color: #333;
font-weight: 500;
text-align: center;
margin-bottom: 8rpx;
}
.fitness-score {
font-size: 30rpx;
font-weight: bold;
color: #1d78e8;
}
.fitness-unit {
font-size: 20rpx;
color: #999;
font-weight: normal;
}
/* ===== 空状态 ===== */
.empty-tip {
text-align: center;
padding: 80rpx 0;
color: #bbb;
font-size: 26rpx;
flex-wrap: wrap;
gap: 10rpx;
}
.meta-tag {
background-color: rgba(255, 255, 255, 0.25);
border-radius: 20rpx;
padding: 4rpx 18rpx;
font-size: 22rpx;
color: #fff;
}
.score-wrap {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 20rpx;
padding: 16rpx 28rpx;
flex-shrink: 0;
}
.score-num {
font-size: 52rpx;
font-weight: bold;
color: #fff;
line-height: 1;
}
/* ===== 章节标题 ===== */
.section-title {
display: flex;
align-items: center;
margin: 36rpx 30rpx 16rpx;
font-size: 28rpx;
font-weight: bold;
color: #222;
}
.title-bar {
width: 6rpx;
height: 28rpx;
border-radius: 6rpx;
background: linear-gradient(180deg, #26A5F6, #1478D4);
margin-right: 16rpx;
}
/* ===== 成绩表格 ===== */
.table-card {
background-color: #fff;
border-radius: 24rpx;
margin: 0 30rpx 40rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(38, 165, 246, 0.1);
}
.table-row {
display: grid;
grid-template-columns: 2fr 1.5fr 1fr 1.2fr;
align-items: center;
padding: 0 24rpx;
min-height: 90rpx;
}
.table-header {
background: linear-gradient(90deg, #EAF4FF, #f5faff);
font-size: 24rpx;
font-weight: bold;
color: #26A5F6;
min-height: 72rpx;
border-bottom: 2rpx solid #e8f3ff;
}
.row-even {
background-color: #fff;
border-bottom: 2rpx solid #f5f5f5;
}
.row-odd {
background-color: #f8fcff;
border-bottom: 2rpx solid #f5f5f5;
}
.col-name {
font-size: 26rpx;
color: #333;
padding-right: 10rpx;
word-break: break-all;
}
.col {
font-size: 25rpx;
color: #555;
text-align: center;
}
.col-score {
font-weight: bold;
color: #26A5F6;
}
.col-rank {
text-align: center;
border-radius: 20rpx;
font-size: 22rpx;
padding: 4rpx 0;
}
.col-rank.rank-1 {
color: #1aad19;
background-color: #e8f9e8;
}
.col-rank.rank-2 {
color: #26A5F6;
background-color: #eaf4ff;
}
.col-rank.rank-3 {
color: #ff9900;
background-color: #fff7e8;
}
.col-rank.rank-4 {
color: #e64340;
background-color: #fff0f0;
}
/* ===== 空状态 ===== */
.empty {
text-align: center;
padding: 80rpx 0;
color: #bbb;
font-size: 26rpx;
}
/* ===== 加载骨架 ===== */
.skeleton {
padding: 24rpx 30rpx;
}
.sk-header {
height: 160rpx;
background: linear-gradient(90deg, #dbeeff 25%, #c8e5ff 50%, #dbeeff 75%);
border-radius: 24rpx;
margin-bottom: 40rpx;
}
.sk-row {
height: 80rpx;
background: linear-gradient(90deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%);
border-radius: 12rpx;
margin-bottom: 16rpx;
}