|
@@ -1,6 +1,11 @@
|
|
|
-import { Component } from '@angular/core';
|
|
|
|
|
|
|
+import { Component, OnInit } from '@angular/core';
|
|
|
import { CommonModule } from '@angular/common';
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { Router, RouterModule } from '@angular/router';
|
|
import { Router, RouterModule } from '@angular/router';
|
|
|
|
|
+import { FormsModule } from '@angular/forms';
|
|
|
|
|
+import { Chart, ChartConfiguration, registerables } from 'chart.js';
|
|
|
|
|
+
|
|
|
|
|
+// 注册Chart.js所需组件
|
|
|
|
|
+Chart.register(...registerables);
|
|
|
|
|
|
|
|
// 健康指标数据接口
|
|
// 健康指标数据接口
|
|
|
export interface HealthMetric {
|
|
export interface HealthMetric {
|
|
@@ -11,6 +16,23 @@ export interface HealthMetric {
|
|
|
trend?: 'up' | 'down' | 'stable';
|
|
trend?: 'up' | 'down' | 'stable';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 历史健康数据接口
|
|
|
|
|
+export interface HealthMetricHistory {
|
|
|
|
|
+ date: Date;
|
|
|
|
|
+ value: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 数据记录表单接口
|
|
|
|
|
+export interface DataRecordForm {
|
|
|
|
|
+ type: 'heartRate' | 'bloodPressure' | 'bloodOxygen';
|
|
|
|
|
+ value: number;
|
|
|
|
|
+ diastolic?: number;
|
|
|
|
|
+ date: Date;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 时间范围选择
|
|
|
|
|
+export type TimeRange = 'week' | 'month' | 'threeMonths' | 'custom';
|
|
|
|
|
+
|
|
|
// 经期数据接口
|
|
// 经期数据接口
|
|
|
export interface MenstrualCycle {
|
|
export interface MenstrualCycle {
|
|
|
startDate: Date;
|
|
startDate: Date;
|
|
@@ -32,11 +54,11 @@ export interface ChronicCondition {
|
|
|
@Component({
|
|
@Component({
|
|
|
selector: 'app-monitoring',
|
|
selector: 'app-monitoring',
|
|
|
standalone: true,
|
|
standalone: true,
|
|
|
- imports: [CommonModule, RouterModule],
|
|
|
|
|
|
|
+ imports: [CommonModule, RouterModule, FormsModule],
|
|
|
templateUrl: './monitoring.component.html',
|
|
templateUrl: './monitoring.component.html',
|
|
|
styleUrl: './monitoring.component.scss'
|
|
styleUrl: './monitoring.component.scss'
|
|
|
})
|
|
})
|
|
|
-export class MonitoringComponent {
|
|
|
|
|
|
|
+export class MonitoringComponent implements OnInit {
|
|
|
today: Date = new Date();
|
|
today: Date = new Date();
|
|
|
|
|
|
|
|
// 心率数据
|
|
// 心率数据
|
|
@@ -66,6 +88,15 @@ export class MonitoringComponent {
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 血氧数据
|
|
|
|
|
+ bloodOxygen: HealthMetric = {
|
|
|
|
|
+ value: 98,
|
|
|
|
|
+ unit: '%',
|
|
|
|
|
+ status: 'normal',
|
|
|
|
|
+ timestamp: new Date(),
|
|
|
|
|
+ trend: 'stable'
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
// 经期数据
|
|
// 经期数据
|
|
|
menstrualCycle: MenstrualCycle = {
|
|
menstrualCycle: MenstrualCycle = {
|
|
|
startDate: new Date('2025-09-15'),
|
|
startDate: new Date('2025-09-15'),
|
|
@@ -95,8 +126,481 @@ export class MonitoringComponent {
|
|
|
// 是否为女性用户 (用于显示经期监测)
|
|
// 是否为女性用户 (用于显示经期监测)
|
|
|
isFemaleUser: boolean = true;
|
|
isFemaleUser: boolean = true;
|
|
|
|
|
|
|
|
|
|
+ // 图表实例
|
|
|
|
|
+ heartRateChart: Chart | null = null;
|
|
|
|
|
+ bloodPressureChart: Chart | null = null;
|
|
|
|
|
+ bloodOxygenChart: Chart | null = null;
|
|
|
|
|
+ correlationChart: Chart | null = null;
|
|
|
|
|
+
|
|
|
|
|
+ // 历史数据
|
|
|
|
|
+ heartRateHistory: HealthMetricHistory[] = [];
|
|
|
|
|
+ bloodPressureHistory: { systolic: HealthMetricHistory[]; diastolic: HealthMetricHistory[] } = {
|
|
|
|
|
+ systolic: [],
|
|
|
|
|
+ diastolic: []
|
|
|
|
|
+ };
|
|
|
|
|
+ bloodOxygenHistory: HealthMetricHistory[] = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 数据记录表单
|
|
|
|
|
+ recordForm: DataRecordForm = {
|
|
|
|
|
+ type: 'heartRate',
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ date: new Date()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 时间范围选择
|
|
|
|
|
+ selectedTimeRange: TimeRange = 'month';
|
|
|
|
|
+
|
|
|
|
|
+ // 模态框控制
|
|
|
|
|
+ showRecordModal: boolean = false;
|
|
|
|
|
+ showDetailModal: boolean = false;
|
|
|
|
|
+ selectedMetric: string = 'heartRate';
|
|
|
|
|
+
|
|
|
constructor(private router: Router) {}
|
|
constructor(private router: Router) {}
|
|
|
|
|
|
|
|
|
|
+ ngOnInit(): void {
|
|
|
|
|
+ this.generateMockHistoryData();
|
|
|
|
|
+ // 延迟初始化图表,确保DOM已加载
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ this.initializeCharts();
|
|
|
|
|
+ }, 100);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 生成模拟历史数据
|
|
|
|
|
+ generateMockHistoryData(): void {
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ const daysToGenerate = 90; // 生成3个月的历史数据
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = daysToGenerate; i >= 0; i--) {
|
|
|
|
|
+ const date = new Date(now);
|
|
|
|
|
+ date.setDate(date.getDate() - i);
|
|
|
|
|
+
|
|
|
|
|
+ // 心率历史数据(60-100之间波动)
|
|
|
|
|
+ const heartRateValue = 60 + Math.floor(Math.random() * 40);
|
|
|
|
|
+ this.heartRateHistory.push({
|
|
|
|
|
+ date: new Date(date),
|
|
|
|
|
+ value: heartRateValue
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 血压历史数据
|
|
|
|
|
+ const systolicValue = 110 + Math.floor(Math.random() * 30);
|
|
|
|
|
+ const diastolicValue = 70 + Math.floor(Math.random() * 20);
|
|
|
|
|
+ this.bloodPressureHistory.systolic.push({
|
|
|
|
|
+ date: new Date(date),
|
|
|
|
|
+ value: systolicValue
|
|
|
|
|
+ });
|
|
|
|
|
+ this.bloodPressureHistory.diastolic.push({
|
|
|
|
|
+ date: new Date(date),
|
|
|
|
|
+ value: diastolicValue
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 血氧历史数据(95-100之间波动)
|
|
|
|
|
+ const bloodOxygenValue = 95 + Math.floor(Math.random() * 5);
|
|
|
|
|
+ this.bloodOxygenHistory.push({
|
|
|
|
|
+ date: new Date(date),
|
|
|
|
|
+ value: bloodOxygenValue
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化图表
|
|
|
|
|
+ initializeCharts(): void {
|
|
|
|
|
+ // 根据选择的时间范围获取数据
|
|
|
|
|
+ const filteredData = this.getFilteredHistoryData();
|
|
|
|
|
+
|
|
|
|
|
+ // 心率图表
|
|
|
|
|
+ this.initializeHeartRateChart(filteredData.heartRate);
|
|
|
|
|
+
|
|
|
|
|
+ // 血压图表
|
|
|
|
|
+ this.initializeBloodPressureChart(filteredData.bloodPressure);
|
|
|
|
|
+
|
|
|
|
|
+ // 血氧图表
|
|
|
|
|
+ this.initializeBloodOxygenChart(filteredData.bloodOxygen);
|
|
|
|
|
+
|
|
|
|
|
+ // 心率-血压关联图表
|
|
|
|
|
+ this.initializeCorrelationChart(filteredData.heartRate, filteredData.bloodPressure.systolic);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取过滤后的历史数据
|
|
|
|
|
+ getFilteredHistoryData() {
|
|
|
|
|
+ const now = new Date();
|
|
|
|
|
+ let startDate = new Date();
|
|
|
|
|
+
|
|
|
|
|
+ switch (this.selectedTimeRange) {
|
|
|
|
|
+ case 'week':
|
|
|
|
|
+ startDate.setDate(now.getDate() - 7);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'month':
|
|
|
|
|
+ startDate.setMonth(now.getMonth() - 1);
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'threeMonths':
|
|
|
|
|
+ startDate.setMonth(now.getMonth() - 3);
|
|
|
|
|
+ break;
|
|
|
|
|
+ default:
|
|
|
|
|
+ startDate.setMonth(now.getMonth() - 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ heartRate: this.heartRateHistory.filter(item => item.date >= startDate),
|
|
|
|
|
+ bloodPressure: {
|
|
|
|
|
+ systolic: this.bloodPressureHistory.systolic.filter(item => item.date >= startDate),
|
|
|
|
|
+ diastolic: this.bloodPressureHistory.diastolic.filter(item => item.date >= startDate)
|
|
|
|
|
+ },
|
|
|
|
|
+ bloodOxygen: this.bloodOxygenHistory.filter(item => item.date >= startDate)
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化心率图表
|
|
|
|
|
+ initializeHeartRateChart(data: HealthMetricHistory[]): void {
|
|
|
|
|
+ const ctx = document.getElementById('heartRateChart') as HTMLCanvasElement;
|
|
|
|
|
+ if (!ctx) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 销毁现有图表
|
|
|
|
|
+ if (this.heartRateChart) {
|
|
|
|
|
+ this.heartRateChart.destroy();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const chartConfig: ChartConfiguration = {
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ data: {
|
|
|
|
|
+ labels: data.map(item => item.date.toLocaleDateString('zh-CN')),
|
|
|
|
|
+ datasets: [{
|
|
|
|
|
+ label: '心率 (bpm)',
|
|
|
|
|
+ data: data.map(item => item.value),
|
|
|
|
|
+ borderColor: '#ef4444',
|
|
|
|
|
+ backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
|
|
|
|
+ tension: 0.3,
|
|
|
|
|
+ fill: true
|
|
|
|
|
+ }]
|
|
|
|
|
+ },
|
|
|
|
|
+ options: {
|
|
|
|
|
+ responsive: true,
|
|
|
|
|
+ maintainAspectRatio: false,
|
|
|
|
|
+ plugins: {
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ display: false
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ mode: 'index',
|
|
|
|
|
+ intersect: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ scales: {
|
|
|
|
|
+ y: {
|
|
|
|
|
+ beginAtZero: false,
|
|
|
|
|
+ min: Math.min(...data.map(d => d.value)) - 10,
|
|
|
|
|
+ max: Math.max(...data.map(d => d.value)) + 10
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.heartRateChart = new Chart(ctx, chartConfig);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化血压图表
|
|
|
|
|
+ initializeBloodPressureChart(data: { systolic: HealthMetricHistory[]; diastolic: HealthMetricHistory[] }): void {
|
|
|
|
|
+ const ctx = document.getElementById('bloodPressureChart') as HTMLCanvasElement;
|
|
|
|
|
+ if (!ctx) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 销毁现有图表
|
|
|
|
|
+ if (this.bloodPressureChart) {
|
|
|
|
|
+ this.bloodPressureChart.destroy();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const chartConfig: ChartConfiguration = {
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ data: {
|
|
|
|
|
+ labels: data.systolic.map(item => item.date.toLocaleDateString('zh-CN')),
|
|
|
|
|
+ datasets: [
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '收缩压 (mmHg)',
|
|
|
|
|
+ data: data.systolic.map(item => item.value),
|
|
|
|
|
+ borderColor: '#3b82f6',
|
|
|
|
|
+ backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
|
|
|
+ tension: 0.3
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ label: '舒张压 (mmHg)',
|
|
|
|
|
+ data: data.diastolic.map(item => item.value),
|
|
|
|
|
+ borderColor: '#10b981',
|
|
|
|
|
+ backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
|
|
|
+ tension: 0.3
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ options: {
|
|
|
|
|
+ responsive: true,
|
|
|
|
|
+ maintainAspectRatio: false,
|
|
|
|
|
+ plugins: {
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ mode: 'index',
|
|
|
|
|
+ intersect: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ scales: {
|
|
|
|
|
+ y: {
|
|
|
|
|
+ beginAtZero: false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.bloodPressureChart = new Chart(ctx, chartConfig);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化血氧图表
|
|
|
|
|
+ initializeBloodOxygenChart(data: HealthMetricHistory[]): void {
|
|
|
|
|
+ const ctx = document.getElementById('bloodOxygenChart') as HTMLCanvasElement;
|
|
|
|
|
+ if (!ctx) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 销毁现有图表
|
|
|
|
|
+ if (this.bloodOxygenChart) {
|
|
|
|
|
+ this.bloodOxygenChart.destroy();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const chartConfig: ChartConfiguration = {
|
|
|
|
|
+ type: 'line',
|
|
|
|
|
+ data: {
|
|
|
|
|
+ labels: data.map(item => item.date.toLocaleDateString('zh-CN')),
|
|
|
|
|
+ datasets: [{
|
|
|
|
|
+ label: '血氧饱和度 (%)',
|
|
|
|
|
+ data: data.map(item => item.value),
|
|
|
|
|
+ borderColor: '#8b5cf6',
|
|
|
|
|
+ backgroundColor: 'rgba(139, 92, 246, 0.1)',
|
|
|
|
|
+ tension: 0.3,
|
|
|
|
|
+ fill: true
|
|
|
|
|
+ }]
|
|
|
|
|
+ },
|
|
|
|
|
+ options: {
|
|
|
|
|
+ responsive: true,
|
|
|
|
|
+ maintainAspectRatio: false,
|
|
|
|
|
+ plugins: {
|
|
|
|
|
+ legend: {
|
|
|
|
|
+ display: false
|
|
|
|
|
+ },
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ mode: 'index',
|
|
|
|
|
+ intersect: false
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ scales: {
|
|
|
|
|
+ y: {
|
|
|
|
|
+ beginAtZero: false,
|
|
|
|
|
+ min: 90,
|
|
|
|
|
+ max: 100
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.bloodOxygenChart = new Chart(ctx, chartConfig);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化心率-血压关联图表
|
|
|
|
|
+ initializeCorrelationChart(heartRateData: HealthMetricHistory[], bloodPressureData: HealthMetricHistory[]): void {
|
|
|
|
|
+ const ctx = document.getElementById('correlationChart') as HTMLCanvasElement;
|
|
|
|
|
+ if (!ctx) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 销毁现有图表
|
|
|
|
|
+ if (this.correlationChart) {
|
|
|
|
|
+ this.correlationChart.destroy();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 确保数据点数量一致
|
|
|
|
|
+ const dataLength = Math.min(heartRateData.length, bloodPressureData.length);
|
|
|
|
|
+ const scatterData: { x: number; y: number }[] = [];
|
|
|
|
|
+ const labels: string[] = [];
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < dataLength; i++) {
|
|
|
|
|
+ scatterData.push({
|
|
|
|
|
+ x: heartRateData[i].value,
|
|
|
|
|
+ y: bloodPressureData[i].value
|
|
|
|
|
+ });
|
|
|
|
|
+ labels.push(heartRateData[i].date.toLocaleDateString('zh-CN'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const chartConfig: ChartConfiguration = {
|
|
|
|
|
+ type: 'scatter',
|
|
|
|
|
+ data: {
|
|
|
|
|
+ datasets: [{
|
|
|
|
|
+ label: '心率 vs 收缩压',
|
|
|
|
|
+ data: scatterData,
|
|
|
|
|
+ backgroundColor: 'rgba(239, 68, 68, 0.7)',
|
|
|
|
|
+ pointRadius: 6,
|
|
|
|
|
+ pointHoverRadius: 8
|
|
|
|
|
+ }]
|
|
|
|
|
+ },
|
|
|
|
|
+ options: {
|
|
|
|
|
+ responsive: true,
|
|
|
|
|
+ maintainAspectRatio: false,
|
|
|
|
|
+ plugins: {
|
|
|
|
|
+ tooltip: {
|
|
|
|
|
+ callbacks: {
|
|
|
|
|
+ label: function(context) {
|
|
|
|
|
+ const index = context.dataIndex;
|
|
|
|
|
+ return [
|
|
|
|
|
+ `心率: ${context.parsed.x} bpm`,
|
|
|
|
|
+ `收缩压: ${context.parsed.y} mmHg`,
|
|
|
|
|
+ `日期: ${labels[index]}`
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ scales: {
|
|
|
|
|
+ x: {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ display: true,
|
|
|
|
|
+ text: '心率 (bpm)'
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ y: {
|
|
|
|
|
+ title: {
|
|
|
|
|
+ display: true,
|
|
|
|
|
+ text: '收缩压 (mmHg)'
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ this.correlationChart = new Chart(ctx, chartConfig);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 时间范围改变
|
|
|
|
|
+ onTimeRangeChange(): void {
|
|
|
|
|
+ this.initializeCharts();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 打开数据记录模态框
|
|
|
|
|
+ openRecordModal(type: 'heartRate' | 'bloodPressure' | 'bloodOxygen'): void {
|
|
|
|
|
+ this.recordForm = {
|
|
|
|
|
+ type,
|
|
|
|
|
+ value: 0,
|
|
|
|
|
+ diastolic: type === 'bloodPressure' ? 0 : undefined,
|
|
|
|
|
+ date: new Date()
|
|
|
|
|
+ };
|
|
|
|
|
+ this.showRecordModal = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭数据记录模态框
|
|
|
|
|
+ closeRecordModal(): void {
|
|
|
|
|
+ this.showRecordModal = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 提交数据记录
|
|
|
|
|
+ submitRecord(): void {
|
|
|
|
|
+ // 验证数据
|
|
|
|
|
+ if (this.recordForm.value <= 0) {
|
|
|
|
|
+ alert('请输入有效的数值');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (this.recordForm.type === 'bloodPressure' && (!this.recordForm.diastolic || this.recordForm.diastolic <= 0)) {
|
|
|
|
|
+ alert('请输入有效的舒张压数值');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新当前数据
|
|
|
|
|
+ switch (this.recordForm.type) {
|
|
|
|
|
+ case 'heartRate':
|
|
|
|
|
+ this.heartRate = {
|
|
|
|
|
+ ...this.heartRate,
|
|
|
|
|
+ value: this.recordForm.value,
|
|
|
|
|
+ timestamp: this.recordForm.date,
|
|
|
|
|
+ status: this.calculateHeartRateStatus(this.recordForm.value)
|
|
|
|
|
+ };
|
|
|
|
|
+ // 添加到历史记录
|
|
|
|
|
+ this.heartRateHistory.push({
|
|
|
|
|
+ date: new Date(this.recordForm.date),
|
|
|
|
|
+ value: this.recordForm.value
|
|
|
|
|
+ });
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'bloodPressure':
|
|
|
|
|
+ this.bloodPressure = {
|
|
|
|
|
+ systolic: {
|
|
|
|
|
+ ...this.bloodPressure.systolic,
|
|
|
|
|
+ value: this.recordForm.value,
|
|
|
|
|
+ timestamp: this.recordForm.date,
|
|
|
|
|
+ status: this.calculateBloodPressureStatus(this.recordForm.value, 'systolic')
|
|
|
|
|
+ },
|
|
|
|
|
+ diastolic: {
|
|
|
|
|
+ ...this.bloodPressure.diastolic,
|
|
|
|
|
+ value: this.recordForm.diastolic || 0,
|
|
|
|
|
+ timestamp: this.recordForm.date,
|
|
|
|
|
+ status: this.calculateBloodPressureStatus(this.recordForm.diastolic || 0, 'diastolic')
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ // 添加到历史记录
|
|
|
|
|
+ this.bloodPressureHistory.systolic.push({
|
|
|
|
|
+ date: new Date(this.recordForm.date),
|
|
|
|
|
+ value: this.recordForm.value
|
|
|
|
|
+ });
|
|
|
|
|
+ this.bloodPressureHistory.diastolic.push({
|
|
|
|
|
+ date: new Date(this.recordForm.date),
|
|
|
|
|
+ value: this.recordForm.diastolic || 0
|
|
|
|
|
+ });
|
|
|
|
|
+ break;
|
|
|
|
|
+ case 'bloodOxygen':
|
|
|
|
|
+ this.bloodOxygen = {
|
|
|
|
|
+ ...this.bloodOxygen,
|
|
|
|
|
+ value: this.recordForm.value,
|
|
|
|
|
+ timestamp: this.recordForm.date,
|
|
|
|
|
+ status: this.calculateBloodOxygenStatus(this.recordForm.value)
|
|
|
|
|
+ };
|
|
|
|
|
+ // 添加到历史记录
|
|
|
|
|
+ this.bloodOxygenHistory.push({
|
|
|
|
|
+ date: new Date(this.recordForm.date),
|
|
|
|
|
+ value: this.recordForm.value
|
|
|
|
|
+ });
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新图表
|
|
|
|
|
+ this.initializeCharts();
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭模态框
|
|
|
|
|
+ this.closeRecordModal();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 打开详情模态框
|
|
|
|
|
+ openDetailModal(metric: string): void {
|
|
|
|
|
+ this.selectedMetric = metric;
|
|
|
|
|
+ this.showDetailModal = true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭详情模态框
|
|
|
|
|
+ closeDetailModal(): void {
|
|
|
|
|
+ this.showDetailModal = false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算心率状态
|
|
|
|
|
+ calculateHeartRateStatus(value: number): 'normal' | 'warning' | 'danger' {
|
|
|
|
|
+ if (value >= 60 && value <= 100) return 'normal';
|
|
|
|
|
+ if ((value >= 50 && value < 60) || (value > 100 && value <= 120)) return 'warning';
|
|
|
|
|
+ return 'danger';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算血压状态
|
|
|
|
|
+ calculateBloodPressureStatus(value: number, type: 'systolic' | 'diastolic'): 'normal' | 'warning' | 'danger' {
|
|
|
|
|
+ if (type === 'systolic') {
|
|
|
|
|
+ if (value < 120) return 'normal';
|
|
|
|
|
+ if (value < 140) return 'warning';
|
|
|
|
|
+ return 'danger';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (value < 80) return 'normal';
|
|
|
|
|
+ if (value < 90) return 'warning';
|
|
|
|
|
+ return 'danger';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算血氧状态
|
|
|
|
|
+ calculateBloodOxygenStatus(value: number): 'normal' | 'warning' | 'danger' {
|
|
|
|
|
+ if (value >= 95) return 'normal';
|
|
|
|
|
+ if (value >= 90) return 'warning';
|
|
|
|
|
+ return 'danger';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 返回仪表盘
|
|
// 返回仪表盘
|
|
|
backToDashboard(): void {
|
|
backToDashboard(): void {
|
|
|
this.router.navigate(['/dashboard']);
|
|
this.router.navigate(['/dashboard']);
|
|
@@ -130,9 +634,12 @@ export class MonitoringComponent {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 格式化日期
|
|
|
|
|
|
|
+ // 格式化日期 - 统一为年/月/日格式
|
|
|
formatDate(date: Date): string {
|
|
formatDate(date: Date): string {
|
|
|
- return date.toLocaleDateString('zh-CN');
|
|
|
|
|
|
|
+ const year = date.getFullYear();
|
|
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
+ const day = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
+ return `${year}/${month}/${day}`;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 计算距离下次经期的天数
|
|
// 计算距离下次经期的天数
|
|
@@ -148,13 +655,92 @@ export class MonitoringComponent {
|
|
|
getHealthAdvice(metric: string): string {
|
|
getHealthAdvice(metric: string): string {
|
|
|
switch (metric) {
|
|
switch (metric) {
|
|
|
case 'heartRate':
|
|
case 'heartRate':
|
|
|
- return '您的心率处于正常范围,请继续保持规律运动和健康饮食。';
|
|
|
|
|
|
|
+ if (this.heartRate.status === 'normal') {
|
|
|
|
|
+ return '您的心率处于正常范围,请继续保持规律运动和健康饮食。';
|
|
|
|
|
+ } else if (this.heartRate.status === 'warning') {
|
|
|
|
|
+ return this.heartRate.value < 60 ? '心率偏低,建议增加有氧运动,如有不适请咨询医生。' : '心率偏高,建议避免剧烈运动,保持情绪稳定,充分休息。';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return '心率异常,建议尽快就医检查,并避免剧烈运动。';
|
|
|
|
|
+ }
|
|
|
case 'bloodPressure':
|
|
case 'bloodPressure':
|
|
|
- return '血压控制良好,建议减少盐的摄入,保持适量运动。';
|
|
|
|
|
|
|
+ const systolicStatus = this.bloodPressure.systolic.status;
|
|
|
|
|
+ const diastolicStatus = this.bloodPressure.diastolic.status;
|
|
|
|
|
+
|
|
|
|
|
+ if (systolicStatus === 'normal' && diastolicStatus === 'normal') {
|
|
|
|
|
+ return '血压控制良好,建议减少盐的摄入,保持适量运动。';
|
|
|
|
|
+ } else if (systolicStatus === 'warning' || diastolicStatus === 'warning') {
|
|
|
|
|
+ return '血压略偏高,建议减少盐分摄入,增加运动,定期监测。';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return '血压明显偏高,建议遵循医嘱,限制盐分,避免剧烈运动。';
|
|
|
|
|
+ }
|
|
|
|
|
+ case 'bloodOxygen':
|
|
|
|
|
+ if (this.bloodOxygen.status === 'normal') {
|
|
|
|
|
+ return '血氧饱和度正常,继续保持良好的生活习惯。';
|
|
|
|
|
+ } else if (this.bloodOxygen.status === 'warning') {
|
|
|
|
|
+ return '血氧饱和度略低,建议增加户外活动,保持良好通风环境。';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return '血氧饱和度偏低,建议尽快就医检查,避免高海拔和密闭环境。';
|
|
|
|
|
+ }
|
|
|
case 'menstrualCycle':
|
|
case 'menstrualCycle':
|
|
|
return '经期规律,注意休息和保暖,避免过度劳累。';
|
|
return '经期规律,注意休息和保暖,避免过度劳累。';
|
|
|
default:
|
|
default:
|
|
|
return '保持健康的生活方式,定期监测健康指标。';
|
|
return '保持健康的生活方式,定期监测健康指标。';
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 获取详细健康报告
|
|
|
|
|
+ getDetailedHealthReport(): string {
|
|
|
|
|
+ let report = '## 健康状况报告\n\n';
|
|
|
|
|
+
|
|
|
|
|
+ // 心率评估
|
|
|
|
|
+ report += `### 心率状况\n`;
|
|
|
|
|
+ report += `- 当前值: ${this.heartRate.value} ${this.heartRate.unit}\n`;
|
|
|
|
|
+ report += `- 状态: ${this.getStatusText(this.heartRate.status)}\n`;
|
|
|
|
|
+ report += `- 趋势: ${this.getTrendText(this.heartRate.trend)}\n\n`;
|
|
|
|
|
+
|
|
|
|
|
+ // 血压评估
|
|
|
|
|
+ report += `### 血压状况\n`;
|
|
|
|
|
+ report += `- 当前值: ${this.bloodPressure.systolic.value}/${this.bloodPressure.diastolic.value} ${this.bloodPressure.systolic.unit}\n`;
|
|
|
|
|
+ report += `- 状态: ${this.getStatusText(this.bloodPressure.systolic.status)}\n`;
|
|
|
|
|
+ report += `- 趋势: ${this.getTrendText(this.bloodPressure.systolic.trend)}\n\n`;
|
|
|
|
|
+
|
|
|
|
|
+ // 血氧评估
|
|
|
|
|
+ report += `### 血氧状况\n`;
|
|
|
|
|
+ report += `- 当前值: ${this.bloodOxygen.value} ${this.bloodOxygen.unit}\n`;
|
|
|
|
|
+ report += `- 状态: ${this.getStatusText(this.bloodOxygen.status)}\n`;
|
|
|
|
|
+ report += `- 趋势: ${this.getTrendText(this.bloodOxygen.trend)}\n\n`;
|
|
|
|
|
+
|
|
|
|
|
+ // 综合建议
|
|
|
|
|
+ report += `### 综合建议\n`;
|
|
|
|
|
+ report += `- 保持每日30分钟中等强度有氧运动\n`;
|
|
|
|
|
+ report += `- 饮食清淡,减少盐分和脂肪摄入\n`;
|
|
|
|
|
+ report += `- 保证充足睡眠,避免熬夜\n`;
|
|
|
|
|
+ report += `- 保持良好心态,避免长期精神压力\n`;
|
|
|
|
|
+
|
|
|
|
|
+ if (this.bloodPressure.systolic.status !== 'normal' || this.bloodPressure.diastolic.status !== 'normal') {
|
|
|
|
|
+ report += `- 建议每日测量血压并记录,如有异常及时就医\n`;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return report;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取状态文本
|
|
|
|
|
+ getStatusText(status?: string): string {
|
|
|
|
|
+ switch (status) {
|
|
|
|
|
+ case 'normal': return '正常';
|
|
|
|
|
+ case 'warning': return '注意';
|
|
|
|
|
+ case 'danger': return '异常';
|
|
|
|
|
+ default: return '未知';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取趋势文本
|
|
|
|
|
+ getTrendText(trend?: string): string {
|
|
|
|
|
+ switch (trend) {
|
|
|
|
|
+ case 'up': return '上升';
|
|
|
|
|
+ case 'down': return '下降';
|
|
|
|
|
+ case 'stable': return '稳定';
|
|
|
|
|
+ default: return '未知';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|