|
|
@@ -2,9 +2,11 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
import { Router, RouterModule } from '@angular/router';
|
|
|
-import { NgxEchartsModule } from 'ngx-echarts';
|
|
|
-import type { EChartsOption, SeriesOption } from 'echarts';
|
|
|
import { Subject, takeUntil } from 'rxjs';
|
|
|
+import { ModalComponent } from '../../shared/components/modal/modal.component';
|
|
|
+
|
|
|
+// 声明 ECharts 全局类型
|
|
|
+declare const echarts: any;
|
|
|
|
|
|
import { WeightDataService } from '../../services/weight-data.service';
|
|
|
import {
|
|
|
@@ -18,7 +20,7 @@ import {
|
|
|
@Component({
|
|
|
selector: 'app-weight',
|
|
|
standalone: true,
|
|
|
- imports: [CommonModule, FormsModule, RouterModule, NgxEchartsModule],
|
|
|
+ imports: [CommonModule, FormsModule, RouterModule, ModalComponent],
|
|
|
templateUrl: './weight.component.html',
|
|
|
styleUrl: './weight.component.scss'
|
|
|
})
|
|
|
@@ -67,11 +69,11 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
|
|
Math = Math;
|
|
|
|
|
|
- // ECharts 配置
|
|
|
- trendChartOption: EChartsOption = {};
|
|
|
- changeChartOption: EChartsOption = {};
|
|
|
- scatterChartOption: EChartsOption = {};
|
|
|
- progressChartOption: EChartsOption = {};
|
|
|
+ // ECharts 图表实例
|
|
|
+ trendChart: any = null;
|
|
|
+ changeChart: any = null;
|
|
|
+ scatterChart: any = null;
|
|
|
+ progressChart: any = null;
|
|
|
|
|
|
constructor(
|
|
|
private router: Router,
|
|
|
@@ -101,11 +103,50 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
this.updateCharts();
|
|
|
this.calculateStats();
|
|
|
});
|
|
|
+
|
|
|
+ // 延迟初始化图表,确保DOM已加载
|
|
|
+ setTimeout(() => {
|
|
|
+ this.initializeChartInstances();
|
|
|
+ }, 100);
|
|
|
+
|
|
|
+ // 添加窗口调整大小事件监听器
|
|
|
+ window.addEventListener('resize', this.handleResize.bind(this));
|
|
|
}
|
|
|
|
|
|
ngOnDestroy(): void {
|
|
|
this.destroy$.next();
|
|
|
this.destroy$.complete();
|
|
|
+
|
|
|
+ // 移除事件监听器并销毁图表
|
|
|
+ window.removeEventListener('resize', this.handleResize.bind(this));
|
|
|
+ if (this.trendChart) this.trendChart.dispose();
|
|
|
+ if (this.changeChart) this.changeChart.dispose();
|
|
|
+ if (this.scatterChart) this.scatterChart.dispose();
|
|
|
+ if (this.progressChart) this.progressChart.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 初始化图表实例
|
|
|
+ private initializeChartInstances(): void {
|
|
|
+ const trendDom = document.getElementById('trendChart');
|
|
|
+ const changeDom = document.getElementById('changeChart');
|
|
|
+ const scatterDom = document.getElementById('scatterChart');
|
|
|
+ const progressDom = document.getElementById('progressChart');
|
|
|
+
|
|
|
+ if (trendDom) this.trendChart = echarts.init(trendDom);
|
|
|
+ if (changeDom) this.changeChart = echarts.init(changeDom);
|
|
|
+ if (scatterDom) this.scatterChart = echarts.init(scatterDom);
|
|
|
+ if (progressDom) this.progressChart = echarts.init(progressDom);
|
|
|
+
|
|
|
+ // 初始化后立即更新图表
|
|
|
+ this.updateCharts();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理窗口调整大小
|
|
|
+ handleResize(): void {
|
|
|
+ if (this.trendChart) this.trendChart.resize();
|
|
|
+ if (this.changeChart) this.changeChart.resize();
|
|
|
+ if (this.scatterChart) this.scatterChart.resize();
|
|
|
+ if (this.progressChart) this.progressChart.resize();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -168,10 +209,32 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
* 更新趋势折线图
|
|
|
*/
|
|
|
private updateTrendChart(): void {
|
|
|
+ if (!this.trendChart) return;
|
|
|
+
|
|
|
const records = [...this.filteredRecords].reverse(); // 从旧到新排序
|
|
|
|
|
|
+ // 如果数据不足,显示提示信息
|
|
|
if (records.length === 0) {
|
|
|
- this.trendChartOption = {};
|
|
|
+ this.trendChart.setOption({
|
|
|
+ title: {
|
|
|
+ text: '体重趋势',
|
|
|
+ left: 'center',
|
|
|
+ textStyle: { fontSize: 16, fontWeight: 'bold', color: '#1f2937' }
|
|
|
+ },
|
|
|
+ graphic: {
|
|
|
+ type: 'text',
|
|
|
+ left: 'center',
|
|
|
+ top: 'center',
|
|
|
+ style: {
|
|
|
+ text: '暂无数据',
|
|
|
+ fontSize: 14,
|
|
|
+ fill: '#9ca3af',
|
|
|
+ textAlign: 'center'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ xAxis: { show: false },
|
|
|
+ yAxis: { show: false }
|
|
|
+ });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -190,7 +253,7 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
// 标注点(关键节点)
|
|
|
const markPoints = this.extractMarkPoints(records);
|
|
|
|
|
|
- this.trendChartOption = {
|
|
|
+ this.trendChart.setOption({
|
|
|
title: {
|
|
|
text: '体重趋势',
|
|
|
left: 'center',
|
|
|
@@ -308,24 +371,65 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
lineStyle: { width: 2, type: 'dotted' },
|
|
|
symbol: 'none'
|
|
|
}] as any[] : [])
|
|
|
- ] as unknown as SeriesOption[]
|
|
|
- };
|
|
|
+ ]
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 更新周/月体重变化柱状图
|
|
|
*/
|
|
|
private updateChangeChart(): void {
|
|
|
+ if (!this.changeChart) return;
|
|
|
+
|
|
|
const changes = this.calculateWeightChanges();
|
|
|
|
|
|
+ // 获取图表标题
|
|
|
+ const getChartTitle = (): string => {
|
|
|
+ if (this.chartPeriod === 'weekly') {
|
|
|
+ // 检查数据跨度以确定是显示日变化还是周变化
|
|
|
+ const records = [...this.filteredRecords].reverse();
|
|
|
+ if (records.length >= 2) {
|
|
|
+ const firstDate = new Date(records[0].date);
|
|
|
+ const lastDate = new Date(records[records.length - 1].date);
|
|
|
+ const daysDiff = Math.floor((lastDate.getTime() - firstDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
|
+ return daysDiff < 14 ? '每日体重变化' : '周体重变化';
|
|
|
+ }
|
|
|
+ return '周体重变化';
|
|
|
+ }
|
|
|
+ return '月体重变化';
|
|
|
+ };
|
|
|
+
|
|
|
+ const chartTitle = getChartTitle();
|
|
|
+
|
|
|
+ // 如果数据不足,显示提示信息
|
|
|
if (changes.labels.length === 0) {
|
|
|
- this.changeChartOption = {};
|
|
|
+ this.changeChart.setOption({
|
|
|
+ title: {
|
|
|
+ text: chartTitle,
|
|
|
+ left: 'center',
|
|
|
+ textStyle: { fontSize: 16, fontWeight: 'bold', color: '#1f2937' }
|
|
|
+ },
|
|
|
+ graphic: {
|
|
|
+ type: 'text',
|
|
|
+ left: 'center',
|
|
|
+ top: 'center',
|
|
|
+ style: {
|
|
|
+ text: '数据不足\n需要至少2条记录',
|
|
|
+ fontSize: 14,
|
|
|
+ fill: '#9ca3af',
|
|
|
+ textAlign: 'center',
|
|
|
+ lineHeight: 24
|
|
|
+ }
|
|
|
+ },
|
|
|
+ xAxis: { show: false },
|
|
|
+ yAxis: { show: false }
|
|
|
+ });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- this.changeChartOption = {
|
|
|
+ this.changeChart.setOption({
|
|
|
title: {
|
|
|
- text: this.chartPeriod === 'weekly' ? '周体重变化' : '月体重变化',
|
|
|
+ text: chartTitle,
|
|
|
left: 'center',
|
|
|
textStyle: { fontSize: 16, fontWeight: 'bold', color: '#1f2937' }
|
|
|
},
|
|
|
@@ -379,117 +483,263 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
data: changes.values.map(v => ({
|
|
|
value: v,
|
|
|
itemStyle: {
|
|
|
- color: v < 0 ? '#10b981' : '#ef4444'
|
|
|
+ color: v < 0
|
|
|
+ ? 'rgba(16, 185, 129, 0.85)' // 绿色(减重)
|
|
|
+ : 'rgba(239, 68, 68, 0.85)', // 红色(增重)
|
|
|
+ borderRadius: [6, 6, 0, 0],
|
|
|
+ shadowColor: v < 0
|
|
|
+ ? 'rgba(16, 185, 129, 0.3)'
|
|
|
+ : 'rgba(239, 68, 68, 0.3)',
|
|
|
+ shadowBlur: 8,
|
|
|
+ shadowOffsetY: 3
|
|
|
}
|
|
|
})),
|
|
|
label: {
|
|
|
show: true,
|
|
|
- position: 'top',
|
|
|
+ position: (params: any) => params.value >= 0 ? 'top' : 'bottom',
|
|
|
formatter: (params: any) => {
|
|
|
const value = params.value;
|
|
|
const sign = value >= 0 ? '+' : '';
|
|
|
- return `${sign}${value.toFixed(1)}kg`;
|
|
|
+ return `${sign}${value.toFixed(1)}`;
|
|
|
},
|
|
|
- color: '#000',
|
|
|
- fontSize: 11,
|
|
|
- fontWeight: 'bold'
|
|
|
+ color: '#1f2937',
|
|
|
+ fontSize: 12,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
|
+ borderRadius: 4,
|
|
|
+ padding: [4, 8]
|
|
|
},
|
|
|
- barWidth: '60%'
|
|
|
+ barWidth: '55%',
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 15,
|
|
|
+ shadowOffsetY: 5
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
]
|
|
|
- };
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 更新体重-体脂率散点图
|
|
|
*/
|
|
|
private updateScatterChart(): void {
|
|
|
+ if (!this.scatterChart) return;
|
|
|
+
|
|
|
+ // 如果数据不足,显示提示信息
|
|
|
if (this.filteredRecords.length === 0) {
|
|
|
- this.scatterChartOption = {};
|
|
|
+ this.scatterChart.setOption({
|
|
|
+ title: {
|
|
|
+ text: '体重 vs 体脂率',
|
|
|
+ left: 'center',
|
|
|
+ textStyle: { fontSize: 14, fontWeight: 'bold', color: '#1f2937' }
|
|
|
+ },
|
|
|
+ graphic: {
|
|
|
+ type: 'text',
|
|
|
+ left: 'center',
|
|
|
+ top: 'center',
|
|
|
+ style: {
|
|
|
+ text: '暂无数据',
|
|
|
+ fontSize: 14,
|
|
|
+ fill: '#9ca3af',
|
|
|
+ textAlign: 'center'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ xAxis: { show: false },
|
|
|
+ yAxis: { show: false }
|
|
|
+ });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const data = this.filteredRecords.map((r, index) => [r.weight, r.bodyFat, r.date, index]);
|
|
|
|
|
|
- this.scatterChartOption = {
|
|
|
- title: {
|
|
|
- text: '体重 vs 体脂率',
|
|
|
- left: 'center',
|
|
|
- textStyle: { fontSize: 14, fontWeight: 'bold', color: '#1f2937' }
|
|
|
- },
|
|
|
+ // 计算趋势线(如果有足够数据)
|
|
|
+ let trendLineData: any[] = [];
|
|
|
+ if (data.length >= 2) {
|
|
|
+ const weights = data.map(d => Number(d[0]));
|
|
|
+ const bodyFats = data.map(d => Number(d[1]));
|
|
|
+ const minWeight = Math.min(...weights);
|
|
|
+ const maxWeight = Math.max(...weights);
|
|
|
+
|
|
|
+ // 简单线性回归
|
|
|
+ const avgWeight = weights.reduce((a, b) => a + b, 0) / weights.length;
|
|
|
+ const avgBodyFat = bodyFats.reduce((a, b) => a + b, 0) / bodyFats.length;
|
|
|
+ let numerator = 0, denominator = 0;
|
|
|
+ for (let i = 0; i < weights.length; i++) {
|
|
|
+ numerator += (Number(weights[i]) - avgWeight) * (Number(bodyFats[i]) - avgBodyFat);
|
|
|
+ denominator += (Number(weights[i]) - avgWeight) ** 2;
|
|
|
+ }
|
|
|
+ const slope = numerator / denominator;
|
|
|
+ const intercept = avgBodyFat - slope * avgWeight;
|
|
|
+
|
|
|
+ trendLineData = [
|
|
|
+ [minWeight, slope * minWeight + intercept],
|
|
|
+ [maxWeight, slope * maxWeight + intercept]
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ this.scatterChart.setOption({
|
|
|
tooltip: {
|
|
|
formatter: (params: any) => {
|
|
|
+ if (params.seriesName === '趋势线') {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
const [weight, bodyFat, date] = params.value;
|
|
|
return `
|
|
|
- <div style="padding: 8px;">
|
|
|
- <div style="font-weight: bold; margin-bottom: 4px;">${date}</div>
|
|
|
- <div>体重: ${weight} kg</div>
|
|
|
- <div>体脂率: ${bodyFat}%</div>
|
|
|
+ <div style="padding: 10px; font-size: 13px;">
|
|
|
+ <div style="font-weight: bold; margin-bottom: 6px; color: #1f2937;">${date}</div>
|
|
|
+ <div style="margin-bottom: 3px;"><span style="color: #667eea;">●</span> 体重: <strong>${weight} kg</strong></div>
|
|
|
+ <div><span style="color: #f59e0b;">●</span> 体脂率: <strong>${bodyFat}%</strong></div>
|
|
|
</div>
|
|
|
`;
|
|
|
},
|
|
|
- backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
|
+ backgroundColor: 'rgba(255, 255, 255, 0.98)',
|
|
|
borderColor: '#e5e7eb',
|
|
|
- borderWidth: 1
|
|
|
+ borderWidth: 1,
|
|
|
+ textStyle: { color: '#374151' }
|
|
|
},
|
|
|
grid: {
|
|
|
- left: '15%',
|
|
|
- right: '10%',
|
|
|
+ left: '12%',
|
|
|
+ right: '12%',
|
|
|
bottom: '15%',
|
|
|
- top: '20%'
|
|
|
+ top: '15%',
|
|
|
+ containLabel: true
|
|
|
},
|
|
|
xAxis: {
|
|
|
type: 'value',
|
|
|
name: '体重 (kg)',
|
|
|
nameLocation: 'middle',
|
|
|
nameGap: 30,
|
|
|
- nameTextStyle: { fontSize: 12 },
|
|
|
- axisLabel: { fontSize: 11 }
|
|
|
+ nameTextStyle: {
|
|
|
+ fontSize: 13,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ color: '#1f2937'
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ fontSize: 11,
|
|
|
+ color: '#6b7280'
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: '#e5e7eb'
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
name: '体脂率 (%)',
|
|
|
nameLocation: 'middle',
|
|
|
nameGap: 40,
|
|
|
- nameTextStyle: { fontSize: 12 },
|
|
|
- axisLabel: { fontSize: 11 }
|
|
|
+ nameTextStyle: {
|
|
|
+ fontSize: 13,
|
|
|
+ fontWeight: 'bold',
|
|
|
+ color: '#1f2937'
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ fontSize: 11,
|
|
|
+ color: '#6b7280'
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ color: '#e5e7eb'
|
|
|
+ }
|
|
|
+ }
|
|
|
},
|
|
|
visualMap: {
|
|
|
+ show: data.length > 1,
|
|
|
min: 0,
|
|
|
max: data.length - 1,
|
|
|
dimension: 3,
|
|
|
orient: 'vertical',
|
|
|
- right: 10,
|
|
|
+ right: 15,
|
|
|
top: 'center',
|
|
|
- text: ['新', '旧'],
|
|
|
- calculable: true,
|
|
|
- textStyle: { fontSize: 11 },
|
|
|
+ text: ['最新', '最早'],
|
|
|
+ textStyle: {
|
|
|
+ fontSize: 11,
|
|
|
+ color: '#6b7280'
|
|
|
+ },
|
|
|
inRange: {
|
|
|
- color: ['#d1d5db', '#3b82f6']
|
|
|
- }
|
|
|
+ color: ['#cbd5e1', '#667eea', '#764ba2']
|
|
|
+ },
|
|
|
+ itemWidth: 15,
|
|
|
+ itemHeight: 100
|
|
|
},
|
|
|
series: [
|
|
|
+ // 趋势线
|
|
|
+ ...(trendLineData.length > 0 ? [{
|
|
|
+ name: '趋势线',
|
|
|
+ type: 'line',
|
|
|
+ data: trendLineData,
|
|
|
+ smooth: false,
|
|
|
+ symbol: 'none',
|
|
|
+ lineStyle: {
|
|
|
+ type: 'dashed',
|
|
|
+ width: 2,
|
|
|
+ color: '#9ca3af',
|
|
|
+ opacity: 0.8
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ z: 0
|
|
|
+ }] : []),
|
|
|
+ // 散点
|
|
|
{
|
|
|
+ name: '数据点',
|
|
|
type: 'scatter',
|
|
|
data: data,
|
|
|
- symbolSize: 14,
|
|
|
+ symbolSize: 18,
|
|
|
+ itemStyle: {
|
|
|
+ borderWidth: 2,
|
|
|
+ borderColor: '#fff',
|
|
|
+ shadowBlur: 8,
|
|
|
+ shadowColor: 'rgba(102, 126, 234, 0.4)',
|
|
|
+ shadowOffsetY: 2
|
|
|
+ },
|
|
|
emphasis: {
|
|
|
itemStyle: {
|
|
|
- shadowBlur: 10,
|
|
|
- shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ shadowBlur: 15,
|
|
|
+ shadowColor: 'rgba(102, 126, 234, 0.6)',
|
|
|
+ borderWidth: 3,
|
|
|
+ scale: 1.3
|
|
|
}
|
|
|
- }
|
|
|
+ },
|
|
|
+ z: 10
|
|
|
}
|
|
|
]
|
|
|
- };
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 更新目标进度环形图
|
|
|
*/
|
|
|
private updateProgressChart(): void {
|
|
|
+ if (!this.progressChart) return;
|
|
|
+
|
|
|
+ // 如果没有目标或数据,显示提示信息
|
|
|
if (!this.goal || this.records.length === 0) {
|
|
|
- this.progressChartOption = {};
|
|
|
+ this.progressChart.setOption({
|
|
|
+ title: {
|
|
|
+ text: '目标进度',
|
|
|
+ left: 'center',
|
|
|
+ top: 10,
|
|
|
+ textStyle: { fontSize: 14, fontWeight: 'bold', color: '#1f2937' }
|
|
|
+ },
|
|
|
+ graphic: {
|
|
|
+ type: 'text',
|
|
|
+ left: 'center',
|
|
|
+ top: 'center',
|
|
|
+ style: {
|
|
|
+ text: !this.goal ? '请先设置目标' : '暂无数据',
|
|
|
+ fontSize: 14,
|
|
|
+ fill: '#9ca3af',
|
|
|
+ textAlign: 'center'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -510,7 +760,7 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
color = '#3b82f6'; // 蓝色
|
|
|
}
|
|
|
|
|
|
- this.progressChartOption = {
|
|
|
+ this.progressChart.setOption({
|
|
|
title: {
|
|
|
text: '目标进度',
|
|
|
left: 'center',
|
|
|
@@ -552,7 +802,7 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
- };
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -612,30 +862,48 @@ export class WeightComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
|
|
|
|
if (this.chartPeriod === 'weekly') {
|
|
|
- // 按周分组
|
|
|
- const weeks: { [key: string]: WeightRecord[] } = {};
|
|
|
+ // 先检查数据的时间跨度
|
|
|
+ const firstDate = new Date(records[0].date);
|
|
|
+ const lastDate = new Date(records[records.length - 1].date);
|
|
|
+ const daysDiff = Math.floor((lastDate.getTime() - firstDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
|
|
|
|
- records.forEach(record => {
|
|
|
- const date = new Date(record.date);
|
|
|
- const weekNum = this.getWeekNumber(date);
|
|
|
- const weekKey = `第${weekNum}周`;
|
|
|
-
|
|
|
- if (!weeks[weekKey]) weeks[weekKey] = [];
|
|
|
- weeks[weekKey].push(record);
|
|
|
- });
|
|
|
-
|
|
|
- // 计算每周变化
|
|
|
- const weekKeys = Object.keys(weeks).slice(-8); // 最近8周
|
|
|
- weekKeys.forEach((weekKey, index) => {
|
|
|
- if (index > 0) {
|
|
|
- const prevWeek = weekKeys[index - 1];
|
|
|
- const prevAvg = this.average(weeks[prevWeek].map(r => r.weight));
|
|
|
- const currAvg = this.average(weeks[weekKey].map(r => r.weight));
|
|
|
+ // 如果数据跨度小于14天,显示每日变化而不是周变化
|
|
|
+ if (daysDiff < 14) {
|
|
|
+ // 显示每日变化
|
|
|
+ for (let i = 1; i < records.length; i++) {
|
|
|
+ const date = new Date(records[i].date);
|
|
|
+ const label = `${date.getMonth() + 1}/${date.getDate()}`;
|
|
|
+ const change = records[i].weight - records[i - 1].weight;
|
|
|
|
|
|
- labels.push(weekKey);
|
|
|
- values.push(currAvg - prevAvg);
|
|
|
+ labels.push(label);
|
|
|
+ values.push(change);
|
|
|
}
|
|
|
- });
|
|
|
+ } else {
|
|
|
+ // 按周分组
|
|
|
+ const weeks: { [key: string]: WeightRecord[] } = {};
|
|
|
+
|
|
|
+ records.forEach(record => {
|
|
|
+ const date = new Date(record.date);
|
|
|
+ const weekNum = this.getWeekNumber(date);
|
|
|
+ const weekKey = `第${weekNum}周`;
|
|
|
+
|
|
|
+ if (!weeks[weekKey]) weeks[weekKey] = [];
|
|
|
+ weeks[weekKey].push(record);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 计算每周变化
|
|
|
+ const weekKeys = Object.keys(weeks).slice(-8); // 最近8周
|
|
|
+ weekKeys.forEach((weekKey, index) => {
|
|
|
+ if (index > 0) {
|
|
|
+ const prevWeek = weekKeys[index - 1];
|
|
|
+ const prevAvg = this.average(weeks[prevWeek].map(r => r.weight));
|
|
|
+ const currAvg = this.average(weeks[weekKey].map(r => r.weight));
|
|
|
+
|
|
|
+ labels.push(weekKey);
|
|
|
+ values.push(currAvg - prevAvg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
} else {
|
|
|
// 按月分组
|
|
|
const months: { [key: string]: WeightRecord[] } = {};
|