update:运动馆

This commit is contained in:
ltx 2026-04-27 17:24:02 +08:00
parent 6e67f91a10
commit afab9b0a53
8 changed files with 315 additions and 42 deletions

View File

@ -98,7 +98,8 @@ Page({
async fetchFitnessReport() {
try {
const res = await getPhysicalFitnessReport()
const list = (res.data && res.data.categoryScoreList) || []
const rawList = (res.data && res.data.categoryScoreList) || []
const list = rawList.filter(item => !(item.name && item.name.includes('米') && item.name.includes('跑')))
const fitnessItems = list.map(item => ({
...item,
unit: UNIT_MAP[item.name] || '',
@ -115,9 +116,23 @@ Page({
// 绘制六边形雷达图
drawRadarChart(that) {
const list = that.data.fitnessReport || []
const categories = list.map(i => i.name)
// 长文本自动换行(超过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 => {
@ -133,11 +148,12 @@ Page({
height: that.data.radarCHeight * that.data.pixelRatio,
categories,
series: [
{ name: '上次成绩', data: prevData, color: '#4A90E2' },
{ name: '本次成绩', data: currentData, color: '#E74C3C' },
{ 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, 10],
@ -147,12 +163,12 @@ Page({
float: 'center',
fontSize: 12,
lineHeight: 11,
margin: 5,
margin: 0,
itemGap: 20,
},
extra: {
radar: {
gridType: 'radar',
gridType: this.data.fitnessItems.length == 4 ? 'circle' : 'radar',
gridColor: '#CCCCCC',
gridCount: 4,
opacity: 0.15,
@ -161,6 +177,7 @@ Page({
labelShow: true,
labelColor: '#333',
fontSize: 13,
labelMargin: 30,
labelPointShow: false,
max: 100,
},
@ -209,6 +226,10 @@ Page({
page: this.data.page + 1,
hasMore: res.data.total > (this.data.pageIndex) * 20,
})
if(this.data.isTrainee && this.data.activeTab === 1){
this.drawRadarChart(this)
}
},
//查看报告详情
@ -224,8 +245,10 @@ Page({
onReady() {
const windowInfo = wx.getWindowInfo()
const pixelRatio = windowInfo.pixelRatio
const radarCWidth = windowInfo.windowWidth * 0.86
const radarCHeight = windowInfo.windowWidth * 0.86
// 宽度 = 屏幕宽度 - 页面两侧 padding(30rpx×2),不计 card padding(由负 margin 抵消)
const rpxRatio = windowInfo.windowWidth / 750
const radarCWidth = Math.floor(windowInfo.windowWidth - 2 * 30 * rpxRatio)
const radarCHeight = Math.floor(radarCWidth * 1.05) // 稍高,给换行标签和 rank 留出空间
this.setData({ radarCWidth, radarCHeight, pixelRatio }, () => {
if (this.data.isTrainee && this.data.activeTab === 1) {
this.drawRadarChart(this)

View File

@ -162,7 +162,7 @@
<image src="{{item.imgUrl}}" class="fitness-icon" mode="aspectFit" />
<!-- </view> -->
<view class="fitness-name">{{item.name}}</view>
<view class="fitness-score">{{item.lastScore}}<text class="fitness-unit"> {{item.unit}}</text></view>
<view class="fitness-score">{{item.scoreTrendChart.axisY[item.scoreTrendChart.axisY.length - 1] || '- -'}}<text class="fitness-unit"> {{item.unit}}</text></view>
</view>
</view>
</block>
@ -181,21 +181,13 @@
<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 class="score-value score-highlight">{{selectedFitnessItem.scoreTrendChart.axisY[selectedFitnessItem.scoreTrendChart.axisY.length - 1] || '- -'}}<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 class="score-value">{{selectedFitnessItem.scoreTrendChart.axisY[selectedFitnessItem.scoreTrendChart.axisY.length - 2] || '- -'}}<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 File

@ -393,6 +393,7 @@
.radar-wrap {
overflow: hidden;
margin: 0 -20rpx; /* 抵消 fitness-card 两侧 padding,让图表更宽 */
}
.radar-legend {

View File

@ -9,7 +9,8 @@ Page({
* 页面的初始数据
*/
data: {
reportDetail:{}
reportDetail:{},
loading: true
},
/**
@ -27,13 +28,21 @@ Page({
async getReport(){
let params = {
classRoomId:this.data.classRoomId,
type:1
}
if(this.data.studentNo){
params.studentNo = this.data.studentNo
}
const res = await getStudentClassRoomReport(params)
const age = res.data.age || 0
const hrMax = 220 - age
const moderateHR = Math.round(hrMax * 0.6) // 中等强度下限
const highHR = Math.round(hrMax * 0.75) // 高强度下限
this.setData({
reportDetail:res.data
reportDetail: res.data,
moderateHR,
highHR,
loading: false
},()=>{
this.drawCharts(this)
})
@ -74,6 +83,7 @@ Page({
}, ],
pixelRatio: that.data.pixelRatio,
animation: true,
enableMarkLine: true,
background: "#FFFFFF",
color: that.data.chartType == 1 ? ["#FFA30C"] : that.data.chartType == 2 ? ["#6CB7FB"] : ["#FF4949"],
padding: [15, 15, 10, 0],
@ -97,13 +107,41 @@ Page({
yAxis: {
gridType: "dash",
gridColor: "#F1F1F1",
// data: [{
// min: 40,
// // axisLine: false,
// fontSize: 12
// }]
data: [{
// min: Math.max(0, that.data.moderateHR - 20),
max: that.data.highHR + 20,
fontSize: 12
}]
},
extra: {
markLine: {
type: 'dash',
dashLength: 6,
data: [
{
value: that.data.moderateHR,
lineColor: '#FFA30C',
showLabel: true,
labelText: '中强度',
labelFontColor: '#FFA30C',
labelBgColor: '#FFF8EC',
labelBgOpacity: 0.9,
labelAlign: 'left',
labelFontSize: 11
},
{
value: that.data.highHR,
lineColor: '#FF4949',
showLabel: true,
labelText: '高强度',
labelFontColor: '#FF4949',
labelBgColor: '#FFF0F0',
labelBgOpacity: 0.9,
labelAlign: 'left',
labelFontSize: 11
}
]
},
line: {
type: "curve",
width: 2,

View File

@ -2,6 +2,46 @@
<view class="page-content">
<mp-bar background="linear-gradient(190deg, #49a5fc 0%, #f7f7f7 800rpx, #f7f7f7 100%)" title="统计" color="#fff"></mp-bar>
<!-- 骨架屏 -->
<view wx:if="{{loading}}" class="skeleton">
<view class="skeleton-title">
<view class="skeleton-block skeleton-tag"></view>
<view class="skeleton-block skeleton-title-text"></view>
</view>
<view class="skeleton-card">
<view class="skeleton-block skeleton-label"></view>
<view class="skeleton-block skeleton-chart"></view>
</view>
<view class="skeleton-heart-box">
<view class="skeleton-heart-item">
<view class="skeleton-block skeleton-heart-label"></view>
<view class="skeleton-block skeleton-heart-value"></view>
</view>
<view class="skeleton-heart-item">
<view class="skeleton-block skeleton-heart-label"></view>
<view class="skeleton-block skeleton-heart-value"></view>
</view>
<view class="skeleton-heart-item">
<view class="skeleton-block skeleton-heart-label"></view>
<view class="skeleton-block skeleton-heart-value"></view>
</view>
</view>
<view class="skeleton-title">
<view class="skeleton-block skeleton-tag"></view>
<view class="skeleton-block skeleton-title-text"></view>
</view>
<view class="skeleton-list-item" wx:for="{{[1,2,3]}}" wx:key="*this">
<view class="skeleton-block skeleton-list-icon"></view>
<view class="skeleton-list-info">
<view class="skeleton-block skeleton-list-line"></view>
<view class="skeleton-block skeleton-list-line-short"></view>
</view>
</view>
</view>
<!-- 正式内容 -->
<block wx:if="{{!loading}}">
<view class="title">
<view class="title-before"></view>心率统计
</view>
@ -10,7 +50,16 @@
<view class="charts" style="width: {{cWidth}}px;height: {{cHeight}}px;">
<canvas canvas-id="mycanvas" type="2d" id="mycanvas" style="width: {{cWidth}}px;height: {{cHeight}}px;" bindtouchstart="touchstart" bindtouchmove="touchmove" bindtouchend="touchend" />
</view>
<view class="hr-legend">
<view class="hr-legend-item">
<view class="hr-legend-line hr-legend-moderate"></view>
<text>中等强度 {{moderateHR}}次/分</text>
</view>
<view class="hr-legend-item">
<view class="hr-legend-line hr-legend-high"></view>
<text>高强度 {{highHR}}次/分</text>
</view>
</view>
</view>
<!-- <view class="tp">
@ -63,4 +112,5 @@
<view wx:if="{{reportDetail.jumpRecordList.length <= 0}}" class="no">暂无记录~</view>
</block>
</view>

View File

@ -242,6 +242,35 @@
font-size: 26rpx;
}
.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;
}
.homework-item {
background-color: #fff;
border-radius: 20rpx;
@ -390,4 +419,124 @@
color: rgb(211, 13, 13);
font-size: 38rpx;
}
/* ======== 骨架屏 ======== */
@keyframes skeleton-loading {
0% { background-position: -600rpx 0; }
100% { background-position: 600rpx 0; }
}
.skeleton-block {
border-radius: 10rpx;
background: linear-gradient(90deg, #ebebeb 25%, #f5f5f5 50%, #ebebeb 75%);
background-size: 1200rpx 100%;
animation: skeleton-loading 1.4s infinite linear;
}
.skeleton {
padding-bottom: 40rpx;
}
.skeleton-title {
display: flex;
align-items: center;
margin-top: 60rpx;
margin-left: 30rpx;
}
.skeleton-tag {
width: 6rpx;
height: 24rpx;
border-radius: 6rpx;
margin-right: 16rpx;
}
.skeleton-title-text {
width: 160rpx;
height: 28rpx;
}
.skeleton-card {
background-color: #fff;
box-shadow: 0 0 10rpx rgb(223, 222, 222);
border-radius: 20rpx;
margin: 28rpx 30rpx;
padding: 24rpx 30rpx;
}
.skeleton-label {
width: 80rpx;
height: 26rpx;
margin-bottom: 20rpx;
}
.skeleton-chart {
width: 100%;
height: 220rpx;
border-radius: 16rpx;
}
.skeleton-heart-box {
display: flex;
padding: 0 30rpx;
margin-top: 30rpx;
}
.skeleton-heart-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 20rpx;
height: 130rpx;
margin: 0 20rpx;
background-color: #f5f5f5;
justify-content: center;
gap: 16rpx;
}
.skeleton-heart-label {
width: 80rpx;
height: 22rpx;
}
.skeleton-heart-value {
width: 60rpx;
height: 36rpx;
}
.skeleton-list-item {
display: flex;
align-items: center;
background-color: #fff;
border-radius: 20rpx;
padding: 20rpx 30rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
margin: 30rpx 30rpx;
}
.skeleton-list-icon {
width: 110rpx;
height: 110rpx;
border-radius: 30rpx;
margin-right: 30rpx;
flex-shrink: 0;
}
.skeleton-list-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.skeleton-list-line {
width: 70%;
height: 28rpx;
}
.skeleton-list-line-short {
width: 50%;
height: 26rpx;
}

View File

@ -2455,22 +2455,35 @@ function drawRadarLabel(angleList, radius, centerPosition, opts, config, context
context.fill();
}
if(radarOption.labelShow === true){
var labelMargin = radarOption.labelMargin != null ? radarOption.labelMargin : config.radarLabelTextMargin;
var pos = {
x: (radius + config.radarLabelTextMargin * opts.pix) * Math.cos(angle),
y: (radius + config.radarLabelTextMargin * opts.pix) * Math.sin(angle)
x: (radius + labelMargin * opts.pix) * Math.cos(angle),
y: (radius + labelMargin * opts.pix) * Math.sin(angle)
};
var posRelativeCanvas = convertCoordinateOrigin(pos.x, pos.y, centerPosition);
var startX = posRelativeCanvas.x;
var startY = posRelativeCanvas.y;
var labelLines = (opts.categories[index] || '').split('\n');
var maxLineWidth = Math.max.apply(null, labelLines.map(function(l) { return measureText(l, config.fontSize, context); }));
if (util.approximatelyEqual(pos.x, 0)) {
startX -= measureText(opts.categories[index] || '', config.fontSize, context) / 2;
startX -= maxLineWidth / 2;
} else if (pos.x < 0) {
startX -= measureText(opts.categories[index] || '', config.fontSize, context);
startX -= maxLineWidth;
}
context.beginPath();
context.setFontSize(config.fontSize);
context.setFillStyle(radarOption.labelColor || opts.fontColor);
context.fillText(opts.categories[index] || '', startX, startY + config.fontSize / 2);
var lineHeight = config.fontSize + 4 * opts.pix;
var offsetY = labelLines.length > 1 ? -(labelLines.length - 1) * lineHeight / 2 : 0;
labelLines.forEach(function(line, li) {
var lx = posRelativeCanvas.x;
if (util.approximatelyEqual(pos.x, 0)) {
lx -= measureText(line, config.fontSize, context) / 2;
} else if (pos.x < 0) {
lx -= measureText(line, config.fontSize, context);
}
context.fillText(line, lx, startY + config.fontSize / 2 + offsetY + li * lineHeight);
});
context.closePath();
context.stroke();
}
@ -5581,27 +5594,34 @@ function drawRadarDataPoints(series, opts, config, context) {
radarDataPoints.forEach(function(eachSeries, seriesIndex) {
context.beginPath();
var fontSize = eachSeries.textSize * opts.pix || config.fontSize;
var dataLabels = opts.series && opts.series[seriesIndex] && opts.series[seriesIndex].dataLabels;
context.setFontSize(fontSize);
context.setFillStyle(eachSeries.textColor || opts.fontColor);
eachSeries.data.forEach(function(item, index) {
var labelText = dataLabels && dataLabels[index] != null ? String(dataLabels[index]) : String(item.value);
var labelLines = labelText.split('\n');
var lineHeight = fontSize + 3 * opts.pix;
// helper: draw multi-line text at a given (x, y, align)
function drawLines(x, y, align) {
context.setTextAlign(align);
labelLines.forEach(function(line, li) {
context.fillText(line, x, y + li * lineHeight);
});
}
//如果是中心点垂直的上下点位
if(Math.abs(item.position.x - centerPosition.x)<2){
//如果在上面
if(item.position.y < centerPosition.y){
context.setTextAlign('center');
context.fillText(item.value, item.position.x, item.position.y - 4);
drawLines(item.position.x, item.position.y - (labelLines.length * lineHeight + 4), 'center');
}else{
context.setTextAlign('center');
context.fillText(item.value, item.position.x, item.position.y + fontSize + 2);
drawLines(item.position.x, item.position.y + fontSize + 2, 'center');
}
}else{
//如果在左侧
if(item.position.x < centerPosition.x){
context.setTextAlign('right');
context.fillText(item.value, item.position.x - 4, item.position.y + fontSize / 2 - 2);
drawLines(item.position.x - 4, item.position.y + fontSize / 2 - 2 - (labelLines.length - 1) * lineHeight / 2, 'right');
}else{
context.setTextAlign('left');
context.fillText(item.value, item.position.x + 4, item.position.y + fontSize / 2 - 2);
drawLines(item.position.x + 4, item.position.y + fontSize / 2 - 2 - (labelLines.length - 1) * lineHeight / 2, 'left');
}
}
});

View File

@ -1,5 +1,5 @@
// const ipAddress = "https://wechatapi.yuedongsports.com/"; // 根据实际情况修改
const ipAddress = "https://wechatapi-qa.yuedongsports.com/"; // 根据实际情况修改
const ipAddress = "https://wechatapi.yuedongsports.com/"; // 根据实际情况修改
// const ipAddress = "https://wechatapi-qa.yuedongsports.com/"; // 根据实际情况修改
// const ipAddress = "http://192.168.0.146:9993/"; // 根据实际情况修改
// const ipAddress = "http://localhost:9993/"; // 根据实际情况修改