瀏覽代碼

feat(weight): 添加体重管理模块和导航功能

- 创建体重管理模块,包含组件、路由和样式
- 在仪表盘添加体重卡片并实现导航功能
- 优化运动模块的热量显示样式
17846405080 1 周之前
父節點
當前提交
0a749e3dd0

+ 4 - 0
campus_health_app/frontend/campus-health-app/src/app/app.routes.server.ts

@@ -37,6 +37,10 @@ export const serverRoutes: Routes = [
     path: 'school-services',
     loadChildren: () => import('./modules/school-services/school-services.routes').then(m => m.schoolServicesRoutes)
   },
+  {
+    path: 'weight',
+    loadChildren: () => import('./modules/weight/weight.routes').then(m => m.weightRoutes)
+  },
   {
     path: '',
     redirectTo: 'login',

+ 4 - 0
campus_health_app/frontend/campus-health-app/src/app/app.routes.ts

@@ -35,6 +35,10 @@ export const routes: Routes = [
     path: 'school-services',
     loadChildren: () => import('./modules/school-services/school-services.routes').then(m => m.schoolServicesRoutes)
   },
+  {
+    path: 'weight',
+    loadChildren: () => import('./modules/weight/weight.routes').then(m => m.weightRoutes)
+  },
   {
     path: '',
     redirectTo: 'login',

+ 2 - 1
campus_health_app/frontend/campus-health-app/src/app/modules/dashboard/dashboard.component.html

@@ -57,11 +57,12 @@
 
       <!-- 健康数据概览卡片 -->
       <div class="dashboard-cards">
-        <div class="dashboard-card">
+        <div class="dashboard-card" (click)="navigateTo('weight')" style="cursor: pointer;">
           <div class="card-icon">⚖️</div>
           <div class="card-content">
             <h3>体重管理</h3>
             <p>最近一次记录: 65kg</p>
+            <p class="click-hint">点击查看详细体重数据</p>
           </div>
         </div>
         <div class="dashboard-card">

+ 23 - 95
campus_health_app/frontend/campus-health-app/src/app/modules/exercise/exercise.component.html

@@ -5,108 +5,36 @@
   </header>
 
   <main class="exercise-main">
-    <!-- 今日运动概览 -->
-    <div class="exercise-overview">
-      <h2>今日运动概览</h2>
-      <div class="exercise-summary">
-        <div class="summary-item">
-          <span class="summary-label">总时长</span>
-          <span class="summary-value">75分钟</span>
-          <div class="progress-ring">
-            <svg class="progress-ring-svg" width="60" height="60">
-              <circle class="progress-ring-circle-bg" cx="30" cy="30" r="25"></circle>
-              <circle class="progress-ring-circle" cx="30" cy="30" r="25" style="stroke-dasharray: 157; stroke-dashoffset: 39;"></circle>
-            </svg>
-            <span class="progress-text">75%</span>
-          </div>
-        </div>
-        <div class="summary-item">
-          <span class="summary-label">消耗热量</span>
-          <span class="summary-value">550 千卡</span>
-          <div class="progress-ring">
-            <svg class="progress-ring-svg" width="60" height="60">
-              <circle class="progress-ring-circle-bg" cx="30" cy="30" r="25"></circle>
-              <circle class="progress-ring-circle calories" cx="30" cy="30" r="25" style="stroke-dasharray: 157; stroke-dashoffset: 55;"></circle>
-            </svg>
-            <span class="progress-text">65%</span>
-          </div>
-        </div>
-        <div class="summary-item">
-          <span class="summary-label">总距离</span>
-          <span class="summary-value">3.5公里</span>
-          <div class="progress-ring">
-            <svg class="progress-ring-svg" width="60" height="60">
-              <circle class="progress-ring-circle-bg" cx="30" cy="30" r="25"></circle>
-              <circle class="progress-ring-circle distance" cx="30" cy="30" r="25" style="stroke-dasharray: 157; stroke-dashoffset: 47;"></circle>
-            </svg>
-            <span class="progress-text">70%</span>
-          </div>
-        </div>
-      </div>
-      
-      <!-- 运动类型分布 -->
-      <div class="exercise-chart">
-        <h3>运动类型分布</h3>
-        <div class="chart-container">
-          <div class="chart-legend">
-            <div class="legend-item">
-              <span class="legend-color cardio"></span>
-              <span class="legend-label">有氧运动</span>
-              <span class="legend-value">45分钟</span>
-            </div>
-            <div class="legend-item">
-              <span class="legend-color strength"></span>
-              <span class="legend-label">力量训练</span>
-              <span class="legend-value">20分钟</span>
-            </div>
-            <div class="legend-item">
-              <span class="legend-color flexibility"></span>
-              <span class="legend-label">柔韧性</span>
-              <span class="legend-value">10分钟</span>
-            </div>
-          </div>
-          <div class="chart-visual">
-            <div class="chart-segment cardio" style="--percentage: 60%;"></div>
-            <div class="chart-segment strength" style="--percentage: 27%;"></div>
-            <div class="chart-segment flexibility" style="--percentage: 13%;"></div>
-          </div>
-        </div>
-      </div>
-    </div>
-
     <!-- 今日运动记录列表 -->
     <div class="exercises-list">
       <h2>今日运动记录</h2>
-      <div class="exercises-container">
-        <ng-container *ngFor="let exercise of todayExercises">
-          <div class="exercise-card">
-            <div class="exercise-header">
-              <h3>{{ exercise.type }}</h3>
-              <span class="exercise-time">{{ exercise.time }}</span>
-            </div>
-            <div class="exercise-content">
-              <div class="exercise-details">
-                <div class="detail-item">
-                  <span class="detail-label">时长</span>
-                  <span class="detail-value">{{ exercise.duration }}</span>
-                </div>
-                <div class="detail-item" *ngIf="exercise.distance">
-                  <span class="detail-label">距离</span>
-                  <span class="detail-value">{{ exercise.distance }}</span>
-                </div>
-                <div class="detail-item" *ngIf="exercise.description">
-                  <span class="detail-label">描述</span>
-                  <span class="detail-value">{{ exercise.description }}</span>
-                </div>
+      <ng-container *ngFor="let exercise of todayExercises">
+        <div class="exercise-card">
+          <div class="exercise-header">
+            <h3>{{ exercise.type }}</h3>
+          </div>
+          <div class="exercise-content">
+            <div class="exercise-details">
+              <div class="detail-item">
+                <span class="detail-label">时长</span>
+                <span class="detail-value">{{ exercise.duration }}</span>
+              </div>
+              <div class="detail-item" *ngIf="exercise.distance">
+                <span class="detail-label">距离</span>
+                <span class="detail-value">{{ exercise.distance }}</span>
               </div>
-              <div class="calories-info">
-                <span class="calories-label">消耗热量</span>
-                <span class="calories-value">{{ exercise.calories }} 千卡</span>
+              <div class="detail-item" *ngIf="exercise.description">
+                <span class="detail-label">描述</span>
+                <span class="detail-value">{{ exercise.description }}</span>
+              </div>
+              <div class="detail-item calories-highlight">
+                <span class="detail-label">消耗热量</span>
+                <span class="detail-value calories-highlight">{{ exercise.calories }} 千卡</span>
               </div>
             </div>
           </div>
-        </ng-container>
-      </div>
+        </div>
+      </ng-container>
     </div>
 
     <!-- 添加运动按钮 -->

+ 6 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/exercise/exercise.component.scss

@@ -316,6 +316,12 @@
   font-weight: 500;
 }
 
+.detail-item.calories-highlight .detail-value.calories-highlight {
+  color: #ea4335;
+  font-weight: 600;
+  font-size: 1rem;
+}
+
 .calories-info {
   display: flex;
   justify-content: space-between;

+ 76 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/weight/weight.component.html

@@ -0,0 +1,76 @@
+<div class="weight-container">
+  <!-- 头部导航 -->
+  <header class="weight-header">
+    <button class="back-button" (click)="backToDashboard()">← 返回</button>
+    <h1>体重管理</h1>
+  </header>
+
+  <!-- 主内容区域 -->
+  <main class="weight-main">
+    <!-- 当前体重概览 -->
+    <div class="weight-overview">
+      <h2>当前体重信息</h2>
+      <div class="current-stats">
+        <div class="stat-card">
+          <div class="stat-icon">⚖️</div>
+          <div class="stat-content">
+            <span class="stat-label">体重</span>
+            <span class="stat-value">{{ latestWeight }} kg</span>
+          </div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-icon">🧍</div>
+          <div class="stat-content">
+            <span class="stat-label">体脂率</span>
+            <span class="stat-value">{{ latestBodyFat }}%</span>
+          </div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-icon">💪</div>
+          <div class="stat-content">
+            <span class="stat-label">肌肉含量</span>
+            <span class="stat-value">{{ latestMuscleMass }} kg</span>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 历史体重记录列表 -->
+    <div class="weight-records">
+      <h2>历史体重记录</h2>
+      <div class="records-container">
+        <@for (record of weightRecords; track record.date) {
+          <div class="record-card">
+            <div class="record-header">
+              <span class="record-date">{{ record.date }}</span>
+            </div>
+            <div class="record-details">
+              <div class="detail-item">
+                <span class="detail-label">体重</span>
+                <span class="detail-value weight-value">{{ record.weight }} kg</span>
+              </div>
+              <div class="detail-item">
+                <span class="detail-label">体脂率</span>
+                <span class="detail-value body-fat-value">{{ record.bodyFat }}%</span>
+              </div>
+              <div class="detail-item">
+                <span class="detail-label">肌肉含量</span>
+                <span class="detail-value muscle-value">{{ record.muscleMass }} kg</span>
+              </div>
+            </div>
+          </div>
+        } @empty {
+          <div class="no-records">
+            <p>暂无体重记录</p>
+          </div>
+        }
+      </div>
+    </div>
+
+    <!-- 健康建议区域 -->
+    <div class="weight-tips">
+      <h3>体重管理建议</h3>
+      <p>保持规律的测量频率,建议每周同一时间记录一次体重。结合合理饮食和适量运动,逐步达到健康体重目标。</p>
+    </div>
+  </main>
+</div>

+ 273 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/weight/weight.component.scss

@@ -0,0 +1,273 @@
+// Weight组件样式
+
+.weight-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+  color: #333;
+}
+
+// 头部导航
+.weight-header {
+  display: flex;
+  align-items: center;
+  padding: 1.5rem 2rem;
+  background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
+  color: white;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+}
+
+.back-button {
+  background: rgba(255, 255, 255, 0.1);
+  border: 1px solid rgba(255, 255, 255, 0.2);
+  border-radius: 12px;
+  padding: 0.75rem 1rem;
+  font-size: 1rem;
+  cursor: pointer;
+  margin-right: 1.5rem;
+  color: white;
+  transition: all 0.3s ease;
+  backdrop-filter: blur(10px);
+
+  &:hover {
+    background: rgba(255, 255, 255, 0.2);
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  }
+}
+
+.weight-header h1 {
+  margin: 0;
+  font-size: 1.8rem;
+  color: white;
+  font-weight: 700;
+}
+
+// 主内容区域
+.weight-main {
+  flex: 1;
+  padding: 2rem;
+  overflow-y: auto;
+}
+
+// 体重概览区域
+.weight-overview {
+  background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+  border-radius: 20px;
+  padding: 2rem;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+  margin-bottom: 2rem;
+  border: 1px solid rgba(226, 232, 240, 0.5);
+}
+
+.weight-overview h2 {
+  margin: 0 0 1.5rem 0;
+  font-size: 1.5rem;
+  color: #1e293b;
+  font-weight: 700;
+  background: linear-gradient(135deg, #1e293b 0%, #6366f1 100%);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.current-stats {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+  gap: 1.5rem;
+}
+
+.stat-card {
+  background: white;
+  border-radius: 16px;
+  padding: 1.5rem;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  border: 1px solid rgba(226, 232, 240, 0.5);
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+  transition: transform 0.3s ease, box-shadow 0.3s ease;
+
+  &:hover {
+    transform: translateY(-4px);
+    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+  }
+}
+
+.stat-icon {
+  font-size: 2.5rem;
+}
+
+.stat-content {
+  flex: 1;
+}
+
+.stat-label {
+  display: block;
+  color: #64748b;
+  font-size: 0.95rem;
+  font-weight: 500;
+  margin-bottom: 0.5rem;
+}
+
+.stat-value {
+  display: block;
+  font-size: 1.8rem;
+  font-weight: 700;
+  color: #4f46e5;
+}
+
+// 历史体重记录列表
+.weight-records {
+  margin-bottom: 2rem;
+}
+
+.weight-records h2 {
+  margin: 0 0 1.5rem 0;
+  font-size: 1.5rem;
+  color: #1e293b;
+  font-weight: 700;
+  background: linear-gradient(135deg, #1e293b 0%, #6366f1 100%);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.records-container {
+  display: flex;
+  flex-direction: column;
+  gap: 1.2rem;
+}
+
+.record-card {
+  background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+  border-radius: 16px;
+  padding: 1.5rem;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  border: 1px solid rgba(226, 232, 240, 0.5);
+  position: relative;
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 4px;
+    background: linear-gradient(90deg, #6366f1 0%, #4f46e5 100%);
+    transform: scaleX(0);
+    transition: transform 0.3s ease;
+  }
+
+  &:hover {
+    transform: translateY(-4px);
+    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
+    
+    &::before {
+      transform: scaleX(1);
+    }
+  }
+}
+
+.record-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 1rem;
+}
+
+.record-date {
+  color: #202124;
+  font-size: 1.1rem;
+  font-weight: 600;
+}
+
+.record-details {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+  gap: 1rem;
+  padding-top: 1rem;
+  border-top: 1px solid #f1f3f4;
+}
+
+.detail-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.detail-label {
+  color: #64748b;
+  font-size: 0.95rem;
+  font-weight: 500;
+}
+
+.detail-value {
+  color: #202124;
+  font-size: 1rem;
+  font-weight: 600;
+}
+
+.weight-value {
+  color: #4f46e5;
+}
+
+.body-fat-value {
+  color: #ec4899;
+}
+
+.muscle-value {
+  color: #10b981;
+}
+
+.no-records {
+  text-align: center;
+  padding: 4rem 2rem;
+  color: #64748b;
+}
+
+// 健康建议区域
+.weight-tips {
+  background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
+  border-radius: 20px;
+  padding: 2rem;
+  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
+  border: 1px solid rgba(226, 232, 240, 0.5);
+  position: relative;
+  overflow: hidden;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 4px;
+    background: linear-gradient(90deg, #10b981 0%, #059669 100%);
+  }
+}
+
+.weight-tips h3 {
+  margin: 0 0 1.5rem 0;
+  font-size: 1.3rem;
+  color: #1e293b;
+  font-weight: 700;
+  background: linear-gradient(135deg, #1e293b 0%, #10b981 100%);
+  -webkit-background-clip: text;
+  -webkit-text-fill-color: transparent;
+  background-clip: text;
+}
+
+.weight-tips p {
+  margin: 0;
+  color: #64748b;
+  font-size: 1rem;
+  line-height: 1.6;
+  padding: 1rem;
+  background: linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%);
+  border-radius: 12px;
+  border-left: 4px solid #10b981;
+}

+ 53 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/weight/weight.component.ts

@@ -0,0 +1,53 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Router, RouterModule } from '@angular/router';
+
+// 体重记录数据模型
+interface WeightRecord {
+  date: string; // 记录日期
+  weight: number; // 体重(kg)
+  bodyFat: number; // 体脂率(%)
+  muscleMass: number; // 肌肉含量(kg)
+}
+
+@Component({
+  selector: 'app-weight',
+  standalone: true,
+  imports: [CommonModule, RouterModule],
+  templateUrl: './weight.component.html',
+  styleUrl: './weight.component.scss'
+})
+export class WeightComponent {
+  // 历史体重记录数据(模拟数据)
+  weightRecords: WeightRecord[] = [
+    { date: '2025-10-13', weight: 65.0, bodyFat: 22.5, muscleMass: 28.5 },
+    { date: '2025-10-10', weight: 65.2, bodyFat: 22.7, muscleMass: 28.3 },
+    { date: '2025-10-07', weight: 65.5, bodyFat: 23.0, muscleMass: 28.0 },
+    { date: '2025-10-04', weight: 65.8, bodyFat: 23.2, muscleMass: 27.8 },
+    { date: '2025-10-01', weight: 66.0, bodyFat: 23.5, muscleMass: 27.5 },
+    { date: '2025-09-28', weight: 66.2, bodyFat: 23.8, muscleMass: 27.3 },
+    { date: '2025-09-25', weight: 66.5, bodyFat: 24.0, muscleMass: 27.0 }
+  ];
+
+  // 最近一次记录的体重
+  get latestWeight(): number {
+    return this.weightRecords.length > 0 ? this.weightRecords[0].weight : 0;
+  }
+
+  // 最近一次记录的体脂率
+  get latestBodyFat(): number {
+    return this.weightRecords.length > 0 ? this.weightRecords[0].bodyFat : 0;
+  }
+
+  // 最近一次记录的肌肉含量
+  get latestMuscleMass(): number {
+    return this.weightRecords.length > 0 ? this.weightRecords[0].muscleMass : 0;
+  }
+
+  constructor(private router: Router) {}
+
+  // 返回仪表盘
+  backToDashboard(): void {
+    this.router.navigate(['/dashboard']);
+  }
+}

+ 10 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/weight/weight.routes.ts

@@ -0,0 +1,10 @@
+import { Routes } from '@angular/router';
+import { WeightComponent } from './weight.component';
+
+export const weightRoutes: Routes = [
+  {
+    path: '',
+    component: WeightComponent,
+    title: '体重管理 - 数智健调系统'
+  }
+];