| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- <div class="weight-container">
- <header class="header">
- <button class="back-btn" (click)="backToDashboard()">← 返回</button>
- <h1>体重管理</h1>
- <div class="header-actions">
- <button class="btn btn-primary" (click)="openAddRecordDialog()">+ 录入体重</button>
- <button class="btn btn-secondary" (click)="openGoalDialog()">目标设置</button>
- </div>
- </header>
- <div class="stats-cards" *ngIf="stats">
- <div class="stat-card">
- <div class="stat-label">当前体重</div>
- <div class="stat-value">{{ stats.currentWeight.toFixed(1) }} <span>kg</span></div>
- </div>
- <div class="stat-card">
- <div class="stat-label">体重变化</div>
- <div class="stat-value" [class.positive]="stats.weightChange > 0" [class.negative]="stats.weightChange < 0">
- {{ stats.weightChange > 0 ? '+' : '' }}{{ stats.weightChange.toFixed(1) }} <span>kg</span>
- </div>
- </div>
- <div class="stat-card">
- <div class="stat-label">平均周变化</div>
- <div class="stat-value" [class.positive]="stats.avgWeeklyChange > 0" [class.negative]="stats.avgWeeklyChange < 0">
- {{ stats.avgWeeklyChange > 0 ? '+' : '' }}{{ stats.avgWeeklyChange.toFixed(2) }} <span>kg/周</span>
- </div>
- </div>
- </div>
- <div class="chart-section">
- <div class="trend-chart-container">
- <div id="trendChart" class="chart" style="width: 100%; height: 100%;"></div>
- <div class="trend-summary" *ngIf="records.length > 0">
- <p>
- 当前 {{ records[0].weight.toFixed(1) }}kg,较 {{ records[records.length - 1].date }}
- {{ records[records.length - 1].weight > records[0].weight ? '减少' : '增加' }}
- {{ Math.abs(records[records.length - 1].weight - records[0].weight).toFixed(1) }}kg
- </p>
- </div>
- </div>
- <div class="progress-chart-container" *ngIf="goal">
- <div id="progressChart" class="chart-small" style="width: 100%; height: 100%;"></div>
- </div>
- </div>
- <div class="filter-bar">
- <button class="filter-btn" [class.active]="filter.timePeriod === '7days'" (click)="updateTimePeriod('7days')">7天</button>
- <button class="filter-btn" [class.active]="filter.timePeriod === '30days'" (click)="updateTimePeriod('30days')">30天</button>
- <button class="filter-btn" [class.active]="filter.timePeriod === '90days'" (click)="updateTimePeriod('90days')">90天</button>
- <button class="filter-btn" [class.active]="filter.timePeriod === 'all'" (click)="updateTimePeriod('all')">全部</button>
- <button class="filter-btn" [class.active]="filter.timePeriod === 'custom'" (click)="showCustomDatePicker = !showCustomDatePicker">自定义</button>
- <select class="filter-select" [(ngModel)]="filter.condition" (change)="applyFilter(); updateCharts();">
- <option value="all">全部条件</option>
- <option value="fasting">空腹</option>
- <option value="after_meal">餐后</option>
- </select>
- </div>
- <!-- 自定义日期范围选择器 -->
- <div class="custom-date-picker" *ngIf="showCustomDatePicker">
- <div class="date-range-inputs">
- <div class="date-input-group">
- <label>开始日期</label>
- <input type="date" [(ngModel)]="filter.startDate" />
- </div>
- <div class="date-input-group">
- <label>结束日期</label>
- <input type="date" [(ngModel)]="filter.endDate" />
- </div>
- <button class="btn btn-primary" (click)="applyCustomDateRange()">应用</button>
- </div>
- </div>
- <!-- 标签筛选 -->
- <div class="tag-filter" *ngIf="availableTags.length > 0">
- <div class="tag-filter-title">按标签筛选:</div>
- <div class="tag-filter-buttons">
- <button *ngFor="let tag of availableTags" class="tag-filter-btn" [class.active]="filter.tags?.includes(tag)" (click)="toggleFilterTag(tag)">
- {{ tag }}
- </button>
- </div>
- </div>
- <!-- 体重变化与体脂率分析(横向布局) -->
- <div class="analysis-charts-container">
- <div class="change-chart-section">
- <div class="chart-header">
- <h3 class="chart-title">体重变化趋势</h3>
- <button class="toggle-btn" (click)="toggleChartPeriod()">切换至{{ chartPeriod === 'weekly' ? '月' : '周' }}视图</button>
- </div>
- <div id="changeChart" class="chart-medium" style="width: 100%; height: 100%;"></div>
- </div>
- <div class="scatter-chart-section">
- <div class="chart-header">
- <h3 class="chart-title">体重体脂率关系</h3>
- </div>
- <div id="scatterChart" class="chart-medium" style="width: 100%; height: 100%;"></div>
- </div>
- </div>
- <div class="anomaly-section" *ngIf="anomalies.length > 0">
- <h3>健康提醒</h3>
- <div class="anomaly-list">
- <div *ngFor="let anomaly of anomalies" class="anomaly-item" [class.info]="anomaly.severity === 'info'" [class.warning]="anomaly.severity === 'warning'" [class.danger]="anomaly.severity === 'danger'">
- <span class="anomaly-icon">{{ anomaly.severity === 'info' ? 'ℹ️' : anomaly.severity === 'warning' ? '⚠️' : '❗' }}</span>
- <span class="anomaly-message">{{ anomaly.message }}</span>
- </div>
- </div>
- </div>
- <!-- 添加记录对话框 -->
- <app-modal
- [(isOpen)]="showAddRecordDialog"
- [config]="{
- title: '录入体重数据',
- icon: '⚖️',
- size: 'medium',
- theme: 'default',
- showFooter: false
- }">
- <div class="weight-record-form">
- <div class="form-group">
- <label>日期</label>
- <input type="date" [(ngModel)]="newRecord.date" max="{{ today }}" />
- </div>
- <div class="form-group">
- <label>测量时间</label>
- <input type="time" [(ngModel)]="newRecord.measurementTime" />
- </div>
- <div class="form-group">
- <label>体重 (kg)</label>
- <input type="number" [(ngModel)]="newRecord.weight" step="0.1" min="30" max="200" />
- </div>
- <div class="form-group">
- <label>体脂率 (%)</label>
- <input type="number" [(ngModel)]="newRecord.bodyFat" step="0.1" min="5" max="60" />
- </div>
- <div class="form-group">
- <label>肌肉量 (kg)</label>
- <input type="number" [(ngModel)]="newRecord.muscleMass" step="0.1" min="10" max="100" />
- </div>
- <div class="form-group">
- <label>测量条件</label>
- <select [(ngModel)]="newRecord.measurementCondition">
- <option value="fasting">空腹</option>
- <option value="after_meal">餐后</option>
- </select>
- </div>
- <div class="form-group">
- <label>备注</label>
- <textarea [(ngModel)]="newRecord.notes" rows="3"></textarea>
- </div>
- <div class="form-group">
- <label>标签(关键节点标记)</label>
- <div class="tag-selector">
- <div class="available-tags">
- <button *ngFor="let tag of availableTags" type="button" class="tag-btn" [class.selected]="newRecord.tags?.includes(tag)" (click)="addTagToRecord(tag)">
- {{ tag }}
- </button>
- </div>
- <div class="selected-tags" *ngIf="newRecord.tags && newRecord.tags.length > 0">
- <span class="selected-tag" *ngFor="let tag of newRecord.tags">
- {{ tag }}
- <button type="button" class="remove-tag" (click)="removeTagFromRecord(tag)">×</button>
- </span>
- </div>
- <div class="custom-tag-input">
- <input type="text" [(ngModel)]="newTagInput" placeholder="添加自定义标签" (keyup.enter)="addCustomTag()" />
- <button type="button" class="btn btn-secondary btn-sm" (click)="addCustomTag()">添加</button>
- </div>
- </div>
- </div>
- <div class="dialog-actions">
- <button class="btn btn-secondary" (click)="showAddRecordDialog = false">取消</button>
- <button class="btn btn-primary" (click)="addRecord()">保存</button>
- </div>
- </div>
- </app-modal>
- <!-- 目标设置对话框 -->
- <app-modal
- [(isOpen)]="showGoalDialog"
- [config]="{
- title: '设置目标',
- icon: '🎯',
- size: 'small',
- theme: 'gradient',
- showFooter: false
- }">
- <div class="goal-setting-form">
- <div class="form-group">
- <label>目标体重 (kg)</label>
- <input type="number" [(ngModel)]="newGoal.targetWeight" step="0.1" min="30" max="200" />
- </div>
- <div class="form-group">
- <label>目标体脂率 (%)</label>
- <input type="number" [(ngModel)]="newGoal.targetBodyFat" step="0.1" min="5" max="60" />
- </div>
- <div class="form-group">
- <label>目标日期</label>
- <input type="date" [(ngModel)]="newGoal.targetDate" [min]="tomorrow" />
- </div>
- <div class="dialog-actions">
- <button class="btn btn-secondary" (click)="showGoalDialog = false">取消</button>
- <button class="btn btn-primary" (click)="setGoal()">保存</button>
- </div>
- </div>
- </app-modal>
- <!-- 调试:一键生成90天模拟数据 -->
- <div style="padding: 16px; text-align: center;">
- <button class="btn btn-secondary" (click)="generateMockData()">一键生成90天模拟数据</button>
- </div>
- </div>
|