Просмотр исходного кода

feat(frontend): upgrade app shell, auth flow and shared calendar/modal components

17846405080 1 месяц назад
Родитель
Сommit
ab4c7ba2f6
21 измененных файлов с 926 добавлено и 153 удалено
  1. 2 1
      campus_health_app/frontend/campus-health-app/angular.json
  2. 67 1
      campus_health_app/frontend/campus-health-app/src/app/app.html
  3. 8 5
      campus_health_app/frontend/campus-health-app/src/app/app.routes.ts
  4. 163 2
      campus_health_app/frontend/campus-health-app/src/app/app.scss
  5. 53 4
      campus_health_app/frontend/campus-health-app/src/app/app.ts
  6. 12 12
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/forgot-password/forgot-password.component.scss
  7. 27 45
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/forgot-password/forgot-password.component.ts
  8. 11 11
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/login/login.component.scss
  9. 10 2
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/login/login.component.ts
  10. 8 8
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/register/register.component.scss
  11. 34 20
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/register/register.component.ts
  12. 2 2
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/verify-phone/verify-phone.component.html
  13. 10 10
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/verify-phone/verify-phone.component.scss
  14. 17 25
      campus_health_app/frontend/campus-health-app/src/app/modules/auth/verify-phone/verify-phone.component.ts
  15. 35 0
      campus_health_app/frontend/campus-health-app/src/app/shared/components/calendar-grid/calendar-grid.component.html
  16. 267 0
      campus_health_app/frontend/campus-health-app/src/app/shared/components/calendar-grid/calendar-grid.component.scss
  17. 117 0
      campus_health_app/frontend/campus-health-app/src/app/shared/components/calendar-grid/calendar-grid.component.ts
  18. 15 0
      campus_health_app/frontend/campus-health-app/src/app/shared/components/modal/modal.component.scss
  19. 1 1
      campus_health_app/frontend/campus-health-app/src/app/shared/components/modal/modal.config.ts
  20. 3 2
      campus_health_app/frontend/campus-health-app/src/index.html
  21. 64 2
      campus_health_app/frontend/campus-health-app/src/styles.scss

+ 2 - 1
campus_health_app/frontend/campus-health-app/angular.json

