weight.component.html 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. <div class="weight-container">
  2. <header class="header">
  3. <button class="back-btn" (click)="backToDashboard()">← 返回</button>
  4. <h1>体重管理</h1>
  5. <div class="header-actions">
  6. <button class="btn btn-primary" (click)="openAddRecordDialog()">+ 录入体重</button>
  7. <button class="btn btn-secondary" (click)="openGoalDialog()">目标设置</button>
  8. </div>
  9. </header>
  10. <div class="stats-cards" *ngIf="stats">
  11. <div class="stat-card">
  12. <div class="stat-label">当前体重</div>
  13. <div class="stat-value">{{ stats.currentWeight.toFixed(1) }} <span>kg</span></div>
  14. </div>
  15. <div class="stat-card">
  16. <div class="stat-label">体重变化</div>
  17. <div class="stat-value" [class.positive]="stats.weightChange > 0" [class.negative]="stats.weightChange < 0">
  18. {{ stats.weightChange > 0 ? '+' : '' }}{{ stats.weightChange.toFixed(1) }} <span>kg</span>
  19. </div>
  20. </div>
  21. <div class="stat-card">
  22. <div class="stat-label">平均周变化</div>
  23. <div class="stat-value" [class.positive]="stats.avgWeeklyChange > 0" [class.negative]="stats.avgWeeklyChange < 0">
  24. {{ stats.avgWeeklyChange > 0 ? '+' : '' }}{{ stats.avgWeeklyChange.toFixed(2) }} <span>kg/周</span>
  25. </div>
  26. </div>
  27. </div>
  28. <div class="chart-section">
  29. <div class="trend-chart-container">
  30. <div id="trendChart" class="chart" style="width: 100%; height: 100%;"></div>
  31. <div class="trend-summary" *ngIf="records.length > 0">
  32. <p>
  33. 当前 {{ records[0].weight.toFixed(1) }}kg,较 {{ records[records.length - 1].date }}
  34. {{ records[records.length - 1].weight > records[0].weight ? '减少' : '增加' }}
  35. {{ Math.abs(records[records.length - 1].weight - records[0].weight).toFixed(1) }}kg
  36. </p>
  37. </div>
  38. </div>
  39. <div class="progress-chart-container" *ngIf="goal">
  40. <div id="progressChart" class="chart-small" style="width: 100%; height: 100%;"></div>
  41. </div>
  42. </div>
  43. <div class="filter-bar">
  44. <button class="filter-btn" [class.active]="filter.timePeriod === '7days'" (click)="updateTimePeriod('7days')">7天</button>
  45. <button class="filter-btn" [class.active]="filter.timePeriod === '30days'" (click)="updateTimePeriod('30days')">30天</button>
  46. <button class="filter-btn" [class.active]="filter.timePeriod === '90days'" (click)="updateTimePeriod('90days')">90天</button>
  47. <button class="filter-btn" [class.active]="filter.timePeriod === 'all'" (click)="updateTimePeriod('all')">全部</button>
  48. <button class="filter-btn" [class.active]="filter.timePeriod === 'custom'" (click)="showCustomDatePicker = !showCustomDatePicker">自定义</button>
  49. <select class="filter-select" [(ngModel)]="filter.condition" (change)="applyFilter(); updateCharts();">
  50. <option value="all">全部条件</option>
  51. <option value="fasting">空腹</option>
  52. <option value="after_meal">餐后</option>
  53. </select>
  54. </div>
  55. <!-- 自定义日期范围选择器 -->
  56. <div class="custom-date-picker" *ngIf="showCustomDatePicker">
  57. <div class="date-range-inputs">
  58. <div class="date-input-group">
  59. <label>开始日期</label>
  60. <input type="date" [(ngModel)]="filter.startDate" />
  61. </div>
  62. <div class="date-input-group">
  63. <label>结束日期</label>
  64. <input type="date" [(ngModel)]="filter.endDate" />
  65. </div>
  66. <button class="btn btn-primary" (click)="applyCustomDateRange()">应用</button>
  67. </div>
  68. </div>
  69. <!-- 标签筛选 -->
  70. <div class="tag-filter" *ngIf="availableTags.length > 0">
  71. <div class="tag-filter-title">按标签筛选:</div>
  72. <div class="tag-filter-buttons">
  73. <button *ngFor="let tag of availableTags" class="tag-filter-btn" [class.active]="filter.tags?.includes(tag)" (click)="toggleFilterTag(tag)">
  74. {{ tag }}
  75. </button>
  76. </div>
  77. </div>
  78. <!-- 体重变化与体脂率分析(横向布局) -->
  79. <div class="analysis-charts-container">
  80. <div class="change-chart-section">
  81. <div class="chart-header">
  82. <h3 class="chart-title">体重变化趋势</h3>
  83. <button class="toggle-btn" (click)="toggleChartPeriod()">切换至{{ chartPeriod === 'weekly' ? '月' : '周' }}视图</button>
  84. </div>
  85. <div id="changeChart" class="chart-medium" style="width: 100%; height: 100%;"></div>
  86. </div>
  87. <div class="scatter-chart-section">
  88. <div class="chart-header">
  89. <h3 class="chart-title">体重体脂率关系</h3>
  90. </div>
  91. <div id="scatterChart" class="chart-medium" style="width: 100%; height: 100%;"></div>
  92. </div>
  93. </div>
  94. <div class="anomaly-section" *ngIf="anomalies.length > 0">
  95. <h3>健康提醒</h3>
  96. <div class="anomaly-list">
  97. <div *ngFor="let anomaly of anomalies" class="anomaly-item" [class.info]="anomaly.severity === 'info'" [class.warning]="anomaly.severity === 'warning'" [class.danger]="anomaly.severity === 'danger'">
  98. <span class="anomaly-icon">{{ anomaly.severity === 'info' ? 'ℹ️' : anomaly.severity === 'warning' ? '⚠️' : '❗' }}</span>
  99. <span class="anomaly-message">{{ anomaly.message }}</span>
  100. </div>
  101. </div>
  102. </div>
  103. <!-- 添加记录对话框 -->
  104. <app-modal
  105. [(isOpen)]="showAddRecordDialog"
  106. [config]="{
  107. title: '录入体重数据',
  108. icon: '⚖️',
  109. size: 'medium',
  110. theme: 'default',
  111. showFooter: false
  112. }">
  113. <div class="weight-record-form">
  114. <div class="form-group">
  115. <label>日期</label>
  116. <input type="date" [(ngModel)]="newRecord.date" max="{{ today }}" />
  117. </div>
  118. <div class="form-group">
  119. <label>测量时间</label>
  120. <input type="time" [(ngModel)]="newRecord.measurementTime" />
  121. </div>
  122. <div class="form-group">
  123. <label>体重 (kg)</label>
  124. <input type="number" [(ngModel)]="newRecord.weight" step="0.1" min="30" max="200" />
  125. </div>
  126. <div class="form-group">
  127. <label>体脂率 (%)</label>
  128. <input type="number" [(ngModel)]="newRecord.bodyFat" step="0.1" min="5" max="60" />
  129. </div>
  130. <div class="form-group">
  131. <label>肌肉量 (kg)</label>
  132. <input type="number" [(ngModel)]="newRecord.muscleMass" step="0.1" min="10" max="100" />
  133. </div>
  134. <div class="form-group">
  135. <label>测量条件</label>
  136. <select [(ngModel)]="newRecord.measurementCondition">
  137. <option value="fasting">空腹</option>
  138. <option value="after_meal">餐后</option>
  139. </select>
  140. </div>
  141. <div class="form-group">
  142. <label>备注</label>
  143. <textarea [(ngModel)]="newRecord.notes" rows="3"></textarea>
  144. </div>
  145. <div class="form-group">
  146. <label>标签(关键节点标记)</label>
  147. <div class="tag-selector">
  148. <div class="available-tags">
  149. <button *ngFor="let tag of availableTags" type="button" class="tag-btn" [class.selected]="newRecord.tags?.includes(tag)" (click)="addTagToRecord(tag)">
  150. {{ tag }}
  151. </button>
  152. </div>
  153. <div class="selected-tags" *ngIf="newRecord.tags && newRecord.tags.length > 0">
  154. <span class="selected-tag" *ngFor="let tag of newRecord.tags">
  155. {{ tag }}
  156. <button type="button" class="remove-tag" (click)="removeTagFromRecord(tag)">×</button>
  157. </span>
  158. </div>
  159. <div class="custom-tag-input">
  160. <input type="text" [(ngModel)]="newTagInput" placeholder="添加自定义标签" (keyup.enter)="addCustomTag()" />
  161. <button type="button" class="btn btn-secondary btn-sm" (click)="addCustomTag()">添加</button>
  162. </div>
  163. </div>
  164. </div>
  165. <div class="dialog-actions">
  166. <button class="btn btn-secondary" (click)="showAddRecordDialog = false">取消</button>
  167. <button class="btn btn-primary" (click)="addRecord()">保存</button>
  168. </div>
  169. </div>
  170. </app-modal>
  171. <!-- 目标设置对话框 -->
  172. <app-modal
  173. [(isOpen)]="showGoalDialog"
  174. [config]="{
  175. title: '设置目标',
  176. icon: '🎯',
  177. size: 'small',
  178. theme: 'gradient',
  179. showFooter: false
  180. }">
  181. <div class="goal-setting-form">
  182. <div class="form-group">
  183. <label>目标体重 (kg)</label>
  184. <input type="number" [(ngModel)]="newGoal.targetWeight" step="0.1" min="30" max="200" />
  185. </div>
  186. <div class="form-group">
  187. <label>目标体脂率 (%)</label>
  188. <input type="number" [(ngModel)]="newGoal.targetBodyFat" step="0.1" min="5" max="60" />
  189. </div>
  190. <div class="form-group">
  191. <label>目标日期</label>
  192. <input type="date" [(ngModel)]="newGoal.targetDate" [min]="tomorrow" />
  193. </div>
  194. <div class="dialog-actions">
  195. <button class="btn btn-secondary" (click)="showGoalDialog = false">取消</button>
  196. <button class="btn btn-primary" (click)="setGoal()">保存</button>
  197. </div>
  198. </div>
  199. </app-modal>
  200. <!-- 调试:一键生成90天模拟数据 -->
  201. <div style="padding: 16px; text-align: center;">
  202. <button class="btn btn-secondary" (click)="generateMockData()">一键生成90天模拟数据</button>
  203. </div>
  204. </div>