@@ -28,7 +28,8 @@
             ],
             "styles": [
               "src/styles.scss"
-            ]
+            ],
+            "scripts": []
           },
           "configurations": {
             "production": {

+ 67 - 1
campus_health_app/frontend/campus-health-app/src/app/app.html

@@ -1 +1,67 @@
-<router-outlet></router-outlet>
+<div class="app-shell">
+  <main class="app-content">
+    <router-outlet></router-outlet>
+  </main>
+
+  <!-- 底部 Tab 导航,在认证页面隐藏 -->
+  <nav class="app-bottom-nav" *ngIf="showBottomNav">
+    <!-- 首页:仪表盘 -->
+    <a
+      routerLink="/dashboard"
+      routerLinkActive="active"
+      class="nav-item"
+    >
+      <span class="icon">🏠</span>
+      <span class="label">首页</span>
+    </a>
+
+    <!-- 训练动作查询:动作库 -->
+    <a
+      routerLink="/exercise/actions"
+      routerLinkActive="active"
+      class="nav-item"
+    >
+      <span class="icon">🏋️</span>
+      <span class="label">动作</span>
+    </a>
+
+    <!-- 中间加号:快速添加运动或饮食记录 -->
+    <button
+      type="button"
+      class="nav-item nav-plus"
+      (click)="openQuickAdd()"
+    >
+      <span class="plus-circle">+</span>
+    </button>
+
+    <!-- 校园服务 -->
+    <a
+      routerLink="/school-services"
+      routerLinkActive="active"
+      class="nav-item"
+    >
+      <span class="icon">🏫</span>
+      <span class="label">校园</span>
+    </a>
+
+    <!-- 我的:个人信息及账号 -->
+    <a
+      routerLink="/user-center"
+      routerLinkActive="active"
+      class="nav-item"
+    >
+      <span class="icon">👤</span>
+      <span class="label">我的</span>
+    </a>
+  </nav>
+
+  <!-- 快速添加运动/饮食记录的底部弹出面板 -->
+  <div class="quick-add-overlay" *ngIf="showQuickAdd">
+    <div class="quick-add-backdrop" (click)="closeQuickAdd()"></div>
+    <div class="quick-add-sheet">
+      <button class="quick-add-btn" (click)="goToAddExercise()">+ 添加运动记录</button>
+      <button class="quick-add-btn" (click)="goToAddMeal()">+ 添加饮食记录</button>
+      <button class="quick-add-cancel" (click)="closeQuickAdd()">取消</button>
+    </div>
+  </div>
+</div>

+ 8 - 5
campus_health_app/frontend/campus-health-app/src/app/app.routes.ts

@@ -1,11 +1,14 @@
 import { Routes } from '@angular/router';
-import { LoginComponent } from './modules/auth/login/login.component';
 
 export const routes: Routes = [
+  {
+    path: 'auth',
+    loadChildren: () => import('./modules/auth/auth.routes').then(m => m.authRoutes)
+  },
   {
     path: 'login',
-    component: LoginComponent,
-    title: '登录 - 数智健调系统'
+    redirectTo: 'auth/login',
+    pathMatch: 'full'
   },
   {
     path: 'dashboard',
@@ -41,11 +44,11 @@ export const routes: Routes = [
   },
   {
     path: '',
-    redirectTo: 'login',
+    redirectTo: 'auth/login',
     pathMatch: 'full'
   },
   {
     path: '**',
-    redirectTo: 'login'
+    redirectTo: 'auth/login'
   }
 ];

+ 163 - 2
campus_health_app/frontend/campus-health-app/src/app/app.scss

@@ -1,9 +1,170 @@
-/* App组件样式 */
+/* App组件样式 - 移动端 APP 壳结构 */
 :host {
-  display: block;
+  display: flex;
+  justify-content: center;
+  align-items: stretch;
   width: 100%;
+  /* 固定为视口高度并裁剪,保证底部导航栏永远在可视区域内 */
   height: 100vh;
+  height: 100dvh;
+  overflow: hidden;
   background-color: var(--bg-primary);
   color: var(--text-primary);
   transition: background-color 0.3s ease, color 0.3s ease;
+}
+
+.app-shell {
+  flex: 1;
+  width: 100%;
+  height: 100%;
+  max-width: 480px; /* 限制为手机常见宽度,居中显示 */
+  max-height: 100%;
+  margin: 0 auto;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  background-color: var(--bg-primary);
+}
+
+/* 整个 App 只有这一个滚动容器,页面内部不再单独滚动 */
+.app-content {
+  flex: 1 1 auto;
+  min-height: 0;
+  width: 100%;
+  overflow-y: auto;
+  overflow-x: hidden;
+  -webkit-overflow-scrolling: touch;
+  overscroll-behavior: contain;
+}
+
+.app-bottom-nav {
+  flex: 0 0 auto; /* 固定占位,绝不被压缩或被滚动带走 */
+  position: relative;
+  z-index: 20;
+  display: flex;
+  justify-content: space-around;
+  align-items: center;
+  padding: 6px 4px calc(6px + env(safe-area-inset-bottom));
+  border-top: 1px solid rgba(148, 163, 184, 0.4);
+  background: rgba(15, 23, 42, 0.96);
+  backdrop-filter: blur(16px);
+}
+
+.app-bottom-nav .nav-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 2px;
+  padding: 4px 0;
+  color: #e5e7eb;
+  text-decoration: none;
+  font-size: 11px;
+}
+
+.app-bottom-nav .nav-item .icon {
+  font-size: 18px;
+  line-height: 1;
+}
+
+.app-bottom-nav .nav-item .label {
+  font-size: 11px;
+}
+
+.app-bottom-nav .nav-item.active {
+  color: #38bdf8;
+  font-weight: 600;
+}
+
+/* 中间加号样式 */
+.app-bottom-nav .nav-plus {
+  flex: 0 0 auto; /* 防止被压缩,并使其遵循 width 设定 */
+  width: 56px; /* 增加宽度 */
+  height: 36px; /* 稍微增加高度以保持比例 */
+  margin: 0 12px;
+  border: none;
+  background: transparent;
+  padding: 0;
+  z-index: 10;
+  align-self: center;
+}
+
+.plus-circle {
+  width: 100%;
+  height: 100%;
+  border-radius: 8px; /* 较小的圆角 */
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: transparent; /* 透明背景 */
+  border: 2px solid #fff; /* 白色边框 */
+  color: #fff;
+  font-size: 20px;
+  line-height: 1;
+  font-weight: 500;
+  box-shadow: none; /* 移除阴影 */
+  transition: transform 0.2s ease, opacity 0.2s ease;
+}
+
+.app-bottom-nav .nav-plus:active .plus-circle {
+  transform: scale(0.92);
+  opacity: 0.7;
+  box-shadow: none;
+}
+
+/* 快速添加底部弹出层 */
+.quick-add-overlay {
+  position: fixed;
+  inset: 0;
+  display: flex;
+  justify-content: center;
+  align-items: flex-end;
+  z-index: 1100;
+}
+
+.quick-add-backdrop {
+  position: absolute;
+  inset: 0;
+  background: rgba(15, 23, 42, 0.55);
+}
+
+.quick-add-sheet {
+  position: relative;
+  width: 100%;
+  max-width: 480px;
+  margin: 0 auto;
+  background: #0b1120;
+  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
+  border-radius: 16px 16px 0 0;
+  box-shadow: 0 -10px 30px rgba(15, 23, 42, 0.8);
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.quick-add-btn,
+.quick-add-cancel {
+  width: 100%;
+  border-radius: 999px;
+  border: none;
+  padding: 10px 16px;
+  font-size: 14px;
+  cursor: pointer;
+}
+
+.quick-add-btn {
+  background: linear-gradient(135deg, #38bdf8, #6366f1);
+  color: #fff;
+}
+
+.quick-add-cancel {
+  background: transparent;
+  color: #e5e7eb;
+}
+
+@media (max-width: 480px) {
+  .app-shell {
+    max-width: 100%;
+  }
 }

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

@@ -1,11 +1,60 @@
 import { Component } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
+import { NgIf } from '@angular/common';
+import { Router, RouterOutlet, RouterLink, RouterLinkActive, NavigationEnd } from '@angular/router';
+import { filter } from 'rxjs/operators';
 
 @Component({
   selector: 'app-root',
   standalone: true,
-  imports: [RouterOutlet],
-  template: '<router-outlet></router-outlet>',
+  imports: [RouterOutlet, RouterLink, RouterLinkActive, NgIf],
+  templateUrl: './app.html',
   styleUrl: './app.scss'
 })
-export class App {}
+export class App {
+  showQuickAdd = false;
+  showBottomNav = true;
+
+  // 需要隐藏底部导航栏的路由
+  private hideNavRoutes = [
+    '/auth',
+    '/login',
+    '/register'
+  ];
+
+  constructor(public router: Router) {
+    // 监听路由变化,动态控制底部导航栏显示
+    this.router.events
+      .pipe(filter(event => event instanceof NavigationEnd))
+      .subscribe((event: NavigationEnd) => {
+        this.updateBottomNavVisibility(event.url);
+      });
+    
+    // 初始化时检查当前路由
+    this.updateBottomNavVisibility(this.router.url);
+  }
+
+  private updateBottomNavVisibility(url: string): void {
+    // 检查当前路由是否需要隐藏底部导航栏
+    this.showBottomNav = !this.hideNavRoutes.some(route => 
+      url.startsWith(route) || url === route
+    );
+  }
+
+  openQuickAdd(): void {
+    this.showQuickAdd = true;
+  }
+
+  closeQuickAdd(): void {
+    this.showQuickAdd = false;
+  }
+
+  goToAddMeal(): void {
+    this.showQuickAdd = false;
+    this.router.navigate(['/diet/add-meal']);
+  }
+
+  goToAddExercise(): void {
+    this.showQuickAdd = false;
+    this.router.navigate(['/exercise/add']);
+  }
+}

+ 12 - 12
campus_health_app/frontend/campus-health-app/src/app/modules/auth/forgot-password/forgot-password.component.scss

@@ -4,7 +4,7 @@
   justify-content: center;
   align-items: center;
   min-height: 100vh;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #01847F 0%, #016c68 100%);
   padding: 20px;
 }
 
@@ -46,7 +46,7 @@
 
 .phone-number {
   font-weight: 600;
-  color: #059669;
+  color: #01847F;
 }
 
 .forgot-password-form {
@@ -77,9 +77,9 @@
 
   &:focus {
     outline: none;
-    border-color: #34d399;
+    border-color: #01847F;
     background-color: white;
-    box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
+    box-shadow: 0 0 0 3px rgba(1, 132, 127, 0.1);
   }
 
   &:disabled {
@@ -104,7 +104,7 @@
 .btn-resend {
   background: none;
   border: none;
-  color: #059669;
+  color: #01847F;
   cursor: pointer;
   font-size: 14px;
   font-weight: 500;
@@ -112,7 +112,7 @@
   padding: 4px 8px;
 
   &:hover:not(:disabled) {
-    color: #047857;
+    color: #016c68;
   }
 
   &:disabled {
@@ -148,7 +148,7 @@
 }
 
 .btn-submit {
-  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+  background: linear-gradient(135deg, #01847F 0%, #016c68 100%);
   color: white;
   border: none;
   padding: 14px 24px;
@@ -161,7 +161,7 @@
 
   &:hover:not(:disabled) {
     transform: translateY(-2px);
-    box-shadow: 0 10px 25px -5px rgba(16, 185, 129, 0.5);
+    box-shadow: 0 10px 25px -5px rgba(1, 132, 127, 0.5);
   }
 
   &:disabled {
@@ -184,8 +184,8 @@
   transition: all 0.3s ease;
 
   &:hover:not(:disabled) {
-    border-color: #34d399;
-    color: #059669;
+    border-color: #01847F;
+    color: #01847F;
   }
 
   &:disabled {
@@ -202,7 +202,7 @@
 .link-btn {
   background: none;
   border: none;
-  color: #059669;
+  color: #01847F;
   cursor: pointer;
   font-size: 14px;
   font-weight: 500;
@@ -210,7 +210,7 @@
   padding: 8px 16px;
 
   &:hover {
-    color: #047857;
+    color: #016c68;
   }
 }
 

+ 27 - 45
campus_health_app/frontend/campus-health-app/src/app/modules/auth/forgot-password/forgot-password.component.ts

@@ -1,8 +1,7 @@
-import { Component } from '@angular/core';
+import { Component, OnDestroy } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { Router } from '@angular/router';
-import { AuthService } from '../../../services/auth.service';
 
 @Component({
   selector: 'app-forgot-password',
@@ -11,7 +10,7 @@ import { AuthService } from '../../../services/auth.service';
   templateUrl: './forgot-password.component.html',
   styleUrl: './forgot-password.component.scss'
 })
-export class ForgotPasswordComponent {
+export class ForgotPasswordComponent implements OnDestroy {
   phone = '';
   verificationCode = '';
   newPassword = '';
@@ -24,11 +23,10 @@ export class ForgotPasswordComponent {
   step = 1; // 1: 输入手机号,2: 输入验证码和新密码
 
   constructor(
-    private authService: AuthService,
     private router: Router
   ) {}
 
-  // 发送验证码
+  // 发送验证码(前端模拟)
   sendVerificationCode(): void {
     // 手机号验证
     if (!this.phone) {
@@ -45,27 +43,16 @@ export class ForgotPasswordComponent {
     this.isLoading = true;
     this.errorMessage = '';
 
-    this.authService.verifyPhone(this.phone).subscribe({
-      next: (response) => {
-        if (response.success) {
-          this.startCountdown();
-          this.successMessage = '验证码已发送,请查收短信';
-          this.step = 2;
-        } else {
-          this.errorMessage = response.message || '发送验证码失败,请稍后重试';
-        }
-      },
-      error: (error) => {
-        console.error('Send verification code error:', error);
-        this.errorMessage = '网络错误,请稍后重试';
-      },
-      complete: () => {
-        this.isLoading = false;
-      }
-    });
+    // 模拟网络请求延迟
+    setTimeout(() => {
+      this.isLoading = false;
+      this.startCountdown();
+      this.successMessage = '验证码已发送,请查收短信';
+      this.step = 2;
+    }, 1000);
   }
 
-  // 重置密码
+  // 重置密码(前端模拟)
   resetPassword(): void {
     // 验证码验证
     if (!this.verificationCode) {
@@ -78,6 +65,12 @@ export class ForgotPasswordComponent {
       return;
     }
 
+    // 模拟验证码检查
+    if (this.verificationCode !== '123456') {
+      this.errorMessage = '验证码错误,请输入123456';
+      return;
+    }
+
     // 新密码验证
     if (!this.newPassword) {
       this.errorMessage = '请设置新密码';
@@ -99,27 +92,16 @@ export class ForgotPasswordComponent {
     this.errorMessage = '';
     this.successMessage = '';
 
-    this.authService.resetPassword(this.phone, this.newPassword, this.verificationCode).subscribe({
-      next: (response) => {
-        if (response.success) {
-          this.successMessage = '密码重置成功!正在跳转到登录页面...';
-          
-          // 3秒后跳转到登录页面
-          setTimeout(() => {
-            this.router.navigate(['/auth/login']);
-          }, 3000);
-        } else {
-          this.errorMessage = response.message || '密码重置失败,请稍后重试';
-        }
-      },
-      error: (error) => {
-        console.error('Reset password error:', error);
-        this.errorMessage = '网络错误,请稍后重试';
-      },
-      complete: () => {
-        this.isLoading = false;
-      }
-    });
+    // 模拟网络请求延迟
+    setTimeout(() => {
+      this.isLoading = false;
+      this.successMessage = '密码重置成功!正在跳转到登录页面...';
+      
+      // 3秒后跳转到登录页面
+      setTimeout(() => {
+        this.router.navigate(['/auth/login']);
+      }, 3000);
+    }, 1500);
   }
 
   // 开始倒计时

+ 11 - 11
campus_health_app/frontend/campus-health-app/src/app/modules/auth/login/login.component.scss

@@ -4,7 +4,7 @@
   justify-content: center;
   align-items: center;
   min-height: 100vh;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #01847F 0%, #016c68 100%);
   padding: 20px;
   position: relative;
   overflow: hidden;
@@ -71,7 +71,7 @@
   font-size: 28px;
   font-weight: 700;
   margin: 0 0 8px 0;
-  background: linear-gradient(135deg, #1f2937 0%, #4f46e5 100%);
+  background: linear-gradient(135deg, #1f2937 0%, #01847F 100%);
   -webkit-background-clip: text;
   -webkit-text-fill-color: transparent;
   background-clip: text;
@@ -130,9 +130,9 @@
 
   &:focus {
     outline: none;
-    border-color: #4f46e5;
+    border-color: #01847F;
     background-color: white;
-    box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1);
+    box-shadow: 0 0 0 4px rgba(1, 132, 127, 0.1);
     transform: translateY(-1px);
   }
 
@@ -161,7 +161,7 @@
 }
 
 .btn-login {
-  background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
+  background: linear-gradient(135deg, #01847F 0%, #016c68 100%);
   color: white;
   border: none;
   padding: 18px 32px;
@@ -187,7 +187,7 @@
 
   &:hover:not(:disabled) {
     transform: translateY(-3px);
-    box-shadow: 0 15px 35px -5px rgba(79, 70, 229, 0.4);
+    box-shadow: 0 15px 35px -5px rgba(1, 132, 127, 0.4);
     
     &::before {
       left: 100%;
@@ -257,7 +257,7 @@
 .link-btn {
   background: none;
   border: none;
-  color: #4f46e5;
+  color: #01847F;
   cursor: pointer;
   font-size: 14px;
   font-weight: 500;
@@ -266,8 +266,8 @@
   border-radius: 8px;
 
   &:hover {
-    color: #7c3aed;
-    background-color: rgba(79, 70, 229, 0.1);
+    color: #016c68;
+    background-color: rgba(1, 132, 127, 0.1);
     transform: translateY(-1px);
   }
 
@@ -290,12 +290,12 @@
 }
 
 .text-link {
-  color: #4f46e5;
+  color: #01847F;
   text-decoration: none;
   transition: color 0.3s ease;
 
   &:hover {
-    color: #7c3aed;
+    color: #016c68;
     text-decoration: underline;
   }
 }

+ 10 - 2
campus_health_app/frontend/campus-health-app/src/app/modules/auth/login/login.component.ts

@@ -37,10 +37,18 @@ export class LoginComponent {
   }
 
   showForgotPassword(): void {
-    alert('忘记密码功能开发中...');
+    console.log('Navigating to forgot password...');
+    this.router.navigate(['/auth/forgot-password']).then(
+      success => console.log('Navigation success:', success),
+      error => console.error('Navigation error:', error)
+    );
   }
 
   showRegister(): void {
-    alert('注册功能开发中...');
+    console.log('Navigating to register...');
+    this.router.navigate(['/auth/register']).then(
+      success => console.log('Navigation success:', success),
+      error => console.error('Navigation error:', error)
+    );
   }
 }

+ 8 - 8
campus_health_app/frontend/campus-health-app/src/app/modules/auth/register/register.component.scss

@@ -4,7 +4,7 @@
   justify-content: center;
   align-items: center;
   min-height: 100vh;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #01847F 0%, #016c68 100%);
   padding: 20px;
 }
 
@@ -70,9 +70,9 @@
 
   &:focus {
     outline: none;
-    border-color: #34d399;
+    border-color: #01847F;
     background-color: white;
-    box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
+    box-shadow: 0 0 0 3px rgba(1, 132, 127, 0.1);
   }
 
   &:disabled {
@@ -91,7 +91,7 @@
 }
 
 .btn-register {
-  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+  background: linear-gradient(135deg, #01847F 0%, #016c68 100%);
   color: white;
   border: none;
   padding: 14px 24px;
@@ -103,7 +103,7 @@
 
   &:hover:not(:disabled) {
     transform: translateY(-2px);
-    box-shadow: 0 10px 25px -5px rgba(16, 185, 129, 0.5);
+    box-shadow: 0 10px 25px -5px rgba(1, 132, 127, 0.5);
   }
 
   &:disabled {
@@ -127,14 +127,14 @@
 .link-btn {
   background: none;
   border: none;
-  color: #059669;
+  color: #01847F;
   cursor: pointer;
   font-size: 14px;
   font-weight: 500;
   transition: color 0.3s ease;
 
   &:hover {
-    color: #047857;
+    color: #016c68;
   }
 }
 
@@ -146,7 +146,7 @@
 }
 
 .text-link {
-  color: #059669;
+  color: #01847F;
   text-decoration: none;
 
   &:hover {

+ 34 - 20
campus_health_app/frontend/campus-health-app/src/app/modules/auth/register/register.component.ts

@@ -2,7 +2,17 @@ import { Component } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { Router } from '@angular/router';
-import { AuthService, RegisterRequest } from '../../../services/auth.service';
+
+// 注册表单接口
+interface RegisterForm {
+  phone: string;
+  password: string;
+  nickname: string;
+  schoolId: string;
+  grade: string;
+  isUnder14: boolean;
+  parentPhone: string;
+}
 
 @Component({
   selector: 'app-register',
@@ -12,7 +22,7 @@ import { AuthService, RegisterRequest } from '../../../services/auth.service';
   styleUrl: './register.component.scss'
 })
 export class RegisterComponent {
-  registerForm: RegisterRequest = {
+  registerForm: RegisterForm = {
     phone: '',
     password: '',
     nickname: '',
@@ -48,7 +58,6 @@ export class RegisterComponent {
   ];
 
   constructor(
-    private authService: AuthService,
     private router: Router
   ) {}
 
@@ -61,23 +70,28 @@ export class RegisterComponent {
     this.isLoading = true;
     this.errorMessage = '';
 
-    this.authService.register(this.registerForm).subscribe({
-      next: (response) => {
-        if (response.success) {
-          // 注册成功,跳转到手机验证页面
-          this.router.navigate(['/auth/verify-phone'], { queryParams: { phone: this.registerForm.phone } });
-        } else {
-          this.errorMessage = response.message || '注册失败,请稍后重试';
-        }
-      },
-      error: (error) => {
-        console.error('Register error:', error);
-        this.errorMessage = '网络错误,请稍后重试';
-      },
-      complete: () => {
-        this.isLoading = false;
-      }
-    });
+    // 模拟网络请求延迟
+    setTimeout(() => {
+      this.isLoading = false;
+      
+      // 模拟注册成功
+      alert(`注册成功!\n手机号:${this.registerForm.phone}\n昵称:${this.registerForm.nickname}\n学校:${this.getSchoolName()}\n年级:${this.getGradeName()}`);
+      
+      // 跳转到登录页面
+      this.router.navigate(['/auth/login']);
+    }, 1500);
+  }
+
+  // 获取学校名称
+  private getSchoolName(): string {
+    const school = this.schools.find(s => s.id === this.registerForm.schoolId);
+    return school ? school.name : '';
+  }
+
+  // 获取年级名称
+  private getGradeName(): string {
+    const grade = this.grades.find(g => g.id === this.registerForm.grade);
+    return grade ? grade.name : '';
   }
 
   private validateForm(): boolean {

+ 2 - 2
campus_health_app/frontend/campus-health-app/src/app/modules/auth/verify-phone/verify-phone.component.html

@@ -3,8 +3,8 @@
     <div class="logo-container">
       <div class="logo">
         <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
-          <circle cx="32" cy="32" r="30" stroke="#34D399" stroke-width="4"/>
-          <path d="M20 32L28 22L44 38" stroke="#10B981" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
+          <circle cx="32" cy="32" r="30" stroke="#01847F" stroke-width="4"/>
+          <path d="M20 32L28 22L44 38" stroke="#01847F" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
         </svg>
       </div>
       <h1>手机验证</h1>

+ 10 - 10
campus_health_app/frontend/campus-health-app/src/app/modules/auth/verify-phone/verify-phone.component.scss

@@ -4,7 +4,7 @@
   justify-content: center;
   align-items: center;
   min-height: 100vh;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #01847F 0%, #019B94 100%);
   padding: 20px;
 }
 
@@ -46,7 +46,7 @@
 
 .phone-number {
   font-weight: 600;
-  color: #059669;
+  color: #01847F;
 }
 
 .verify-form {
@@ -77,9 +77,9 @@
 
   &:focus {
     outline: none;
-    border-color: #34d399;
+    border-color: #01847F;
     background-color: white;
-    box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.1);
+    box-shadow: 0 0 0 3px rgba(1, 132, 127, 0.1);
   }
 
   &:disabled {
@@ -103,7 +103,7 @@
 .btn-resend {
   background: none;
   border: none;
-  color: #059669;
+  color: #01847F;
   cursor: pointer;
   font-size: 14px;
   font-weight: 500;
@@ -111,7 +111,7 @@
   padding: 8px 16px;
 
   &:hover:not(:disabled) {
-    color: #047857;
+    color: #016B66;
   }
 
   &:disabled {
@@ -132,8 +132,8 @@
 }
 
 .success-message {
-  background-color: #d1fae5;
-  color: #065f46;
+  background-color: #E6F7F6;
+  color: #016B66;
   padding: 12px 16px;
   border-radius: 8px;
   font-size: 14px;
@@ -142,7 +142,7 @@
 }
 
 .btn-verify {
-  background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+  background: linear-gradient(135deg, #01847F 0%, #016B66 100%);
   color: white;
   border: none;
   padding: 14px 24px;
@@ -154,7 +154,7 @@
 
   &:hover:not(:disabled) {
     transform: translateY(-2px);
-    box-shadow: 0 10px 25px -5px rgba(16, 185, 129, 0.5);
+    box-shadow: 0 10px 25px -5px rgba(1, 132, 127, 0.5);
   }
 
   &:disabled {

+ 17 - 25
campus_health_app/frontend/campus-health-app/src/app/modules/auth/verify-phone/verify-phone.component.ts

@@ -1,8 +1,7 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
-import { AuthService } from '../../../services/auth.service';
 
 @Component({
   selector: 'app-verify-phone',
@@ -11,7 +10,7 @@ import { AuthService } from '../../../services/auth.service';
   templateUrl: './verify-phone.component.html',
   styleUrl: './verify-phone.component.scss'
 })
-export class VerifyPhoneComponent implements OnInit {
+export class VerifyPhoneComponent implements OnInit, OnDestroy {
   phone = '';
   verificationCode = '';
   isLoading = false;
@@ -22,7 +21,6 @@ export class VerifyPhoneComponent implements OnInit {
 
   constructor(
     private route: ActivatedRoute,
-    private authService: AuthService,
     private router: Router
   ) {}
 
@@ -40,31 +38,20 @@ export class VerifyPhoneComponent implements OnInit {
     this.sendVerificationCode();
   }
 
-  // 发送验证码
+  // 发送验证码(前端模拟)
   sendVerificationCode(): void {
     this.isLoading = true;
     this.errorMessage = '';
 
-    this.authService.verifyPhone(this.phone).subscribe({
-      next: (response) => {
-        if (response.success) {
-          this.startCountdown();
-          this.successMessage = '验证码已发送,请查收短信';
-        } else {
-          this.errorMessage = response.message || '发送验证码失败,请稍后重试';
-        }
-      },
-      error: (error) => {
-        console.error('Send verification code error:', error);
-        this.errorMessage = '网络错误,请稍后重试';
-      },
-      complete: () => {
-        this.isLoading = false;
-      }
-    });
+    // 模拟网络请求延迟
+    setTimeout(() => {
+      this.isLoading = false;
+      this.startCountdown();
+      this.successMessage = '验证码已发送,请查收短信';
+    }, 1000);
   }
 
-  // 提交验证
+  // 提交验证(前端模拟)
   onSubmit(): void {
     // 验证码验证
     if (!this.verificationCode) {
@@ -77,13 +64,18 @@ export class VerifyPhoneComponent implements OnInit {
       return;
     }
 
+    // 模拟验证码检查
+    if (this.verificationCode !== '123456') {
+      this.errorMessage = '验证码错误,请输入123456';
+      return;
+    }
+
     this.isLoading = true;
     this.errorMessage = '';
     this.successMessage = '';
 
-    // 模拟验证过程(实际应调用后端API)
+    // 模拟验证过程
     setTimeout(() => {
-      // 假设验证成功
       this.isLoading = false;
       this.successMessage = '验证成功!正在跳转到登录页面...';
       

+ 35 - 0
campus_health_app/frontend/campus-health-app/src/app/shared/components/calendar-grid/calendar-grid.component.html

@@ -0,0 +1,35 @@
+<div class="calendar-wrapper" [attr.data-theme]="themeColor">
+  <!-- 月份导航 -->
+  <div class="month-nav">
+    <button class="nav-btn" (click)="onPreviousMonth()">‹</button>
+    <span class="month-title">{{ currentYearMonth }}</span>
+    <button class="nav-btn" (click)="onNextMonth()">›</button>
+    <button class="back-today-btn" (click)="onGoToCurrentMonth()">回到本月 →</button>
+  </div>
+
+  <!-- 日历网格 -->
+  <div class="calendar">
+    <div class="calendar-header">
+      <span class="weekday" *ngFor="let day of weekDays">{{ day }}</span>
+    </div>
+    <div class="calendar-grid">
+      <div *ngFor="let day of calendarDays" 
+           class="calendar-day"
+           [class.empty]="!day"
+           [class.has-record]="day && day.hasData"
+           [class.completed]="day?.completed"
+           [class.today]="day && isToday(day.date)"
+           [class.selected]="day && isSelected(day.date)"
+           (click)="onDateClick(day)">
+        <ng-container *ngIf="day">
+          <span class="day-number">{{ day.date.getDate() }}</span>
+          <div class="day-content" *ngIf="day.hasData">
+            <span class="completion-badge" *ngIf="day.badge">{{ day.badge }}</span>
+            <span class="primary-text" *ngIf="day.primaryText">{{ day.primaryText }}</span>
+            <span class="secondary-text" *ngIf="day.secondaryText">{{ day.secondaryText }}</span>
+          </div>
+        </ng-container>
+      </div>
+    </div>
+  </div>
+</div>

+ 267 - 0
campus_health_app/frontend/campus-health-app/src/app/shared/components/calendar-grid/calendar-grid.component.scss

@@ -0,0 +1,267 @@
+.calendar-wrapper {
+  --theme-primary: #10b981;
+  --theme-primary-light: #d1fae5;
+  --theme-primary-gradient: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
+  --theme-border: #a7f3d0;
+  --theme-completed-gradient: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
+  --theme-completed-border: #93c5fd;
+
+  &[data-theme="purple"] {
+    --theme-primary: #7c3aed;
+    --theme-primary-light: #ede9fe;
+    --theme-primary-gradient: linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%);
+    --theme-border: #c4b5fd;
+    --theme-completed-gradient: linear-gradient(135deg, #dbeafe 0%, #c7d2fe 100%);
+    --theme-completed-border: #a5b4fc;
+  }
+
+  &[data-theme="blue"] {
+    --theme-primary: #0284c7;
+    --theme-primary-light: #e0f2fe;
+    --theme-primary-gradient: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+    --theme-border: #7dd3fc;
+    --theme-completed-gradient: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
+    --theme-completed-border: #93c5fd;
+  }
+}
+
+.month-nav {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 1rem;
+  margin-bottom: 1rem;
+}
+
+.nav-btn {
+  width: 32px;
+  height: 32px;
+  border: none;
+  background: white;
+  border-radius: 50%;
+  font-size: 1.2rem;
+  color: #64748b;
+  cursor: pointer;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+  transition: all 0.2s;
+
+  &:hover {
+    background: #f1f5f9;
+  }
+}
+
+.month-title {
+  font-size: 1.1rem;
+  font-weight: 600;
+  color: #1e293b;
+}
+
+.back-today-btn {
+  padding: 0.35rem 0.75rem;
+  background: var(--theme-primary-light);
+  border: none;
+  border-radius: 16px;
+  font-size: 0.8rem;
+  color: var(--theme-primary);
+  cursor: pointer;
+  transition: all 0.2s;
+
+  &:hover {
+    filter: brightness(0.95);
+  }
+}
+
+.calendar {
+  background: white;
+  border-radius: 16px;
+  padding: 1rem;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
+  width: 100%;
+  max-width: 100%;
+  overflow: hidden;
+  box-sizing: border-box;
+}
+
+.calendar-header {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  margin-bottom: 0.5rem;
+}
+
+.weekday {
+  text-align: center;
+  font-size: 0.85rem;
+  font-weight: 500;
+  color: #64748b;
+  padding: 0.5rem 0;
+}
+
+.calendar-grid {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 3px;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.calendar-day {
+  aspect-ratio: 1;
+  position: relative;
+  border-radius: 8px;
+  background: #f8fafc;
+  cursor: pointer;
+  transition: all 0.2s ease;
+  overflow: hidden;
+  box-sizing: border-box;
+  min-height: 60px;
+  display: flex;
+  flex-direction: column;
+
+  &.empty {
+    background: transparent;
+    cursor: default;
+  }
+
+  &.has-record {
+    background: var(--theme-primary-gradient);
+    border: 1px solid var(--theme-border);
+  }
+
+  &.completed {
+    background: var(--theme-completed-gradient);
+    border: 1px solid var(--theme-completed-border);
+  }
+
+  &.today {
+    box-shadow: 0 0 0 2px var(--theme-primary);
+  }
+
+  &.selected {
+    box-shadow: 0 0 0 2px #3b82f6;
+    background: #eff6ff !important;
+  }
+
+  &:hover:not(.empty) {
+    transform: scale(1.02);
+  }
+}
+
+.day-number {
+  font-weight: 600;
+  color: #1e293b;
+  font-size: 0.75rem;
+  padding: 0.25rem 0.35rem 0.1rem;
+  line-height: 1;
+  flex-shrink: 0;
+}
+
+.day-content {
+  padding: 0 0.35rem 0.25rem;
+  display: flex;
+  flex-direction: column;
+  gap: 1px;
+  overflow: hidden;
+  flex: 1;
+  justify-content: flex-start;
+}
+
+.completion-badge {
+  color: var(--theme-primary);
+  font-size: 0.65rem;
+  font-weight: bold;
+  line-height: 1.2;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.primary-text {
+  color: #475569;
+  font-size: 0.65rem;
+  line-height: 1.2;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.secondary-text {
+  color: #9ca3af;
+  font-size: 0.6rem;
+  line-height: 1.1;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+/* 响应式调整 */
+@media (max-width: 480px) {
+  .calendar-day {
+    min-height: 55px;
+  }
+
+  .day-number {
+    font-size: 0.7rem;
+    padding: 0.2rem 0.25rem 0.05rem;
+  }
+
+  .day-content {
+    padding: 0 0.25rem 0.2rem;
+  }
+
+  .primary-text,
+  .completion-badge {
+    font-size: 0.6rem;
+  }
+
+  .secondary-text {
+    font-size: 0.55rem;
+  }
+}
+
+@media (max-width: 360px) {
+  .calendar-grid {
+    gap: 2px;
+  }
+
+  .calendar-day {
+    min-height: 50px;
+  }
+
+  .day-number {
+    font-size: 0.65rem;
+    padding: 0.15rem 0.2rem 0.05rem;
+  }
+
+  .day-content {
+    padding: 0 0.2rem 0.15rem;
+  }
+
+  .primary-text,
+  .completion-badge {
+    font-size: 0.55rem;
+  }
+
+  .secondary-text {
+    font-size: 0.5rem;
+  }
+}
+
+/* 超小屏幕优化 */
+@media (max-width: 320px) {
+  .calendar-day {
+    min-height: 45px;
+  }
+
+  .day-number {
+    font-size: 0.6rem;
+  }
+
+  .primary-text,
+  .completion-badge {
+    font-size: 0.5rem;
+  }
+
+  .secondary-text {
+    font-size: 0.45rem;
+  }
+}

+ 117 - 0
campus_health_app/frontend/campus-health-app/src/app/shared/components/calendar-grid/calendar-grid.component.ts

@@ -0,0 +1,117 @@
+import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+export interface CalendarDayData {
+  date: Date;
+  hasData: boolean;
+  completed?: boolean;
+  primaryText?: string;    // 主要显示文本(如运动类型、体重值、餐食类型)
+  secondaryText?: string;  // 次要显示文本(如时长、体脂率、热量)
+  badge?: string;          // 徽章(如 ✓)
+}
+
+@Component({
+  selector: 'app-calendar-grid',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './calendar-grid.component.html',
+  styleUrl: './calendar-grid.component.scss'
+})
+export class CalendarGridComponent implements OnChanges {
+  @Input() currentDate: Date = new Date();
+  @Input() dayDataMap: Map<string, CalendarDayData> = new Map();
+  @Input() selectedDate: Date | null = null;
+  @Input() isManageMode: boolean = false;
+  @Input() selectedRecords: Set<string> = new Set();
+  @Input() themeColor: 'green' | 'purple' | 'blue' = 'green';
+
+  @Output() dateSelected = new EventEmitter<CalendarDayData | null>();
+  @Output() previousMonth = new EventEmitter<void>();
+  @Output() nextMonth = new EventEmitter<void>();
+  @Output() goToCurrentMonth = new EventEmitter<void>();
+
+  weekDays = ['一', '二', '三', '四', '五', '六', '日'];
+  calendarDays: (CalendarDayData | null)[] = [];
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['currentDate'] || changes['dayDataMap']) {
+      this.buildCalendar();
+    }
+  }
+
+  get currentYearMonth(): string {
+    return `${this.currentDate.getFullYear()}年${this.currentDate.getMonth() + 1}月`;
+  }
+
+  buildCalendar(): void {
+    const year = this.currentDate.getFullYear();
+    const month = this.currentDate.getMonth();
+    const firstDay = new Date(year, month, 1);
+    const lastDay = new Date(year, month + 1, 0);
+    let startWeekDay = firstDay.getDay();
+    startWeekDay = startWeekDay === 0 ? 6 : startWeekDay - 1;
+
+    this.calendarDays = [];
+    
+    // 填充月初空白
+    for (let i = 0; i < startWeekDay; i++) {
+      this.calendarDays.push(null);
+    }
+
+    // 填充日期
+    for (let day = 1; day <= lastDay.getDate(); day++) {
+      const date = new Date(year, month, day);
+      const dateKey = this.formatDateKey(date);
+      const dayData = this.dayDataMap.get(dateKey);
+      
+      this.calendarDays.push(dayData || {
+        date,
+        hasData: false,
+        primaryText: '',
+        secondaryText: '',
+        badge: ''
+      });
+    }
+  }
+
+  onDateClick(day: CalendarDayData | null): void {
+    if (!day) return;
+    this.dateSelected.emit(day);
+  }
+
+  isToday(date: Date): boolean {
+    const today = new Date();
+    return date.getDate() === today.getDate() && 
+           date.getMonth() === today.getMonth() && 
+           date.getFullYear() === today.getFullYear();
+  }
+
+  isSelected(date: Date): boolean {
+    if (this.isManageMode) {
+      return this.selectedRecords.has(this.formatDateKey(date));
+    }
+    if (!this.selectedDate) return false;
+    return date.getDate() === this.selectedDate.getDate() && 
+           date.getMonth() === this.selectedDate.getMonth() && 
+           date.getFullYear() === this.selectedDate.getFullYear();
+  }
+
+  private formatDateKey(date: Date): string {
+    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}`;
+  }
+
+  onPreviousMonth(): void {
+    this.previousMonth.emit();
+  }
+
+  onNextMonth(): void {
+    this.nextMonth.emit();
+  }
+
+  onGoToCurrentMonth(): void {
+    this.goToCurrentMonth.emit();
+  }
+}

+ 15 - 0
campus_health_app/frontend/campus-health-app/src/app/shared/components/modal/modal.component.scss

@@ -253,6 +253,21 @@
   }
 }
 
+.theme-system {
+  .modal-header.has-gradient {
+    background: linear-gradient(135deg, #01847F 0%, #016c68 100%);
+  }
+  
+  .modal-btn-confirm {
+    background: linear-gradient(135deg, #01847F 0%, #016c68 100%);
+    box-shadow: 0 2px 8px rgba(1, 132, 127, 0.3);
+    
+    &:hover:not(:disabled) {
+      box-shadow: 0 4px 12px rgba(1, 132, 127, 0.4);
+    }
+  }
+}
+
 // 弹窗内容样式辅助类
 .detail-modal-content,
 .record-modal-content,

+ 1 - 1
campus_health_app/frontend/campus-health-app/src/app/shared/components/modal/modal.config.ts

@@ -3,7 +3,7 @@
  */
 
 export type ModalSize = 'small' | 'medium' | 'large' | 'xlarge';
-export type ModalTheme = 'default' | 'gradient' | 'success' | 'warning' | 'danger';
+export type ModalTheme = 'default' | 'gradient' | 'success' | 'warning' | 'danger' | 'system';
 
 export interface ModalConfig {
   // 基础配置

+ 3 - 2
campus_health_app/frontend/campus-health-app/src/index.html

@@ -6,8 +6,9 @@
   <base href="/">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
-  <!-- ECharts 国内CDN -->
-  <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
+  <!-- ECharts 国内CDN (BootCDN) -->
+  <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.4.3/echarts.min.js" 
+          onerror="this.onerror=null;this.src='https://cdn.staticfile.net/echarts/5.4.3/echarts.min.js'"></script>
 </head>
 <body>
   <app-root></app-root>

+ 64 - 2
campus_health_app/frontend/campus-health-app/src/styles.scss

@@ -3,8 +3,8 @@
 /* 全局变量定义 */
 :root {
   // 主色调
-  --primary-color: #4f46e5;
-  --primary-light: #7c3aed;
+  --primary-color: #01847F;
+  --primary-light: #016b67;
   --secondary-color: #10b981;
   --accent-color: #f59e0b;
   --danger-color: #ef4444;
@@ -360,4 +360,66 @@ a:hover {
 .dining-card {
   border: 1px solid var(--border-color);
   transition: border-color 0.3s ease;
+}
+
+/* ============================================================
+   统一 App 页面尺寸为手机页面 & 消除双滚动条
+   ------------------------------------------------------------
+   问题来源:页面根容器使用 height/min-height: 100vh,而 .app-content
+   本身已经是「视口高度 - 底部导航」的滚动容器,导致页面既溢出外层、
+   又在内部生成滚动条,出现两条滑轨。
+   解决:锁定视口 → 路由页面宿主占满内容区 → 页面根容器自适应内容区
+   高度 → 内部主内容区不再单独滚动,滚动统一交给 .app-content。
+   ============================================================ */
+
+/* 1) 锁定视口,禁止页面级滚动 */
+html,
+body {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  overflow: hidden;
+}
+
+app-root {
+  display: block;
+  height: 100%;
+}
+
+/* 2) 每个路由页面宿主统一占满手机内容区(排除 router-outlet 占位元素) */
+.app-content > *:not(router-outlet) {
+  display: block !important;
+  width: 100%;
+  height: 100% !important;
+  min-height: 0 !important;
+}
+
+/* 3) 页面最外层容器统一按内容区高度自适应,不再使用 100vh */
+.app-content > *:not(router-outlet) > .dashboard-container,
+.app-content > *:not(router-outlet) > .diet-container,
+.app-content > *:not(router-outlet) > .add-meal-container,
+.app-content > *:not(router-outlet) > .diet-history-container,
+.app-content > *:not(router-outlet) > .exercise-container,
+.app-content > *:not(router-outlet) > .add-exercise-container,
+.app-content > *:not(router-outlet) > .actions-container,
+.app-content > *:not(router-outlet) > .history-container,
+.app-content > *:not(router-outlet) > .fitness-summary-container,
+.app-content > *:not(router-outlet) > .knowledge-container,
+.app-content > *:not(router-outlet) > .monitoring-container,
+.app-content > *:not(router-outlet) > .school-services-container,
+.app-content > *:not(router-outlet) > .user-center-container,
+.app-content > *:not(router-outlet) > .weight-container,
+.app-content > *:not(router-outlet) > .diet-mobile-container,
+.app-content > *:not(router-outlet) > .weight-mobile-container,
+.app-content > *:not(router-outlet) > .phone-inner {
+  height: auto !important;
+  min-height: 100% !important;
+  overflow: visible !important;
+}
+
+/* 4) 页面内部主内容区不再独立滚动,避免与 .app-content 形成两条滑轨 */
+.app-content > *:not(router-outlet) > [class$="-container"] > [class$="-main"],
+.app-content > *:not(router-outlet) > [class$="-container"] > .main-content,
+.app-content > *:not(router-outlet) > .phone-inner > * {
+  overflow: visible !important;
 }