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

feat(frontend): add campus service modals (faq/feedback/team/venue) and polish user center

17846405080 1 месяц назад
Родитель
Сommit
db51d6ebd6
19 измененных файлов с 4042 добавлено и 185 удалено
  1. 24 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/faq-modal/faq-modal.component.html
  2. 152 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/faq-modal/faq-modal.component.scss
  3. 59 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/faq-modal/faq-modal.component.ts
  4. 34 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/feedback-modal/feedback-modal.component.html
  5. 166 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/feedback-modal/feedback-modal.component.scss
  6. 60 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/feedback-modal/feedback-modal.component.ts
  7. 57 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/team-detail-modal/team-detail-modal.component.html
  8. 211 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/team-detail-modal/team-detail-modal.component.scss
  9. 31 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/team-detail-modal/team-detail-modal.component.ts
  10. 57 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/venue-detail-modal/venue-detail-modal.component.html
  11. 297 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/venue-detail-modal/venue-detail-modal.component.scss
  12. 82 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/venue-detail-modal/venue-detail-modal.component.ts
  13. 523 23
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/school-services.component.html
  14. 1322 103
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/school-services.component.scss
  15. 351 44
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/school-services.component.ts
  16. 71 0
      campus_health_app/frontend/campus-health-app/src/app/modules/school-services/school-services.types.ts
  17. 152 5
      campus_health_app/frontend/campus-health-app/src/app/modules/user-center/user-center.component.html
  18. 250 5
      campus_health_app/frontend/campus-health-app/src/app/modules/user-center/user-center.component.scss
  19. 143 5
      campus_health_app/frontend/campus-health-app/src/app/modules/user-center/user-center.component.ts

+ 24 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/faq-modal/faq-modal.component.html

@@ -0,0 +1,24 @@
+<div class="modal-overlay" [class.active]="isVisible" (click)="onClose()">
+  <div class="modal-container" [class.active]="isVisible" (click)="stopPropagation($event)">
+    <div class="modal-header">
+      <h3>常见问题</h3>
+      <button class="close-btn" (click)="onClose()">×</button>
+    </div>
+    <div class="modal-body">
+      <div class="faq-list">
+        <div class="faq-item" *ngFor="let faq of faqs; let i = index" [class.expanded]="faq.expanded" (click)="toggleFaq(i)">
+          <div class="faq-question">
+            <span>{{ faq.question }}</span>
+            <span class="toggle-icon">{{ faq.expanded ? '−' : '+' }}</span>
+          </div>
+          <div class="faq-answer" [style.max-height]="faq.expanded ? '200px' : '0'">
+            <p>{{ faq.answer }}</p>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <button class="btn-primary" (click)="onClose()">我知道了</button>
+    </div>
+  </div>
+</div>

+ 152 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/faq-modal/faq-modal.component.scss

@@ -0,0 +1,152 @@
+@use "sass:color";
+
+$brand-teal: #01847F;
+$text-main: #333;
+$text-muted: #666;
+
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+  display: none;
+  align-items: center;
+  justify-content: center;
+  z-index: 10000;
+
+  &.active {
+    display: flex;
+  }
+}
+
+.modal-container {
+  background: white;
+  width: 90%;
+  max-width: 400px;
+  border-radius: 16px;
+  overflow: hidden;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  display: none;
+  flex-direction: column;
+  max-height: 80vh;
+  transform: translateY(20px);
+
+  &.active {
+    display: flex;
+    transform: translateY(0);
+  }
+}
+
+// Ensure the container also gets the active class from the parent if needed, 
+// or simpler: rely on the overlay's active state to trigger children if using strict CSS selectors.
+// However, in VenueModal, .modal-container ALSO has [class.active]="isVisible".
+// Let's verify the HTML first. The current HTML for FAQ Modal uses [class.active] on BOTH overlay and container?
+// Let's check HTML in a separate step or assume we should match Venue Modal logic.
+// Venue Modal SCSS uses: .modal-container { ... &.active { transform: translateY(0); } }
+// So we should assume container gets active class too.
+
+.modal-header {
+  padding: 16px 20px;
+  border-bottom: 1px solid #eee;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  h3 {
+    margin: 0;
+    font-size: 1.1rem;
+    color: $text-main;
+  }
+
+  .close-btn {
+    background: none;
+    border: none;
+    font-size: 1.5rem;
+    color: #999;
+    cursor: pointer;
+    padding: 0;
+    line-height: 1;
+    
+    &:hover {
+      color: $text-main;
+    }
+  }
+}
+
+.modal-body {
+  padding: 20px;
+  overflow-y: auto;
+}
+
+.faq-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.faq-item {
+  border: 1px solid #eee;
+  border-radius: 8px;
+  overflow: hidden;
+  transition: all 0.3s ease;
+
+  &.expanded {
+    border-color: $brand-teal;
+    background: rgba(1, 132, 127, 0.02);
+  }
+}
+
+.faq-question {
+  padding: 12px 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  cursor: pointer;
+  font-weight: 500;
+  color: $text-main;
+  font-size: 0.95rem;
+
+  .toggle-icon {
+    font-size: 1.2rem;
+    color: $brand-teal;
+    font-weight: 300;
+  }
+}
+
+.faq-answer {
+  padding: 0 16px;
+  overflow: hidden;
+  transition: max-height 0.3s ease;
+  
+  p {
+    margin: 0 0 12px 0;
+    font-size: 0.9rem;
+    color: $text-muted;
+    line-height: 1.5;
+  }
+}
+
+.modal-footer {
+  padding: 16px 20px;
+  border-top: 1px solid #eee;
+  display: flex;
+  justify-content: center;
+
+  .btn-primary {
+    width: 100%;
+    padding: 10px;
+    background: $brand-teal;
+    color: white;
+    border: none;
+    border-radius: 8px;
+    font-size: 1rem;
+    cursor: pointer;
+    transition: background 0.3s;
+
+    &:hover {
+      background: color.adjust($brand-teal, $lightness: 5%);
+    }
+  }
+}

+ 59 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/faq-modal/faq-modal.component.ts

@@ -0,0 +1,59 @@
+import { Component, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-faq-modal',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './faq-modal.component.html',
+  styleUrls: ['./faq-modal.component.scss']
+})
+export class FaqModalComponent implements OnChanges {
+  @Input() isVisible: boolean = false;
+  @Output() close = new EventEmitter<void>();
+
+  constructor() {
+    console.log('FaqModalComponent initialized');
+  }
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['isVisible']) {
+      console.log('FaqModal isVisible changed:', this.isVisible);
+    }
+  }
+
+  faqs = [
+    {
+      question: '如何预约体育场馆?',
+      answer: '点击首页或服务页面的“场馆预约”,选择您需要的场馆(如羽毛球馆),选择日期和空闲时段,确认后即可完成预约。',
+      expanded: false
+    },
+    {
+      question: '食堂拥挤指数是如何计算的?',
+      answer: '系统通过食堂入口的红外计数器和支付数据实时分析,每分钟更新一次拥挤状态,帮助您错峰就餐。',
+      expanded: false
+    },
+    {
+      question: '如何加入运动队伍?',
+      answer: '在“运动组队”板块浏览感兴趣的队伍,点击卡片查看详情。如果队伍未满员,点击“加入”按钮即可成为队员。',
+      expanded: false
+    },
+    {
+      question: '饮食记录的数据准确吗?',
+      answer: '我们采用标准食物营养库数据。建议您在记录时尽量准确估算分量,以获得更精准的营养分析。',
+      expanded: false
+    }
+  ];
+
+  toggleFaq(index: number): void {
+    this.faqs[index].expanded = !this.faqs[index].expanded;
+  }
+
+  onClose(): void {
+    this.close.emit();
+  }
+
+  stopPropagation(event: Event): void {
+    event.stopPropagation();
+  }
+}

+ 34 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/feedback-modal/feedback-modal.component.html

@@ -0,0 +1,34 @@
+<div class="modal-overlay" [class.active]="isVisible" (click)="onClose()">
+  <div class="modal-container" [class.active]="isVisible" (click)="stopPropagation($event)">
+    <div class="modal-header">
+      <h3>意见反馈</h3>
+      <button class="close-btn" (click)="onClose()">×</button>
+    </div>
+    <div class="modal-body">
+      <div class="form-group">
+        <label>反馈类型</label>
+        <div class="type-selector">
+          <div 
+            class="type-chip" 
+            *ngFor="let type of types" 
+            [class.active]="feedbackType === type.value"
+            (click)="feedbackType = type.value">
+            {{ type.label }}
+          </div>
+        </div>
+      </div>
+      
+      <div class="form-group">
+        <label>详细描述</label>
+        <textarea 
+          [(ngModel)]="content" 
+          placeholder="请详细描述您遇到的问题或建议..."
+          rows="5"></textarea>
+      </div>
+    </div>
+    <div class="modal-footer">
+      <button class="btn-cancel" (click)="onClose()">取消</button>
+      <button class="btn-submit" (click)="onSubmit()" [disabled]="!content.trim()">提交反馈</button>
+    </div>
+  </div>
+</div>

+ 166 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/feedback-modal/feedback-modal.component.scss

@@ -0,0 +1,166 @@
+@use "sass:color";
+
+$brand-teal: #01847F;
+$brand-pink: #F9D2E4;
+$brand-pink-deep: #8b4c63;
+$text-main: #333;
+$text-muted: #666;
+
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+  display: none;
+  align-items: center;
+  justify-content: center;
+  z-index: 10000;
+
+  &.active {
+    display: flex;
+  }
+}
+
+.modal-container {
+  background: white;
+  width: 90%;
+  max-width: 400px;
+  border-radius: 16px;
+  overflow: hidden;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  display: flex;
+  flex-direction: column;
+  transform: translateY(20px);
+  transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+
+  &.active {
+    transform: translateY(0);
+  }
+}
+
+.modal-header {
+  padding: 16px 20px;
+  border-bottom: 1px solid #eee;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+
+  h3 {
+    margin: 0;
+    font-size: 1.1rem;
+    color: $text-main;
+  }
+
+  .close-btn {
+    background: none;
+    border: none;
+    font-size: 1.5rem;
+    color: #999;
+    cursor: pointer;
+    padding: 0;
+    line-height: 1;
+    
+    &:hover {
+      color: $text-main;
+    }
+  }
+}
+
+.modal-body {
+  padding: 20px;
+}
+
+.form-group {
+  margin-bottom: 20px;
+
+  label {
+    display: block;
+    font-size: 0.9rem;
+    font-weight: 500;
+    color: $text-main;
+    margin-bottom: 8px;
+  }
+  
+  textarea {
+    width: 100%;
+    padding: 12px;
+    border: 1px solid #ddd;
+    border-radius: 8px;
+    font-family: inherit;
+    font-size: 0.95rem;
+    resize: none;
+    outline: none;
+    transition: border-color 0.3s;
+    box-sizing: border-box; /* Fix for padding issue */
+
+    &:focus {
+      border-color: $brand-teal;
+    }
+  }
+}
+
+.type-selector {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+
+.type-chip {
+  padding: 6px 14px;
+  background: #f5f5f5;
+  border-radius: 20px;
+  font-size: 0.85rem;
+  color: $text-muted;
+  cursor: pointer;
+  transition: all 0.2s;
+  border: 1px solid transparent;
+
+  &.active {
+    background: $brand-pink;
+    color: $brand-pink-deep;
+    border-color: color.adjust($brand-pink, $lightness: -10%);
+    font-weight: 500;
+  }
+}
+
+.modal-footer {
+  padding: 16px 20px;
+  border-top: 1px solid #eee;
+  display: flex;
+  gap: 12px;
+
+  button {
+    flex: 1;
+    padding: 10px;
+    border: none;
+    border-radius: 8px;
+    font-size: 0.95rem;
+    cursor: pointer;
+    transition: background 0.3s;
+  }
+
+  .btn-cancel {
+    background: #f5f5f5;
+    color: $text-muted;
+
+    &:hover {
+      background: #e5e5e5;
+    }
+  }
+
+  .btn-submit {
+    background: $brand-teal;
+    color: white;
+
+    &:hover {
+      background: color.adjust($brand-teal, $lightness: 5%);
+    }
+
+    &:disabled {
+      background: #ccc;
+      cursor: not-allowed;
+    }
+  }
+}

+ 60 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/feedback-modal/feedback-modal.component.ts

@@ -0,0 +1,60 @@
+import { Component, EventEmitter, Input, Output, OnChanges, SimpleChanges } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+@Component({
+  selector: 'app-feedback-modal',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './feedback-modal.component.html',
+  styleUrls: ['./feedback-modal.component.scss']
+})
+export class FeedbackModalComponent implements OnChanges {
+  @Input() isVisible: boolean = false;
+  @Output() close = new EventEmitter<void>();
+
+  constructor() {
+    console.log('FeedbackModalComponent initialized');
+  }
+
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes['isVisible']) {
+      console.log('FeedbackModal isVisible changed:', this.isVisible);
+    }
+  }
+
+  feedbackType: string = 'suggestion';
+  content: string = '';
+  
+  types = [
+    { value: 'suggestion', label: '功能建议' },
+    { value: 'bug', label: '问题反馈' },
+    { value: 'complaint', label: '投诉' },
+    { value: 'other', label: '其他' }
+  ];
+
+  onClose(): void {
+    this.close.emit();
+    this.resetForm();
+  }
+
+  onSubmit(): void {
+    if (!this.content.trim()) {
+      alert('请输入反馈内容');
+      return;
+    }
+    
+    // 模拟提交
+    alert('感谢您的反馈!我们会尽快处理。');
+    this.onClose();
+  }
+
+  resetForm(): void {
+    this.feedbackType = 'suggestion';
+    this.content = '';
+  }
+
+  stopPropagation(event: Event): void {
+    event.stopPropagation();
+  }
+}

+ 57 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/team-detail-modal/team-detail-modal.component.html

@@ -0,0 +1,57 @@
+<div class="modal-overlay" [class.active]="isVisible" (click)="onClose()">
+  <div class="modal-container" [class.active]="isVisible" (click)="stopPropagation($event)">
+    <div class="modal-header">
+      <h3>{{ team?.name }}</h3>
+      <span class="activity-badge">{{ team?.activity }}</span>
+      <button class="close-btn" (click)="onClose()">×</button>
+    </div>
+    
+    <div class="modal-body">
+      <div class="info-row">
+        <span class="icon">📝</span>
+        <p class="description">{{ team?.description }}</p>
+      </div>
+
+      <div class="info-section">
+        <h4>训练计划概览</h4>
+        <div class="plan-card">
+          <div class="plan-item">
+            <span class="dot"></span>
+            <div class="plan-content">
+              <span class="plan-time">{{ team?.time }}</span>
+              <span class="plan-detail">常规训练 - {{ team?.location }}</span>
+            </div>
+          </div>
+          <!-- 模拟更多计划 -->
+          <div class="plan-item">
+            <span class="dot"></span>
+            <div class="plan-content">
+              <span class="plan-time">周末加练</span>
+              <span class="plan-detail">体能强化与战术配合</span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <div class="team-stats">
+        <div class="stat-item">
+          <span class="label">队长</span>
+          <span class="value">{{ team?.captain }}</span>
+        </div>
+        <div class="stat-item">
+          <span class="label">当前人数</span>
+          <span class="value">{{ team?.currentMembers }}/{{ team?.maxMembers }}</span>
+        </div>
+      </div>
+    </div>
+
+    <div class="modal-footer">
+      <button class="btn-cancel" (click)="onClose()">取消</button>
+      <button class="btn-join" 
+              [disabled]="team?.currentMembers >= team?.maxMembers"
+              (click)="onJoin()">
+        {{ team?.currentMembers >= team?.maxMembers ? '人数已满' : '申请加入' }}
+      </button>
+    </div>
+  </div>
+</div>

+ 211 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/team-detail-modal/team-detail-modal.component.scss

@@ -0,0 +1,211 @@
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 3000;
+  opacity: 0;
+  visibility: hidden;
+  transition: all 0.3s ease;
+
+  &.active {
+    opacity: 1;
+    visibility: visible;
+  }
+}
+
+.modal-container {
+  background: white;
+  width: 90%;
+  max-width: 500px;
+  border-radius: 12px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+  transform: translateY(20px);
+  transition: all 0.3s ease;
+  overflow: hidden;
+
+  &.active {
+    transform: translateY(0);
+  }
+}
+
+.modal-header {
+  padding: 20px;
+  border-bottom: 1px solid #f0f0f0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+
+  h3 {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #333;
+  }
+
+  .activity-badge {
+    background: #e6f7ff;
+    color: #1890ff;
+    padding: 2px 8px;
+    border-radius: 4px;
+    font-size: 12px;
+    margin-left: 10px;
+    margin-right: auto;
+  }
+
+  .close-btn {
+    background: none;
+    border: none;
+    font-size: 24px;
+    color: #999;
+    cursor: pointer;
+    line-height: 1;
+    padding: 0;
+
+    &:hover {
+      color: #666;
+    }
+  }
+}
+
+.modal-body {
+  padding: 20px;
+}
+
+.info-row {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 20px;
+
+  .icon {
+    font-size: 18px;
+  }
+
+  .description {
+    margin: 0;
+    color: #666;
+    font-size: 14px;
+    line-height: 1.5;
+  }
+}
+
+.info-section {
+  margin-bottom: 20px;
+
+  h4 {
+    margin: 0 0 12px 0;
+    font-size: 15px;
+    color: #333;
+  }
+}
+
+.plan-card {
+  background: #f9f9f9;
+  border-radius: 8px;
+  padding: 16px;
+}
+
+.plan-item {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 12px;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+
+  .dot {
+    width: 8px;
+    height: 8px;
+    background: #1890ff;
+    border-radius: 50%;
+    margin-top: 6px;
+  }
+
+  .plan-content {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .plan-time {
+    font-weight: 500;
+    font-size: 13px;
+    color: #333;
+  }
+
+  .plan-detail {
+    font-size: 12px;
+    color: #666;
+  }
+}
+
+.team-stats {
+  display: flex;
+  gap: 24px;
+  padding-top: 16px;
+  border-top: 1px solid #f0f0f0;
+
+  .stat-item {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+
+    .label {
+      font-size: 12px;
+      color: #999;
+    }
+
+    .value {
+      font-size: 14px;
+      font-weight: 500;
+      color: #333;
+    }
+  }
+}
+
+.modal-footer {
+  padding: 16px 20px;
+  background: #f9f9f9;
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+
+  button {
+    padding: 8px 20px;
+    border-radius: 6px;
+    border: none;
+    font-size: 14px;
+    cursor: pointer;
+    transition: all 0.2s;
+  }
+
+  .btn-cancel {
+    background: white;
+    border: 1px solid #d9d9d9;
+    color: #666;
+
+    &:hover {
+      border-color: #1890ff;
+      color: #1890ff;
+    }
+  }
+
+  .btn-join {
+    background: #1890ff;
+    color: white;
+
+    &:hover {
+      background: #40a9ff;
+    }
+
+    &:disabled {
+      background: #d9d9d9;
+      cursor: not-allowed;
+    }
+  }
+}

+ 31 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/team-detail-modal/team-detail-modal.component.ts

@@ -0,0 +1,31 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-team-detail-modal',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './team-detail-modal.component.html',
+  styleUrls: ['./team-detail-modal.component.scss']
+})
+export class TeamDetailModalComponent {
+  @Input() isVisible: boolean = false;
+  @Input() team: any = null;
+  @Output() close = new EventEmitter<void>();
+  @Output() join = new EventEmitter<number>();
+
+  onClose(): void {
+    this.close.emit();
+  }
+
+  onJoin(): void {
+    if (this.team) {
+      this.join.emit(this.team.id);
+    }
+  }
+
+  // Prevent clicks inside the modal content from closing the modal
+  stopPropagation(event: Event): void {
+    event.stopPropagation();
+  }
+}

+ 57 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/venue-detail-modal/venue-detail-modal.component.html

@@ -0,0 +1,57 @@
+<div class="modal-overlay" [class.active]="isVisible" (click)="onClose()">
+  <div class="modal-container" [class.active]="isVisible" (click)="stopPropagation($event)">
+    <div class="modal-header">
+      <h3>{{ venue?.name }}</h3>
+      <span class="price-badge">{{ venue?.price }}</span>
+      <button class="close-btn" (click)="onClose()">×</button>
+    </div>
+    
+    <div class="modal-body">
+      <div class="venue-info">
+        <div class="venue-image" *ngIf="venue?.image">
+          <img [src]="venue?.image" [alt]="venue?.name">
+        </div>
+        <div class="tags-row">
+          <span class="tag" *ngFor="let tag of venue?.tags">{{ tag }}</span>
+        </div>
+      </div>
+
+      <div class="booking-section">
+        <h4>选择时段 <span class="date-hint">今天 (12月06日)</span></h4>
+        <div class="slots-grid">
+          <div 
+            class="time-slot" 
+            *ngFor="let slot of extendedTimeSlots"
+            [class.full]="slot.status === 'full'"
+            [class.selected]="selectedSlot === slot.time"
+            (click)="selectSlot(slot)">
+            <span class="time">{{ slot.time }}</span>
+            <span class="status">{{ slot.status === 'full' ? '已满' : (selectedSlot === slot.time ? '已选' : '可约') }}</span>
+          </div>
+        </div>
+        <div class="legend">
+          <div class="legend-item"><span class="dot available"></span>可预约</div>
+          <div class="legend-item"><span class="dot selected"></span>已选择</div>
+          <div class="legend-item"><span class="dot full"></span>已约满</div>
+        </div>
+      </div>
+    </div>
+
+    <div class="modal-footer">
+      <div class="selected-info" *ngIf="selectedSlot">
+        已选: <span class="highlight">{{ selectedSlot }}</span>
+      </div>
+      <div class="selected-info placeholder" *ngIf="!selectedSlot">
+        请选择预约时段
+      </div>
+      <div class="actions">
+        <button class="btn-cancel" (click)="onClose()">取消</button>
+        <button class="btn-confirm" 
+                [disabled]="!selectedSlot"
+                (click)="onBook()">
+          立即预约
+        </button>
+      </div>
+    </div>
+  </div>
+</div>

+ 297 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/venue-detail-modal/venue-detail-modal.component.scss

@@ -0,0 +1,297 @@
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 3000;
+  opacity: 0;
+  visibility: hidden;
+  transition: all 0.3s ease;
+
+  &.active {
+    opacity: 1;
+    visibility: visible;
+  }
+}
+
+.modal-container {
+  background: white;
+  width: 90%;
+  max-width: 500px;
+  border-radius: 16px;
+  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
+  transform: translateY(20px);
+  transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+  max-height: 85vh;
+
+  &.active {
+    transform: translateY(0);
+  }
+}
+
+.modal-header {
+  padding: 16px 20px;
+  border-bottom: 1px solid #f0f0f0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background: #fff;
+
+  h3 {
+    margin: 0;
+    font-size: 1.1rem;
+    font-weight: 600;
+    color: #333;
+  }
+
+  .price-badge {
+    background: #fff0f6;
+    color: #c41d7f;
+    padding: 2px 8px;
+    border-radius: 4px;
+    font-size: 0.85rem;
+    font-weight: 500;
+    margin-left: 8px;
+    margin-right: auto;
+  }
+
+  .close-btn {
+    background: none;
+    border: none;
+    font-size: 1.5rem;
+    color: #999;
+    cursor: pointer;
+    line-height: 1;
+    padding: 4px;
+    
+    &:hover {
+      color: #666;
+    }
+  }
+}
+
+.modal-body {
+  padding: 20px;
+  overflow-y: auto;
+  flex: 1;
+}
+
+.venue-info {
+  margin-bottom: 24px;
+
+  .venue-image {
+    width: 100%;
+    height: 160px;
+    border-radius: 8px;
+    overflow: hidden;
+    margin-bottom: 12px;
+    background: #f5f5f5;
+
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+  }
+
+  .tags-row {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+
+    .tag {
+      font-size: 0.8rem;
+      color: #666;
+      background: #f5f5f5;
+      padding: 4px 10px;
+      border-radius: 4px;
+    }
+  }
+}
+
+.booking-section {
+  h4 {
+    margin: 0 0 16px 0;
+    font-size: 1rem;
+    color: #333;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+
+    .date-hint {
+      font-size: 0.85rem;
+      color: #999;
+      font-weight: normal;
+    }
+  }
+}
+
+.slots-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 10px;
+  margin-bottom: 16px;
+}
+
+.time-slot {
+  border: 1px solid #e8e8e8;
+  border-radius: 8px;
+  padding: 8px 4px;
+  text-align: center;
+  cursor: pointer;
+  transition: all 0.2s;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background: #fff;
+
+  .time {
+    font-size: 0.95rem;
+    font-weight: 500;
+    color: #333;
+    margin-bottom: 2px;
+  }
+
+  .status {
+    font-size: 0.7rem;
+    color: #01847F;
+  }
+
+  &:hover {
+    border-color: #01847F;
+    background: rgba(1, 132, 127, 0.02);
+  }
+
+  &.full {
+    background: #f5f5f5;
+    border-color: #f0f0f0;
+    cursor: not-allowed;
+
+    .time {
+      color: #ccc;
+    }
+
+    .status {
+      color: #ccc;
+    }
+  }
+
+  &.selected {
+    background: #01847F;
+    border-color: #01847F;
+
+    .time {
+      color: #fff;
+    }
+
+    .status {
+      color: rgba(255, 255, 255, 0.9);
+    }
+  }
+}
+
+.legend {
+  display: flex;
+  justify-content: center;
+  gap: 20px;
+  margin-top: 16px;
+  
+  .legend-item {
+    display: flex;
+    align-items: center;
+    font-size: 0.8rem;
+    color: #666;
+    
+    .dot {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      margin-right: 6px;
+      
+      &.available { background: #fff; border: 1px solid #01847F; }
+      &.selected { background: #01847F; }
+      &.full { background: #ccc; }
+    }
+  }
+}
+
+.modal-footer {
+  padding: 16px 20px;
+  border-top: 1px solid #f0f0f0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background: #fff;
+
+  .selected-info {
+    font-size: 0.9rem;
+    color: #666;
+
+    &.placeholder {
+      color: #999;
+    }
+
+    .highlight {
+      color: #01847F;
+      font-weight: 600;
+      font-size: 1.1rem;
+    }
+  }
+
+  .actions {
+    display: flex;
+    gap: 12px;
+
+    button {
+      padding: 8px 20px;
+      border-radius: 6px;
+      border: none;
+      font-size: 0.9rem;
+      cursor: pointer;
+      transition: all 0.2s;
+    }
+
+    .btn-cancel {
+      background: #f5f5f5;
+      color: #666;
+
+      &:hover {
+        background: #e8e8e8;
+      }
+    }
+
+    .btn-confirm {
+      background: #01847F;
+      color: white;
+
+      &:hover {
+        background: #016c68;
+      }
+
+      &:disabled {
+        background: #d9d9d9;
+        cursor: not-allowed;
+      }
+    }
+  }
+}
+
+// 响应式调整
+@media (max-width: 480px) {
+  .slots-grid {
+    grid-template-columns: repeat(3, 1fr);
+  }
+  
+  .modal-container {
+    width: 95%;
+  }
+}

+ 82 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/components/venue-detail-modal/venue-detail-modal.component.ts

@@ -0,0 +1,82 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Venue } from '../../school-services.types';
+
+@Component({
+  selector: 'app-venue-detail-modal',
+  standalone: true,
+  imports: [CommonModule],
+  templateUrl: './venue-detail-modal.component.html',
+  styleUrls: ['./venue-detail-modal.component.scss']
+})
+export class VenueDetailModalComponent {
+  @Input() isVisible: boolean = false;
+  @Input() venue: Venue | null = null;
+  @Output() close = new EventEmitter<void>();
+  @Output() book = new EventEmitter<{venueId: number, time: string}>();
+
+  selectedSlot: string | null = null;
+
+  // 模拟更多时间段数据
+  extendedTimeSlots: { time: string; status: 'full' | 'available' | 'selected' }[] = [];
+
+  ngOnChanges(): void {
+    if (this.venue) {
+      // 基于传入的现有时间段,扩展生成一整天的时间表
+      this.generateTimeSlots();
+      this.selectedSlot = null;
+    }
+  }
+
+  generateTimeSlots(): void {
+    if (!this.venue) return;
+
+    // 基础时间段:09:00 - 22:00
+    const allTimes = [
+      '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', 
+      '16:00', '17:00', '18:00', '19:00', '20:00', '21:00'
+    ];
+
+    this.extendedTimeSlots = allTimes.map(time => {
+      // 检查原数据中是否有状态
+      const existing = this.venue!.timeSlots.find(s => s.time === time);
+      if (existing) {
+        return { time, status: existing.status };
+      }
+      // 随机生成其他时间段状态
+      return { 
+        time, 
+        status: Math.random() > 0.7 ? 'full' : 'available' 
+      };
+    });
+  }
+
+  selectSlot(slot: { time: string; status: string }): void {
+    if (slot.status === 'full') return;
+    
+    // 取消之前的选择
+    if (this.selectedSlot === slot.time) {
+      this.selectedSlot = null;
+    } else {
+      this.selectedSlot = slot.time;
+    }
+  }
+
+  onClose(): void {
+    this.close.emit();
+    this.selectedSlot = null;
+  }
+
+  onBook(): void {
+    if (this.venue && this.selectedSlot) {
+      this.book.emit({
+        venueId: this.venue.id,
+        time: this.selectedSlot
+      });
+    }
+  }
+
+  stopPropagation(event: Event): void {
+    event.stopPropagation();
+  }
+}

+ 523 - 23
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/school-services.component.html

@@ -1,16 +1,141 @@
 <div class="school-services-container">
-  <app-page-header [title]="'校园服务'" (back)="backToDashboard()"></app-page-header>
+  <app-page-header [title]="'校园服务'" [showBack]="false"></app-page-header>
 
   <main class="school-services-main">
     <div class="school-services-content">
+
+      <!-- 顶部总览(方案 B 风格) -->
+      <section class="services-hero">
+        <div class="hero-header">
+          <div class="hero-text-group">
+            <div class="hero-title">校园服务</div>
+            <div class="hero-sub">常用服务都在这里,一屏查看「吃 · 动 · 场馆」</div>
+          </div>
+          <div class="hero-badge">🏫 东区 · 晴 26℃</div>
+        </div>
+
+        <div class="hero-search">
+          <span class="hero-search-icon">🔍</span>
+          <input
+            type="text"
+            placeholder="搜索餐厅 / 场馆 / 队伍,例如『羽毛球』"
+          />
+        </div>
+
+        <div class="hero-grid">
+          <div class="hero-feature" (click)="openDiningPanel()">
+            <div class="hero-feature-icon">🍱</div>
+            <div class="hero-feature-body">
+              <div class="hero-feature-title">校园饮食</div>
+              <div class="hero-feature-sub">今日推荐 {{ diningOptions.length }} 家餐厅</div>
+            </div>
+          </div>
+
+          <div class="hero-feature" (click)="openTeamsPanel()">
+            <div class="hero-feature-icon">🏃</div>
+            <div class="hero-feature-body">
+              <div class="hero-feature-title">运动组队</div>
+              <div class="hero-feature-sub">当前有 {{ exerciseTeams.length }} 支队伍可加入</div>
+            </div>
+          </div>
+
+          <div class="hero-feature" (click)="openVenuesPanel()">
+            <div class="hero-feature-icon">🏟️</div>
+            <div class="hero-feature-body">
+              <div class="hero-feature-title">场馆预约</div>
+              <div class="hero-feature-sub">支持篮球 / 羽毛球等多个场馆</div>
+            </div>
+          </div>
+
+          <div class="hero-feature" (click)="openMySchedulePanel()">
+            <div class="hero-feature-icon">📋</div>
+            <div class="hero-feature-body">
+              <div class="hero-feature-title">我的约队 &amp; 预约</div>
+              <div class="hero-feature-sub">一键总览今日相关安排</div>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      <!-- 今日状态总览 + 今日安排 + 常用入口(方案 B 概览区) -->
+      <section class="overview-section">
+        <div class="section-header">
+          <div class="section-title">今日状态总览</div>
+          <div class="section-hint">帮你快速判断「现在去不去」</div>
+        </div>
+        <div class="overview-card">
+          <div class="state-row">
+            <div class="state-card">
+              <div class="state-title">食堂拥挤指数</div>
+              <div>午餐高峰 · 部分窗口排队</div>
+              <div class="bar-bg"><div class="bar-fill busy"></div></div>
+              <div class="state-footer">
+                <span class="state-busy">建议错峰 10 分钟再去</span>
+                <span>实时刷新 · 1 分钟前</span>
+              </div>
+            </div>
+            <div class="state-card">
+              <div class="state-title">运动场地空闲</div>
+              <div>晚间 18:00-20:00 时段</div>
+              <div class="bar-bg"><div class="bar-fill"></div></div>
+              <div class="state-footer">
+                <span class="state-ok">羽毛球 / 网球场余量充足</span>
+                <span>2 小时后略忙</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </section>
+
+      <section class="overview-section">
+        <div class="section-header">
+          <div class="section-title">我的今日安排</div>
+          <div class="section-hint">一屏查看与你相关的就餐和运动提醒</div>
+        </div>
+        <div class="overview-card">
+          <div class="overview-timeline">
+            <div><strong>12:30</strong> 「青春餐厅」轻食套餐 · 建议 12:20 前出发</div>
+            <div><strong>18:00</strong> 羽毛球馆 3 号场 · 已预约 · 提前 10 分钟到场</div>
+            <div><strong>21:30</strong> 睡前拉伸 10 分钟 · 帮助放松腿部肌群</div>
+          </div>
+          <div class="overview-pill-link">📅 查看本周全部运动 / 就餐提醒 ›</div>
+        </div>
+      </section>
+
+      <section class="overview-section">
+        <div class="section-header">
+          <div class="section-title">常用入口</div>
+          <div class="section-hint">快速跳转到其他健康相关页面</div>
+        </div>
+        <div class="overview-card">
+          <div class="overview-links">
+            <div class="overview-link-row" (click)="goToExerciseDetail()">
+              <span>🏃 运动详情</span>
+              <span class="link-sub">查看今日运动时长</span>
+            </div>
+            <div class="overview-link-row" (click)="goToStepsDetail()">
+              <span>👣 步数详情</span>
+              <span class="link-sub">查看本周步数趋势</span>
+            </div>
+            <div class="overview-link-row" (click)="goToDietManagement()">
+              <span>🥗 饮食管理</span>
+              <span class="link-sub">打开今日饮食记录</span>
+            </div>
+          </div>
+        </div>
+      </section>
+
       <!-- 校园饮食推荐 -->
       <section class="service-section">
         <h2>校园饮食推荐</h2>
         <div class="dining-options">
           @for (option of diningOptions; track option.id) {
-            <div class="dining-card">
+            <div class="dining-card" (click)="openDiningDetail(option)">
               <div class="dining-image">
                 <img [src]="option.imageUrl" alt="{{ option.name }}" onerror="this.src='https://picsum.photos/300/200'" />
+                <span class="status-badge" [class.crowded]="option.id === 2" [class.idle]="option.id !== 2">
+                  {{ option.id === 2 ? '拥挤' : '空闲' }}
+                </span>
               </div>
               <div class="dining-info">
                 <div class="dining-header">
@@ -31,41 +156,416 @@
       </section>
 
       <!-- 运动组队 -->
-      <section class="service-section">
-        <h2>运动组队</h2>
-        <div class="exercise-teams">
-          @for (team of exerciseTeams; track team.id) {
-            <div class="team-card">
-              <div class="team-header">
-                <h3>{{ team.name }}</h3>
-                <span class="team-activity">{{ team.activity }}</span>
-              </div>
-              <div class="team-details">
-                <p><strong>活动时间:</strong> {{ team.time }}</p>
-                <p><strong>活动地点:</strong> {{ team.location }}</p>
-                <p><strong>当前人数:</strong> {{ team.currentMembers }}/{{ team.maxMembers }}</p>
-                <p><strong>队长:</strong> {{ team.captain }}</p>
-                <p class="team-description">{{ team.description }}</p>
-                <button class="join-team-btn" [disabled]="team.currentMembers >= team.maxMembers" (click)="joinExerciseTeam(team.id)">
-                  {{ team.currentMembers >= team.maxMembers ? '人数已满' : '加入队伍' }}
-                </button>
+      <section class="service-section sports-section">
+        <div class="sports-header">
+          <h2>运动组队</h2>
+          <div class="sports-tabs">
+            <div class="tab-btn" [class.active]="activeTab === 'teams'" (click)="switchTab('teams')">发现队伍</div>
+            <div class="tab-btn" [class.active]="activeTab === 'plans'" (click)="switchTab('plans')">我的训练</div>
+            <div class="tab-btn" [class.active]="activeTab === 'venues'" (click)="switchTab('venues')">场馆预约</div>
+          </div>
+        </div>
+
+        <!-- Tab Content -->
+        <div class="tab-content" [class.active]="activeTab === 'teams'">
+          <div class="filter-bar">
+            <input type="text" class="search-input" placeholder="搜索队伍...">
+            <select class="type-select">
+              <option>所有类型</option>
+              <option>跑步</option>
+              <option>瑜伽</option>
+              <option>篮球</option>
+            </select>
+          </div>
+
+          <div class="exercise-teams">
+            @for (team of exerciseTeams; track team.id) {
+              <div class="team-card" (click)="openTeamDetail(team)">
+                <div class="team-header">
+                  <h3>{{ team.name }}</h3>
+                  <span class="team-activity">{{ team.activity }}</span>
+                </div>
+                <div class="team-details">
+                  <p class="icon-text">🕒 {{ team.time }}</p>
+                  <p class="icon-text">📍 {{ team.location }}</p>
+                  
+                  <div class="team-footer">
+                    <div class="members-info">
+                      <span class="member-count">{{ team.currentMembers }}/{{ team.maxMembers }}人</span>
+                      <div class="progress-mini">
+                        <div class="progress-bar" 
+                             [style.width.%]="(team.currentMembers / team.maxMembers) * 100"
+                             [style.background-color]="team.currentMembers >= team.maxMembers ? '#F9D2E4' : '#01847F'">
+                        </div>
+                      </div>
+                    </div>
+                    <button class="join-team-btn" 
+                            [disabled]="team.currentMembers >= team.maxMembers" 
+                            (click)="$event.stopPropagation(); joinExerciseTeam(team.id)">
+                      {{ team.currentMembers >= team.maxMembers ? '已满员' : '加入' }}
+                    </button>
+                  </div>
+                </div>
               </div>
+            }
+          </div>
+        </div>
+
+        <div class="tab-content" [class.active]="activeTab === 'plans'">
+          <div class="plan-layout">
+            <div class="plan-sidebar">
+              <div class="sidebar-header">我的队伍</div>
+              <ul class="my-team-list">
+                @for (team of myTeams; track team.id) {
+                  <li class="my-team-item" 
+                      [class.active]="selectedMyTeamId === team.id"
+                      (click)="selectMyTeam(team.id)">
+                    <span>{{ team.name }}</span>
+                    @if (team.role === 'captain') {
+                      <span class="role-badge">队长</span>
+                    }
+                  </li>
+                }
+              </ul>
+              <button class="add-plan-btn">+ 发布新计划</button>
             </div>
-          }
+            
+            <div class="plan-timeline">
+              <div class="timeline-header">本周训练安排 (12/06 - 12/12)</div>
+              
+              @for (plan of filteredTrainingPlans; track plan.id) {
+                <div class="timeline-card" [class.pending]="plan.status === 'upcoming'">
+                  <div class="time-box">
+                    <div class="day">{{ plan.day }}</div>
+                    <div class="week">{{ plan.weekday }}</div>
+                  </div>
+                  <div class="content-box">
+                    <div class="plan-title">{{ plan.title }}</div>
+                    <div class="plan-desc">
+                      时间: {{ plan.time }} | 地点: {{ plan.location }} | 强度: 
+                      <span class="stars">
+                        @for (i of [1,2,3,4,5]; track i) {
+                          <span [style.color]="i <= plan.intensity ? '#01847F' : '#e0e0e0'">★</span>
+                        }
+                      </span>
+                    </div>
+                    @if (plan.tags.length > 0) {
+                      <div class="plan-tags">
+                        @for (tag of plan.tags; track tag) {
+                          <span class="tag">{{ tag }}</span>
+                        }
+                      </div>
+                    }
+                  </div>
+                  <div class="action-box">
+                    @if (plan.status === 'pending') {
+                      <button class="btn btn-primary" (click)="checkIn(plan)">签到</button>
+                    }
+                    @if (plan.status === 'upcoming') {
+                      <button class="btn btn-outline" disabled>待开始</button>
+                    }
+                    @if (plan.status === 'completed') {
+                      <button class="btn btn-success" disabled>已完成</button>
+                    }
+                  </div>
+                </div>
+              }
+            </div>
+          </div>
+        </div>
+
+        <div class="tab-content" [class.active]="activeTab === 'venues'">
+          <div class="venues-container">
+            <div class="venues-header">
+              <span class="header-title">快速查看今日 (12/06) 场地状态</span>
+              <a href="javascript:void(0)" class="view-records">查看我的预约记录 ></a>
+            </div>
+
+            <div class="venue-list">
+              @for (venue of venues; track venue.id) {
+                <div class="venue-row">
+                  <div class="venue-image">
+                    <img [src]="venue.image" alt="{{ venue.name }}" (error)="handleImageError($event)">
+                  </div>
+                  <div class="venue-details">
+                    <div class="venue-name">{{ venue.name }}</div>
+                    <div class="venue-tags">
+                      {{ venue.price }} | {{ venue.tags.join(' | ') }}
+                    </div>
+                  </div>
+                  <div class="venue-status">
+                    @for (slot of venue.timeSlots; track slot.time) {
+                      <div class="status-item">
+                        <div class="status-box" [class.full]="slot.status === 'full'" [class.available]="slot.status === 'available'">
+                          {{ slot.status === 'full' ? '满' : '空' }}
+                        </div>
+                        <div class="time-label">{{ slot.time }}</div>
+                      </div>
+                    }
+                  </div>
+                  <button class="btn btn-primary book-btn" (click)="bookVenue(venue)">预约</button>
+                </div>
+              }
+            </div>
+          </div>
         </div>
       </section>
 
       <!-- 底部帮助与反馈 -->
       <div class="help-feedback-section">
-        <button class="help-btn" (click)="viewFAQs()">
+        <button type="button" class="help-btn" (click)="openFaq()">
           <i class="help-icon">❓</i>
           常见问题
         </button>
-        <button class="feedback-btn" (click)="openFeedback()">
+        <button type="button" class="feedback-btn" (click)="openFeedback()">
           <i class="feedback-icon">💬</i>
           意见反馈
         </button>
       </div>
     </div>
   </main>
+
+  <!-- 校园饮食面板 -->
+  <div class="panel-overlay" [class.active]="isDiningPanelOpen" (click)="closeDiningPanel()"></div>
+  <div class="side-panel" [class.active]="isDiningPanelOpen">
+    <div class="panel-header">
+      <h3>校园饮食推荐</h3>
+      <button class="panel-close" (click)="closeDiningPanel()">×</button>
+    </div>
+    <div class="panel-content">
+      <div class="dining-list">
+        @for (option of diningOptions; track option.id) {
+          <div class="dining-item" (click)="openDiningDetail(option)">
+            <div class="dining-item-header">
+              <h4>{{ option.name }}</h4>
+              <span class="rating">{{ formatRating(option.rating) }}</span>
+            </div>
+            <p class="dining-item-sub">📍 {{ option.location }} ({{ option.distance }})</p>
+            <p class="dining-item-sub">健康选择: {{ option.healthyOptions.join('、') }}</p>
+          </div>
+        }
+      </div>
+    </div>
+  </div>
+
+  <!-- 运动组队面板 -->
+  <div class="panel-overlay" [class.active]="isTeamsPanelOpen" (click)="closeTeamsPanel()"></div>
+  <div class="side-panel" [class.active]="isTeamsPanelOpen">
+    <div class="panel-header">
+      <h3>运动组队</h3>
+      <button class="panel-close" (click)="closeTeamsPanel()">×</button>
+    </div>
+    <div class="panel-content">
+      <div class="teams-list">
+        @for (team of exerciseTeams; track team.id) {
+          <div class="team-item" (click)="openTeamDetail(team)">
+            <div class="team-item-header">
+              <h4>{{ team.name }}</h4>
+              <span class="team-activity-badge">{{ team.activity }}</span>
+            </div>
+            <p class="team-item-sub">🕒 {{ team.time }}</p>
+            <p class="team-item-sub">📍 {{ team.location }}</p>
+            <p class="team-item-sub">👥 {{ team.currentMembers }}/{{ team.maxMembers }} 人</p>
+          </div>
+        }
+      </div>
+    </div>
+  </div>
+
+  <!-- 场馆预约面板 -->
+  <div class="panel-overlay" [class.active]="isVenuesPanelOpen" (click)="closeVenuesPanel()"></div>
+  <div class="side-panel" [class.active]="isVenuesPanelOpen">
+    <div class="panel-header">
+      <h3>场馆预约</h3>
+      <button class="panel-close" (click)="closeVenuesPanel()">×</button>
+    </div>
+    <div class="panel-content">
+      <div class="venues-list">
+        @for (venue of venues; track venue.id) {
+          <div class="venue-item" (click)="bookVenue(venue)">
+            <div class="venue-item-header">
+              <h4>{{ venue.name }}</h4>
+              <span class="venue-price">{{ venue.price }}</span>
+            </div>
+            <p class="venue-item-sub">标签: {{ venue.tags.join(' / ') }}</p>
+            <div class="venue-slots">
+              @for (slot of venue.timeSlots; track slot.time) {
+                <span class="slot-badge" [class.available]="slot.status === 'available'" [class.full]="slot.status === 'full'">
+                  {{ slot.time }} {{ slot.status === 'available' ? '空' : '满' }}
+                </span>
+              }
+            </div>
+          </div>
+        }
+      </div>
+    </div>
+  </div>
+
+  <!-- 我的约队 & 预约面板 -->
+  <div class="panel-overlay" [class.active]="isMySchedulePanelOpen" (click)="closeMySchedulePanel()"></div>
+  <div class="side-panel" [class.active]="isMySchedulePanelOpen">
+    <div class="panel-header">
+      <h3>我的约队 & 预约</h3>
+      <button class="panel-close" (click)="closeMySchedulePanel()">×</button>
+    </div>
+    <div class="panel-content my-schedule-content">
+      
+      <!-- Section 1: 我的队伍 -->
+      <div class="schedule-section">
+        <div class="section-title">我的队伍</div>
+        <div class="my-teams-grid">
+          @for (team of myTeams; track team.id) {
+            <div class="my-team-card">
+              <div class="team-icon">{{ team.role === 'captain' ? '👑' : '🏃' }}</div>
+              <div class="team-info">
+                <div class="team-name">{{ team.name }}</div>
+                <div class="team-role">{{ team.role === 'captain' ? '队长' : '成员' }}</div>
+              </div>
+            </div>
+          }
+          @if (myTeams.length === 0) {
+            <div class="empty-state">暂未加入任何队伍</div>
+          }
+        </div>
+      </div>
+
+      <!-- Section 2: 近期训练计划 -->
+      <div class="schedule-section">
+        <div class="section-title">近期训练安排</div>
+        <div class="schedule-list">
+          @for (plan of myAllTrainingPlans; track plan.id) {
+            <div class="schedule-card" [class.today]="plan.weekday === '今天'">
+              <div class="schedule-date">
+                <span class="day">{{ plan.day }}</span>
+                <span class="week">{{ plan.weekday }}</span>
+              </div>
+              <div class="schedule-info">
+                <div class="schedule-title">{{ plan.title }}</div>
+                <div class="schedule-meta">
+                  <span class="time">🕒 {{ plan.time }}</span>
+                  <span class="loc">📍 {{ plan.location }}</span>
+                </div>
+              </div>
+              <div class="schedule-action">
+                @if (plan.status === 'pending') {
+                  <button class="btn-checkin" (click)="checkIn(plan)">签到</button>
+                } @else if (plan.status === 'completed') {
+                  <span class="status-done">已完成</span>
+                } @else {
+                  <span class="status-wait">待开始</span>
+                }
+              </div>
+            </div>
+          }
+          @if (myAllTrainingPlans.length === 0) {
+            <div class="empty-state">近期没有训练计划</div>
+          }
+        </div>
+      </div>
+
+      <!-- Section 3: 我的预约 -->
+      <div class="schedule-section">
+        <div class="section-title">场馆预约记录</div>
+        <div class="booking-list">
+          @for (booking of myBookings; track booking.id) {
+            <div class="booking-card">
+              <div class="booking-img">
+                <img [src]="booking.venueImage" alt="{{ booking.venueName }}" (error)="handleImageError($event)">
+              </div>
+              <div class="booking-info">
+                <div class="booking-name">{{ booking.venueName }}</div>
+                <div class="booking-time">{{ booking.date }} {{ booking.time }}</div>
+                <div class="booking-price">支付: {{ booking.price }}</div>
+              </div>
+              <div class="booking-status" [class]="booking.status">
+                {{ booking.status === 'upcoming' ? '待使用' : (booking.status === 'completed' ? '已完成' : '已取消') }}
+              </div>
+            </div>
+          }
+          @if (myBookings.length === 0) {
+            <div class="empty-state">暂无预约记录</div>
+          }
+        </div>
+      </div>
+
+    </div>
+  </div>
+
+  <!-- 详情弹窗 (Bottom Sheet) -->
+  <div class="bottom-sheet-overlay" [class.active]="isDetailOpen" (click)="closeDiningDetail()"></div>
+  <div class="bottom-sheet" [class.active]="isDetailOpen">
+    <div class="sheet-handle"></div>
+    <div class="sheet-header" *ngIf="selectedDiningDetail">
+      <div class="sheet-title">{{ selectedDiningDetail.name }}</div>
+      <div class="sheet-subtitle">营业时间: 06:30 - 21:00 | 人均: ¥15</div>
+    </div>
+    
+    <div class="sheet-content" *ngIf="selectedDiningDetail">
+      <!-- 实时状态 -->
+      <div class="detail-section">
+        <div class="section-head">📊 实时状态</div>
+        <div class="crowd-meter">
+          <div class="crowd-value" 
+               [style.width.%]="selectedDiningDetail.crowdLevel" 
+               [style.background]="selectedDiningDetail.status === 'crowded' ? 'linear-gradient(90deg, #F9D2E4, #f973a8)' : 'linear-gradient(90deg, #01847F, #02a39a)'">
+          </div>
+        </div>
+        <div class="crowd-text">
+          {{ selectedDiningDetail.status === 'crowded' ? '当前就餐人数较多,预计排队 15 分钟' : '当前就餐人数较少,无需排队' }}
+        </div>
+      </div>
+
+      <!-- 营养分析 -->
+      <div class="detail-section">
+        <div class="section-head">🥗 营养特色</div>
+        <div class="nutrition-tags">
+          <span class="nutri-tag">优质蛋白</span>
+          <span class="nutri-tag">低GI主食</span>
+          <span class="nutri-tag">根据运动量推荐</span>
+        </div>
+      </div>
+
+      <!-- 推荐菜品 -->
+      <div class="detail-section">
+        <div class="section-head">👍 今日健康推荐</div>
+        <div class="menu-grid">
+          <div class="menu-item" *ngFor="let item of selectedDiningDetail.menuItems">
+            <div class="menu-img" [style.background-image]="'url(' + item.image + ')'"></div>
+            <div class="menu-name">{{ item.name }}</div>
+            <div class="menu-cal">约 {{ item.calories }} kcal</div>
+            <div class="menu-price">¥{{ item.price }}</div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="sheet-footer">
+      <button class="btn btn-secondary" (click)="navigateToDining()">导航前往</button>
+      <button class="btn btn-primary" (click)="orderFood()">去点餐</button>
+    </div>
+  </div>
+  
+  <app-team-detail-modal 
+    [isVisible]="isTeamModalVisible" 
+    [team]="selectedTeam"
+    (close)="closeTeamDetail()"
+    (join)="joinExerciseTeam($event)">
+  </app-team-detail-modal>
+
+  <app-venue-detail-modal
+    [isVisible]="isVenueModalVisible"
+    [venue]="selectedVenue"
+    (close)="closeVenueDetail()"
+    (book)="handleVenueBooking($any($event))">
+  </app-venue-detail-modal>
+
+  <app-faq-modal
+    [isVisible]="isFaqModalVisible"
+    (close)="closeFaq()">
+  </app-faq-modal>
+
+  <app-feedback-modal
+    [isVisible]="isFeedbackModalVisible"
+    (close)="closeFeedback()">
+  </app-feedback-modal>
 </div>

+ 1322 - 103
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/school-services.component.scss

@@ -1,11 +1,21 @@
+@use "sass:color";
+
 // SchoolServices组件样式
 
+$brand-teal: #01847F;
+$brand-teal-soft: rgba(1, 132, 127, 0.08);
+$brand-teal-deep: #016c68;
+$brand-pink: #F9D2E4;
+$brand-pink-deep: #8b4c63;
+$text-main: #0f172a;
+$text-muted: #6b7280;
+
 .school-services-container {
   display: flex;
   flex-direction: column;
   height: 100vh;
-  background-color: #f5f7fa;
-  color: #333;
+  background-color: #f5f5f5;
+  color: $text-main;
 }
 
 // 头部导航
@@ -42,10 +52,430 @@
   flex: 1;
   padding: 2rem;
   overflow-y: auto;
+
+  .school-services-content {
+    max-width: 960px;
+    margin: 0 auto;
+  }
+}
+
+.services-hero {
+  background-color: #fff;
+  border-radius: 16px;
+  padding: 16px 20px;
+  margin-bottom: 24px;
+  box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06);
+
+  .hero-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
+  }
+
+  .hero-title {
+    font-size: 1.25rem;
+    font-weight: 600;
+    color: $text-main;
+  }
+
+  .hero-sub {
+    margin-top: 4px;
+    font-size: 0.7rem;
+    color: $text-muted;
+  }
+
+  .hero-badge {
+    padding: 4px 10px;
+    border-radius: 999px;
+    background: $brand-teal-soft;
+    color: $brand-teal-deep;
+    font-size: 0.7rem;
+  }
+
+  .hero-search {
+    margin-top: 8px;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    padding: 7px 10px;
+    border-radius: 999px;
+    background: #ffffff;
+    border: 1px solid $brand-teal-soft;
+
+    .hero-search-icon {
+      font-size: 0.9rem;
+    }
+
+    input {
+      flex: 1;
+      border: none;
+      outline: none;
+      font-size: 0.85rem;
+      background: transparent;
+      color: $text-main;
+
+      &::placeholder {
+        color: $text-muted;
+      }
+    }
+  }
+
+  .hero-grid {
+    margin-top: 12px;
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 8px;
+  }
+
+  .hero-feature {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    gap: 8px;
+    align-items: center;
+    padding: 8px 9px;
+    border-radius: 14px;
+    background: #f9fafb;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: #f0f1f3;
+      transform: translateY(-2px);
+    }
+  }
+
+  .hero-feature-icon {
+    width: 32px;
+    height: 32px;
+    border-radius: 999px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 18px;
+    background: linear-gradient(135deg, $brand-teal, color.adjust($brand-teal, $lightness: 8%));
+    color: #ffffff;
+  }
+
+  .hero-feature-title {
+    font-size: 0.9rem;
+    font-weight: 600;
+    color: $text-main;
+  }
+
+  .hero-feature-sub {
+    margin-top: 2px;
+    font-size: 0.75rem;
+    color: $text-muted;
+  }
+}
+// 概览区(方案 B:今日状态 / 今日安排 / 常用入口)
+.overview-section {
+  margin-top: 12px;
+
+  .section-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: baseline;
+    margin-bottom: 6px;
+  }
+
+  .section-title {
+    font-size: 0.95rem;
+    font-weight: 600;
+    color: $text-main;
+  }
+
+  .section-hint {
+    font-size: 0.8rem;
+    color: $text-muted;
+  }
+
+  .overview-card {
+    border-radius: 16px;
+    background: #fff;
+    box-shadow: 0 8px 18px rgba(15, 23, 42, 0.06);
+    padding: 10px 10px 8px;
+  }
+
+  .state-row {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 8px;
+  }
+
+  .state-card {
+    border-radius: 14px;
+    background: #f9fafb;
+    padding: 8px 8px 6px;
+    font-size: 0.7rem;
+    color: $text-muted;
+  }
+
+  .state-title {
+    font-size: 0.8rem;
+    font-weight: 600;
+    color: $text-main;
+    margin-bottom: 4px;
+  }
+
+  .bar-bg {
+    margin-top: 6px;
+    height: 6px;
+    border-radius: 999px;
+    background: #e5e7eb;
+    overflow: hidden;
+  }
+
+  .bar-fill {
+    height: 100%;
+    border-radius: inherit;
+    background: linear-gradient(90deg, $brand-teal, color.adjust($brand-teal, $lightness: 8%));
+    width: 70%;
+  }
+
+  .bar-fill.busy {
+    background: linear-gradient(90deg, $brand-pink, #f973a8);
+    width: 88%;
+  }
+
+  .state-footer {
+    margin-top: 4px;
+    display: flex;
+    justify-content: space-between;
+    font-size: 0.7rem;
+  }
+
+  .state-ok {
+    color: $brand-teal-deep;
+  }
+
+  .state-busy {
+    color: $brand-pink-deep;
+  }
+
+  .overview-timeline {
+    margin-top: 4px;
+    border-left: 1px dashed #e5e7eb;
+    padding-left: 10px;
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+    font-size: 0.8rem;
+    color: $text-muted;
+
+    strong {
+      color: $text-main;
+    }
+  }
+
+  .overview-pill-link {
+    margin-top: 4px;
+    font-size: 0.8rem;
+    color: $brand-teal-deep;
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+  }
+
+  .overview-links {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+    margin-top: 4px;
+  }
+
+  .overview-link-row {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-size: 0.8rem;
+    padding: 6px 8px;
+    border-radius: 10px;
+    background: #f9fafb;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: #f0f1f3;
+    }
+
+    .link-sub {
+      font-size: 0.75rem;
+      color: $text-muted;
+    }
+  }
+}
+
+// 侧面板样式(校园饮食、运动组队、场馆预约)
+.panel-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.5);
+  opacity: 0;
+  pointer-events: none;
+  transition: opacity 0.3s ease;
+  z-index: 1000;
+
+  &.active {
+    opacity: 1;
+    pointer-events: auto;
+  }
+}
+
+.side-panel {
+  position: fixed;
+  top: 0;
+  right: 0;
+  width: 100%;
+  max-width: 400px;
+  height: 100vh;
+  background: #fff;
+  transform: translateX(100%);
+  transition: transform 0.3s ease;
+  z-index: 1001;
+  display: flex;
+  flex-direction: column;
+  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
+
+  &.active {
+    transform: translateX(0);
+  }
+
+  .panel-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16px 20px;
+    border-bottom: 1px solid #e5e7eb;
+    background: #f9fafb;
+
+    h3 {
+      margin: 0;
+      font-size: 1.1rem;
+      font-weight: 600;
+      color: $text-main;
+    }
+
+    .panel-close {
+      background: none;
+      border: none;
+      font-size: 1.5rem;
+      cursor: pointer;
+      color: $text-muted;
+      transition: color 0.2s;
+
+      &:hover {
+        color: $text-main;
+      }
+    }
+  }
+
+  .panel-content {
+    flex: 1;
+    overflow-y: auto;
+    padding: 12px;
+  }
+
+  .dining-list,
+  .teams-list,
+  .venues-list {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .dining-item,
+  .team-item,
+  .venue-item {
+    padding: 12px;
+    border-radius: 12px;
+    background: #f9fafb;
+    cursor: pointer;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: #f0f1f3;
+      transform: translateX(-4px);
+    }
+
+    h4 {
+      margin: 0 0 4px 0;
+      font-size: 0.95rem;
+      font-weight: 600;
+      color: $text-main;
+    }
+  }
+
+  .dining-item-header,
+  .team-item-header,
+  .venue-item-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 6px;
+
+    h4 {
+      margin: 0;
+    }
+
+    .rating,
+    .team-activity-badge,
+    .venue-price {
+      font-size: 0.75rem;
+      padding: 2px 6px;
+      border-radius: 999px;
+      background: $brand-teal-soft;
+      color: $brand-teal-deep;
+      font-weight: 500;
+    }
+
+    .venue-price {
+      background: $brand-pink;
+      color: $brand-pink-deep;
+    }
+  }
+
+  .dining-item-sub,
+  .team-item-sub,
+  .venue-item-sub {
+    margin: 2px 0;
+    font-size: 0.8rem;
+    color: $text-muted;
+  }
+
+  .venue-slots {
+    margin-top: 6px;
+    display: flex;
+    flex-wrap: wrap;
+    gap: 4px;
+
+    .slot-badge {
+      font-size: 0.7rem;
+      padding: 2px 6px;
+      border-radius: 4px;
+      background: #e5e7eb;
+      color: $text-muted;
+
+      &.available {
+        background: $brand-teal-soft;
+        color: $brand-teal-deep;
+      }
+
+      &.full {
+        background: $brand-pink;
+        color: $brand-pink-deep;
+      }
+    }
+  }
 }
 
-// 服务区块通用样式
+// 服务区块通用样式(已从主视图中隐藏,仅保留样式以备后用)
 .service-section {
+  display: none;
   background-color: #fff;
   border-radius: 12px;
   padding: 2rem;
@@ -56,9 +486,9 @@
 .service-section h2 {
   margin-top: 0;
   margin-bottom: 1.5rem;
-  color: #2c3e50;
+  color: $text-main;
   font-size: 1.8rem;
-  border-bottom: 2px solid #3498db;
+  border-bottom: 2px solid $brand-teal;
   padding-bottom: 0.5rem;
 }
 
@@ -85,6 +515,21 @@
 .dining-image {
   height: 180px;
   overflow: hidden;
+  position: relative;
+
+  .status-badge {
+    position: absolute;
+    top: 8px;
+    right: 8px;
+    padding: 4px 8px;
+    border-radius: 4px;
+    font-size: 12px;
+    color: white;
+    background: rgba(0,0,0,0.6);
+
+    &.crowded { background: #ff4d4f; }
+    &.idle { background: #52c41a; }
+  }
 }
 
 .dining-image img {
@@ -139,155 +584,929 @@
 
 .healthy-options {
   font-size: 0.9rem;
-  color: #27ae60;
+  color: $brand-teal-deep;
 }
 
 // 运动组队样式
+.sports-section {
+  .sports-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+    border-bottom: 1px solid #f0f0f0;
+    
+    h2 {
+      margin-bottom: 0;
+      border: none;
+      padding-left: 0;
+    }
+  }
+
+  .sports-tabs {
+    display: flex;
+    gap: 24px;
+
+    .tab-btn {
+      padding: 8px 4px 16px;
+      cursor: pointer;
+      color: #666;
+      font-weight: 500;
+      border-bottom: 3px solid transparent;
+      transition: all 0.3s;
+      font-size: 1rem;
+
+      &:hover {
+        color: #1890ff;
+      }
+
+      &.active {
+        color: #1890ff;
+        border-bottom-color: #1890ff;
+      }
+    }
+  }
+
+  .tab-content {
+    display: none;
+    animation: fadeIn 0.3s ease;
+
+    &.active {
+      display: block;
+    }
+  }
+
+  @keyframes fadeIn {
+    from { opacity: 0; transform: translateY(5px); }
+    to { opacity: 1; transform: translateY(0); }
+  }
+
+  .filter-bar {
+    display: flex;
+    gap: 12px;
+    margin-bottom: 20px;
+
+    .search-input {
+      padding: 8px 12px;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      width: 240px;
+      
+      &:focus {
+        border-color: #1890ff;
+        outline: none;
+      }
+    }
+
+    .type-select {
+      padding: 8px 24px 8px 12px;
+      border: 1px solid #d9d9d9;
+      border-radius: 4px;
+      background-color: white;
+      cursor: pointer;
+
+      &:focus {
+        border-color: #1890ff;
+        outline: none;
+      }
+    }
+  }
+}
+
 .exercise-teams {
   display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
-  gap: 1.5rem;
-}
+  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+  gap: 20px;
 
-.team-card {
-  background: #fff9e6;
-  border-radius: 8px;
-  overflow: hidden;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-  transition: transform 0.3s, box-shadow 0.3s;
+  .team-card {
+    background: #fff;
+    border: 1px solid #f0f0f0;
+    border-radius: 8px;
+    padding: 0;
+    transition: all 0.3s ease;
+    cursor: pointer;
+    overflow: hidden;
+    box-shadow: none; 
+
+    &:hover {
+      transform: translateY(-2px);
+      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+    }
+
+    .team-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 0;
+      padding: 16px;
+      background: #fafafa;
+      border-bottom: 1px solid #f0f0f0;
+      color: #333; 
+
+      h3 {
+        margin: 0;
+        font-size: 1rem;
+        color: #333;
+      }
+
+      .team-activity {
+        font-size: 0.75rem;
+        color: $brand-teal-deep;
+        background: $brand-teal-soft;
+        padding: 2px 8px;
+        border-radius: 10px;
+      }
+    }
+
+    .team-details {
+      padding: 16px;
+      text-align: left;
+      
+      .icon-text {
+        color: #666;
+        margin-bottom: 8px;
+        font-size: 0.9rem;
+        display: flex;
+        align-items: center;
+      }
+
+      .team-footer {
+        margin-top: 16px;
+        display: flex;
+        justify-content: space-between;
+        align-items: flex-end;
+
+        .members-info {
+          flex: 1;
+          margin-right: 16px;
+
+          .member-count {
+            font-size: 0.8rem;
+            color: #999;
+            display: block;
+            margin-bottom: 4px;
+          }
+
+          .progress-mini {
+            height: 4px;
+            background: #f0f0f0;
+            border-radius: 2px;
+            overflow: hidden;
+
+            .progress-bar {
+              height: 100%;
+              transition: width 0.3s ease;
+            }
+          }
+        }
+
+        .join-team-btn {
+          padding: 4px 16px;
+          background: $brand-teal;
+          color: white;
+          border: none;
+          border-radius: 4px;
+          cursor: pointer;
+          font-size: 0.85rem;
+          transition: background 0.3s;
+          width: auto; 
+          font-weight: normal;
+
+          &:hover {
+            background: #40a9ff;
+          }
+
+          &:disabled {
+            background: #d9d9d9;
+            cursor: not-allowed;
+            opacity: 0.7;
+          }
+        }
+      }
+    }
+  }
 }
 
-.team-card:hover {
-  transform: translateY(-5px);
-  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
+.placeholder-content {
+  padding: 40px;
+  text-align: center;
+  color: #999;
+  background: #fafafa;
+  border-radius: 8px;
+  border: 1px dashed #d9d9d9;
 }
 
-.team-header {
-  background: linear-gradient(135deg, #f39c12, #e67e22);
-  color: white;
-  padding: 1rem;
+// 我的训练 Tab 样式
+.plan-layout {
   display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
+  gap: 24px;
+  background: #fff;
+  padding-top: 20px;
 
-.team-header h3 {
-  margin: 0;
-  font-size: 1.3rem;
-}
+  .plan-sidebar {
+    width: 220px;
+    border-right: 1px solid #f0f0f0;
+    padding-right: 20px;
 
-.team-activity {
-  background: rgba(255, 255, 255, 0.2);
-  padding: 0.3rem 0.8rem;
-  border-radius: 15px;
-  font-size: 0.9rem;
-}
+    .sidebar-header {
+      font-size: 0.9rem;
+      color: #999;
+      margin-bottom: 12px;
+    }
 
-.team-details {
-  padding: 1.2rem;
-  text-align: left;
-}
+    .my-team-list {
+      list-style: none;
+      padding: 0;
+      margin: 0;
 
-.team-details p {
-  margin: 0.5rem 0;
-  color: #555;
-  font-size: 0.95rem;
-}
+      .my-team-item {
+        padding: 12px 16px;
+        border-radius: 6px;
+        cursor: pointer;
+        margin-bottom: 8px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        transition: all 0.2s;
+        font-size: 0.95rem;
 
-.team-description {
-  font-style: italic;
-  color: #7f8c8d;
-  margin-top: 0.8rem !important;
-  margin-bottom: 1rem !important;
-}
+        &:hover {
+          background: #f5f5f5;
+        }
 
-.join-team-btn {
-  background-color: #27ae60;
-  color: white;
-  border: none;
-  padding: 0.8rem 1.5rem;
-  border-radius: 6px;
-  cursor: pointer;
-  font-size: 1rem;
-  font-weight: bold;
-  transition: background-color 0.3s;
-  width: 100%;
-}
+        &.active {
+          background: #e6f7ff;
+          color: #1890ff;
+          font-weight: 500;
+        }
+
+        .role-badge {
+          font-size: 11px;
+          background: #1890ff;
+          color: white;
+          padding: 2px 8px;
+          border-radius: 10px;
+          transform: scale(0.9);
+        }
+      }
+    }
+
+    .add-plan-btn {
+      width: 100%;
+      margin-top: 16px;
+      padding: 10px;
+      background: #fff;
+      border: 1px dashed #d9d9d9;
+      border-radius: 6px;
+      color: #666;
+      cursor: pointer;
+      transition: all 0.3s;
+
+      &:hover {
+        border-color: $brand-teal;
+        color: $brand-teal;
+      }
+    }
+  }
+
+  .plan-timeline {
+    flex: 1;
+
+    .timeline-header {
+      font-weight: 600;
+      margin-bottom: 20px;
+      font-size: 1rem;
+      color: #333;
+    }
+
+    .timeline-card {
+      background: #fff;
+      border: 1px solid #e8e8e8;
+      border-left: 4px solid #1890ff;
+      padding: 20px;
+      border-radius: 4px;
+      margin-bottom: 16px;
+      display: flex;
+      align-items: center;
+      transition: all 0.2s;
+
+      &.pending {
+        border-left-color: #d9d9d9;
+        
+        .time-box, .plan-title {
+          color: #999;
+        }
+      }
+
+      .time-box {
+        text-align: center;
+        padding-right: 24px;
+        border-right: 1px solid #f0f0f0;
+        min-width: 80px;
+
+        .day {
+          font-size: 24px;
+          font-weight: bold;
+          line-height: 1;
+          margin-bottom: 4px;
+        }
+
+        .week {
+          font-size: 13px;
+          color: #999;
+        }
+      }
+
+      .content-box {
+        flex: 1;
+        padding-left: 24px;
+
+        .plan-title {
+          font-weight: 600;
+          font-size: 1.1rem;
+          margin-bottom: 8px;
+        }
+
+        .plan-desc {
+          font-size: 13px;
+          color: #666;
+          margin-bottom: 8px;
+          display: flex;
+          align-items: center;
+          gap: 6px;
+
+          .stars {
+            display: inline-flex;
+            gap: 2px;
+          }
+        }
+
+        .plan-tags {
+          display: flex;
+          gap: 8px;
+
+          .tag {
+            font-size: 12px;
+            background: #f5f5f5;
+            padding: 2px 8px;
+            border-radius: 4px;
+            color: #666;
+          }
+        }
+      }
+
+      .action-box {
+        padding-left: 20px;
+        
+        .btn {
+          padding: 6px 20px;
+          border-radius: 4px;
+          border: none;
+          cursor: pointer;
+          font-size: 14px;
+
+          &-primary {
+            background: $brand-teal;
+            color: white;
+            &:hover { background: color.adjust($brand-teal, $lightness: 6%); }
+          }
 
-.join-team-btn:hover:not(:disabled) {
-  background-color: #229954;
+          &-outline {
+            background: #fff;
+            border: 1px solid #d9d9d9;
+            color: #999;
+            cursor: default;
+          }
+          
+          &-success {
+            background: $brand-teal;
+            color: white;
+            cursor: default;
+          }
+        }
+      }
+    }
+  }
 }
 
-.join-team-btn:disabled {
-  background-color: #95a5a6;
-  cursor: not-allowed;
+// 场馆预约 Tab 样式
+.venues-container {
+  background: #fff;
+  padding-top: 10px;
+
+  .venues-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+
+    .header-title {
+      font-size: 0.95rem;
+      color: #666;
+    }
+
+    .view-records {
+      font-size: 13px;
+      color: $brand-teal;
+      text-decoration: none;
+      
+      &:hover {
+        text-decoration: underline;
+      }
+    }
+  }
+
+  .venue-list {
+    border: 1px solid #e8e8e8;
+    border-radius: 8px;
+    background: #fff;
+  }
+
+  .venue-row {
+    display: flex;
+    align-items: center;
+    padding: 20px;
+    border-bottom: 1px solid #e8e8e8;
+
+    &:last-child {
+      border-bottom: none;
+    }
+
+    .venue-image {
+      width: 100px;
+      height: 70px;
+      border-radius: 4px;
+      overflow: hidden;
+      margin-right: 20px;
+      background: #f5f5f5;
+      border: 1px solid #eee;
+
+      img {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+    }
+
+    .venue-details {
+      flex: 1;
+
+      .venue-name {
+        font-weight: 600;
+        font-size: 1rem;
+        margin-bottom: 6px;
+        color: #333;
+      }
+
+      .venue-tags {
+        font-size: 12px;
+        color: #999;
+      }
+    }
+
+    .venue-status {
+      display: flex;
+      gap: 16px;
+      margin-right: 30px;
+
+      .status-item {
+        text-align: center;
+        
+        .status-box {
+          width: 28px;
+          height: 28px;
+          border-radius: 4px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          font-size: 12px;
+          margin-bottom: 4px;
+
+          &.full {
+            background: $brand-pink;
+            color: $brand-pink-deep;
+            border: 1px solid color.adjust($brand-pink, $lightness: 8%);
+          }
+
+          &.available {
+            background: $brand-teal-soft;
+            color: $brand-teal-deep;
+            border: 1px solid rgba(1, 132, 127, 0.25);
+          }
+        }
+
+        .time-label {
+          font-size: 11px;
+          color: #999;
+        }
+      }
+    }
+
+    .book-btn {
+      padding: 6px 20px;
+      background: $brand-teal;
+      color: white;
+      border: none;
+      border-radius: 4px;
+      cursor: pointer;
+      font-size: 14px;
+      transition: background 0.3s;
+
+      &:hover {
+        background: color.adjust($brand-teal, $lightness: 6%);
+      }
+    }
+  }
 }
 
 // 帮助与反馈按钮样式
 .help-feedback-section {
   display: flex;
   justify-content: center;
-  gap: 1.5rem;
-  margin-top: 2rem;
-  padding: 1.5rem;
-  background: #fff;
-  border-radius: 8px;
-  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  gap: 1rem;
+  margin-top: 1.5rem;
+  padding: 1rem;
+  background: transparent; // Remove white background box
+  box-shadow: none; // Remove shadow
 }
 
 .help-btn, .feedback-btn {
   display: flex;
   align-items: center;
-  gap: 0.5rem;
-  padding: 1rem 2rem;
+  justify-content: center;
+  gap: 0.4rem;
+  padding: 0.6rem 1.2rem; // Smaller padding
   border: none;
-  border-radius: 6px;
+  border-radius: 20px; // More rounded
   cursor: pointer;
-  font-size: 1rem;
-  font-weight: bold;
-  transition: transform 0.3s, box-shadow 0.3s;
+  font-size: 0.9rem; // Smaller font
+  font-weight: 500;
+  transition: transform 0.2s, box-shadow 0.2s;
+  min-width: 120px; // Ensure consistent width
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); // Subtle shadow for buttons themselves
 }
 
 .help-btn {
-  background-color: #3498db;
-  color: white;
+  background-color: white;
+  color: $brand-teal;
+  border: 1px solid $brand-teal-soft;
 }
 
 .feedback-btn {
-  background-color: #e74c3c;
-  color: white;
+  background-color: white;
+  color: $brand-pink-deep;
+  border: 1px solid color.adjust($brand-pink, $lightness: 10%);
 }
 
 .help-btn:hover, .feedback-btn:hover {
-  transform: translateY(-3px);
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+  transform: translateY(-2px);
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12);
 }
 
 .help-icon, .feedback-icon {
-  font-size: 1.2rem;
+  font-size: 1rem;
+  font-style: normal;
 }
 
-// 响应式设计
-@media (max-width: 768px) {
-  .school-services-main {
-    padding: 1rem;
+// My Schedule Panel Styles
+.my-schedule-content {
+  padding: 20px;
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+}
+
+.schedule-section {
+  .section-title {
+    font-size: 1rem;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 12px;
+    padding-left: 10px;
+    border-left: 3px solid #01847F;
   }
-  
-  .service-section {
-    padding: 1.5rem;
+}
+
+.my-teams-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+  gap: 12px;
+}
+
+.my-team-card {
+  background: #f9f9f9;
+  border-radius: 8px;
+  padding: 12px;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  border: 1px solid #eee;
+
+  .team-icon {
+    font-size: 1.5rem;
+    background: white;
+    width: 36px;
+    height: 36px;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: 0 2px 5px rgba(0,0,0,0.05);
   }
-  
-  .dining-options,
-  .food-list,
-  .exercise-teams {
-    grid-template-columns: 1fr;
+
+  .team-info {
+    .team-name {
+      font-size: 0.9rem;
+      font-weight: 500;
+      color: #333;
+    }
+    .team-role {
+      font-size: 0.75rem;
+      color: #666;
+    }
   }
-  
-  .help-feedback-section {
+}
+
+.schedule-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.schedule-card {
+  display: flex;
+  align-items: center;
+  padding: 12px;
+  background: white;
+  border: 1px solid #eee;
+  border-radius: 8px;
+  gap: 12px;
+
+  &.today {
+    border-color: #01847F;
+    background: rgba(1, 132, 127, 0.02);
+  }
+
+  .schedule-date {
+    display: flex;
     flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    background: #f5f5f5;
+    padding: 8px;
+    border-radius: 6px;
+    min-width: 45px;
+
+    .day {
+      font-size: 1.1rem;
+      font-weight: 700;
+      color: #333;
+      line-height: 1;
+    }
+    .week {
+      font-size: 0.7rem;
+      color: #666;
+      margin-top: 4px;
+    }
+  }
+
+  .schedule-info {
+    flex: 1;
+    
+    .schedule-title {
+      font-size: 0.95rem;
+      font-weight: 500;
+      color: #333;
+      margin-bottom: 4px;
+    }
+    .schedule-meta {
+      font-size: 0.8rem;
+      color: #666;
+      display: flex;
+      gap: 10px;
+    }
+  }
+
+  .schedule-action {
+    .btn-checkin {
+      background: #01847F;
+      color: white;
+      border: none;
+      padding: 6px 12px;
+      border-radius: 16px;
+      font-size: 0.8rem;
+      cursor: pointer;
+    }
+
+    .status-done {
+      color: #01847F;
+      font-size: 0.8rem;
+    }
+    
+    .status-wait {
+      color: #999;
+      font-size: 0.8rem;
+    }
+  }
+}
+
+.booking-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.booking-card {
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  border: 1px solid #eee;
+  border-radius: 8px;
+  gap: 12px;
+
+  .booking-img {
+    width: 60px;
+    height: 60px;
+    border-radius: 6px;
+    overflow: hidden;
+    
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: cover;
+    }
+  }
+
+  .booking-info {
+    flex: 1;
+
+    .booking-name {
+      font-size: 0.9rem;
+      font-weight: 500;
+      color: #333;
+    }
+    .booking-time {
+      font-size: 0.8rem;
+      color: #666;
+      margin: 2px 0;
+    }
+    .booking-price {
+      font-size: 0.8rem;
+      color: #999;
+    }
+  }
+
+  .booking-status {
+    font-size: 0.8rem;
+    padding: 4px 8px;
+    border-radius: 4px;
+    background: #f5f5f5;
+    color: #666;
+
+    &.upcoming {
+      background: #e6f7ff;
+      color: #1890ff;
+    }
+    &.completed {
+      background: #f6ffed;
+      color: #52c41a;
+    }
+  }
+}
+
+.empty-state {
+  text-align: center;
+  padding: 20px;
+  color: #999;
+  font-size: 0.9rem;
+  background: #f9f9f9;
+  border-radius: 8px;
+}
+
+// 响应式设计
+@media (max-width: 480px) {
+  .hero-grid {
+    grid-template-columns: 1fr;
   }
   
-  .help-btn, .feedback-btn {
-    justify-content: center;
+  .dining-card {
+    flex-direction: column;
+    
+    .dining-image {
+      width: 100%;
+      height: 140px;
+    }
   }
-}
+}
+
+// Bottom Sheet Styles
+.bottom-sheet-overlay {
+    position: fixed;
+    top: 0; left: 0; right: 0; bottom: 0;
+    background: rgba(0,0,0,0.5);
+    opacity: 0;
+    pointer-events: none;
+    transition: opacity 0.3s;
+    z-index: 1000;
+    &.active { opacity: 1; pointer-events: auto; }
+}
+
+.bottom-sheet {
+    position: fixed;
+    bottom: 0; left: 0; right: 0;
+    background: #fff;
+    border-radius: 20px 20px 0 0;
+    height: 70vh;
+    transform: translateY(100%);
+    transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
+    display: flex;
+    flex-direction: column;
+    z-index: 1001;
+    &.active { transform: translateY(0); }
+
+    .sheet-handle {
+        width: 40px; height: 4px;
+        background: #e8e8e8;
+        border-radius: 2px;
+        margin: 12px auto;
+    }
+
+    .sheet-header {
+        padding: 0 20px 16px;
+        border-bottom: 1px solid #f0f0f0;
+        .sheet-title { font-size: 20px; font-weight: 600; margin-bottom: 4px; color: #333; }
+        .sheet-subtitle { font-size: 12px; color: #8c8c8c; }
+    }
+
+    .sheet-content {
+        flex: 1;
+        overflow-y: auto;
+        padding: 20px;
+        text-align: left;
+    }
+
+    .detail-section { margin-bottom: 24px; }
+    .section-head { font-size: 15px; font-weight: 600; margin-bottom: 12px; display: flex; align-items: center; gap: 6px; color: #333; }
+
+    .menu-grid {
+        display: grid;
+        grid-template-columns: repeat(2, 1fr);
+        gap: 12px;
+    }
+    .menu-item {
+        border: 1px solid #f0f0f0;
+        border-radius: 8px;
+        padding: 8px;
+        .menu-img {
+            width: 100%; height: 80px;
+            background: #eee;
+            border-radius: 4px;
+            margin-bottom: 8px;
+            background-size: cover;
+        }
+        .menu-name { font-size: 13px; font-weight: 500; color: #333; }
+        .menu-cal { font-size: 11px; color: #8c8c8c; }
+        .menu-price { color: $brand-pink-deep; font-weight: 600; font-size: 13px; margin-top: 4px; }
+    }
+
+    .crowd-meter {
+        height: 6px;
+        background: #f0f0f0;
+        border-radius: 3px;
+        margin: 8px 0;
+        overflow: hidden;
+        .crowd-value {
+            height: 100%;
+            border-radius: 3px;
+            transition: width 0.3s;
+        }
+    }
+    .crowd-text { font-size: 12px; color: #8c8c8c; }
+
+    .nutrition-tags { display: flex; gap: 8px; }
+    .nutri-tag {
+        padding: 4px 8px;
+        background: $brand-teal-soft;
+        border: 1px solid rgba(1, 132, 127, 0.25);
+        border-radius: 4px;
+        color: $brand-teal-deep;
+        font-size: 11px;
+    }
+
+    .sheet-footer {
+        padding: 16px 20px;
+        border-top: 1px solid #f0f0f0;
+        display: flex;
+        gap: 12px;
+        .btn {
+            flex: 1;
+            height: 40px;
+            border-radius: 20px;
+            border: none;
+            font-weight: 500;
+            cursor: pointer;
+            &-secondary { background: #f0f0f0; color: #333; }
+            &-primary { background: $brand-teal; color: white; }
+        }
+    }
+}

+ 351 - 44
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/school-services.component.ts

@@ -1,59 +1,114 @@
-import { Component } from '@angular/core';
+import { Component, ChangeDetectorRef } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { Router, RouterModule } from '@angular/router';
 import { FormsModule } from '@angular/forms';
 import { PageHeaderComponent } from '../../shared/page-header/page-header.component';
-
-// 定义校园饮食推荐接口
-interface DiningOption {
-  id: number;
-  name: string;
-  location: string;
-  distance: string;
-  rating: number;
-  healthyOptions: string[];
-  imageUrl: string;
-}
-
-// 定义食物热量接口
-interface FoodItem {
-  id: number;
-  name: string;
-  calories: number;
-  protein: number;
-  carbs: number;
-  fat: number;
-  category: string;
-}
-
-// 定义运动组队接口
-interface ExerciseTeam {
-  id: number;
-  name: string;
-  activity: string;
-  time: string;
-  location: string;
-  currentMembers: number;
-  maxMembers: number;
-  description: string;
-  captain: string;
-}
+import { TeamDetailModalComponent } from './components/team-detail-modal/team-detail-modal.component';
+import { VenueDetailModalComponent } from './components/venue-detail-modal/venue-detail-modal.component';
+import { FaqModalComponent } from './components/faq-modal/faq-modal.component';
+import { FeedbackModalComponent } from './components/feedback-modal/feedback-modal.component';
+import { DiningOption, FoodItem, ExerciseTeam, TrainingPlan, MyTeam, Venue, MyBooking } from './school-services.types';
 
 @Component({
   selector: 'app-school-services',
   standalone: true,
-  imports: [CommonModule, RouterModule, FormsModule, PageHeaderComponent],
+  imports: [CommonModule, RouterModule, FormsModule, PageHeaderComponent, TeamDetailModalComponent, VenueDetailModalComponent, FaqModalComponent, FeedbackModalComponent],
   templateUrl: './school-services.component.html',
   styleUrl: './school-services.component.scss'
 })
 export class SchoolServicesComponent {
-  constructor(private router: Router) {}
+  constructor(private router: Router, private cdr: ChangeDetectorRef) {}
+
+  // 运动组队Tabs
+  activeTab: 'teams' | 'plans' | 'venues' = 'teams';
+
+  switchTab(tab: 'teams' | 'plans' | 'venues'): void {
+    this.activeTab = tab;
+  }
+
+  // 队伍详情弹窗状态
+  selectedTeam: ExerciseTeam | null = null;
+  isTeamModalVisible: boolean = false;
+
+  openTeamDetail(team: ExerciseTeam): void {
+    this.selectedTeam = team;
+    this.isTeamModalVisible = true;
+  }
+
+  closeTeamDetail(): void {
+    this.isTeamModalVisible = false;
+    setTimeout(() => {
+      this.selectedTeam = null;
+    }, 300);
+  }
 
   // 返回仪表盘
   backToDashboard(): void {
     this.router.navigate(['/dashboard']);
   }
 
+  // 概览区跳转:运动详情 / 步数详情 / 饮食管理
+  goToExerciseDetail(): void {
+    this.router.navigate(['/exercise/detail']);
+  }
+
+  goToStepsDetail(): void {
+    this.router.navigate(['/exercise/steps']);
+  }
+
+  goToDietManagement(): void {
+    this.router.navigate(['/diet']);
+  }
+
+  // Hero 功能卡片面板控制
+  isDiningPanelOpen: boolean = false;
+  isTeamsPanelOpen: boolean = false;
+  isVenuesPanelOpen: boolean = false;
+  isMySchedulePanelOpen: boolean = false;
+
+  openDiningPanel(): void {
+    this.isDiningPanelOpen = true;
+  }
+
+  closeDiningPanel(): void {
+    this.isDiningPanelOpen = false;
+  }
+
+  openTeamsPanel(): void {
+    this.isTeamsPanelOpen = true;
+  }
+
+  closeTeamsPanel(): void {
+    this.isTeamsPanelOpen = false;
+  }
+
+  openVenuesPanel(): void {
+    this.isVenuesPanelOpen = true;
+  }
+
+  closeVenuesPanel(): void {
+    this.isVenuesPanelOpen = false;
+  }
+
+  openMySchedulePanel(): void {
+    console.log('Opening My Schedule Panel... Current state:', this.isMySchedulePanelOpen);
+    this.isMySchedulePanelOpen = true;
+    this.cdr.detectChanges();
+    console.log('Panel state set to true');
+  }
+
+  closeMySchedulePanel(): void {
+    console.log('Closing My Schedule Panel');
+    this.isMySchedulePanelOpen = false;
+    this.cdr.detectChanges();
+  }
+
+  handleImageError(event: any): void {
+    event.target.onerror = null; // Prevent infinite loop
+    // Base64 gray placeholder
+    event.target.src = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiB2aWV3Qm94PSIwIDAgMjAwIDE1MCI+PHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxNTAiIGZpbGw9IiNlNWU3ZWIiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGZpbGw9IiM5OTkiPk5vIEltYWdlPC90ZXh0Pjwvc3ZnPg==';
+  }
+
   // 校园饮食推荐数据
   diningOptions: DiningOption[] = [
     {
@@ -159,9 +214,194 @@ export class SchoolServicesComponent {
       maxMembers: 10,
       description: '周末篮球友谊赛,锻炼身体,享受团队合作乐趣',
       captain: '王强'
+    },
+    {
+      id: 4,
+      name: '极速羽毛球',
+      activity: '羽毛球',
+      time: '每周三、五 19:00',
+      location: '综合体育馆',
+      currentMembers: 4,
+      maxMembers: 8,
+      description: '羽毛球爱好者集结,切磋球技,共同进步',
+      captain: '陈晨'
     }
   ];
 
+  // 我的训练数据
+  myTeams: MyTeam[] = [
+    { id: 1, name: '破晓跑团', role: 'captain' },
+    { id: 2, name: '云舒瑜伽团', role: 'member' }
+  ];
+  selectedMyTeamId: number = 1;
+
+  trainingPlans: TrainingPlan[] = [
+    {
+      id: 1,
+      teamId: 1,
+      day: '06',
+      date: '2023-12-06',
+      weekday: '今天',
+      title: 'LSD 长距离慢跑训练',
+      time: '18:30',
+      location: '东区田径场',
+      intensity: 3,
+      tags: ['有氧耐力'],
+      status: 'pending' // 待签到
+    },
+    {
+      id: 2,
+      teamId: 1,
+      day: '08',
+      date: '2023-12-08',
+      weekday: '周日',
+      title: '核心肌群强化',
+      time: '09:00',
+      location: '线上会议',
+      intensity: 2,
+      tags: [],
+      status: 'upcoming' // 待开始
+    },
+    {
+      id: 3,
+      teamId: 2,
+      day: '07',
+      date: '2023-12-07',
+      weekday: '明天',
+      title: '晨间唤醒瑜伽',
+      time: '07:00',
+      location: '体育馆舞蹈房',
+      intensity: 2,
+      tags: ['柔韧性', '呼吸控制'],
+      status: 'upcoming'
+    },
+    {
+      id: 4,
+      teamId: 2,
+      day: '09',
+      date: '2023-12-09',
+      weekday: '周一',
+      title: '流瑜伽进阶',
+      time: '18:30',
+      location: '体育馆舞蹈房',
+      intensity: 4,
+      tags: ['核心力量', '平衡'],
+      status: 'upcoming'
+    }
+  ];
+
+  // 获取当前选中队伍的训练计划
+  get filteredTrainingPlans(): TrainingPlan[] {
+    return this.trainingPlans.filter(plan => plan.teamId === this.selectedMyTeamId);
+  }
+
+  // 获取所有我加入队伍的训练计划
+  get myAllTrainingPlans(): TrainingPlan[] {
+    try {
+      if (!this.myTeams || !this.trainingPlans) return [];
+      
+      const myTeamIds = this.myTeams.map(t => t.id);
+      return this.trainingPlans
+        .filter(p => p && myTeamIds.includes(p.teamId))
+        .sort((a, b) => {
+          if (!a.date || !a.time || !b.date || !b.time) return 0;
+          const timeA = new Date(a.date.replace(/-/g, '/') + ' ' + a.time).getTime();
+          const timeB = new Date(b.date.replace(/-/g, '/') + ' ' + b.time).getTime();
+          return timeA - timeB;
+        });
+    } catch (e) {
+      console.warn('Error calculating training plans:', e);
+      return [];
+    }
+  }
+
+  selectMyTeam(teamId: number): void {
+    this.selectedMyTeamId = teamId;
+  }
+
+  checkIn(plan: TrainingPlan): void {
+    plan.status = 'completed';
+    alert('签到成功!');
+  }
+
+  // 场馆预约数据
+  venues: Venue[] = [
+    {
+      id: 1,
+      name: '室内篮球馆',
+      image: 'https://images.unsplash.com/photo-1546519638-68e109498ee3?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=60',
+      price: '¥20/小时',
+      tags: ['地板防滑', '空调开放'],
+      timeSlots: [
+        { time: '18:00', status: 'full' },
+        { time: '19:00', status: 'available' },
+        { time: '20:00', status: 'available' }
+      ]
+    },
+    {
+      id: 2,
+      name: '综合羽毛球馆',
+      image: 'https://images.unsplash.com/photo-1626224583764-84786c71971e?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=60',
+      price: '¥15/小时',
+      tags: ['需自备球拍'],
+      timeSlots: [
+        { time: '18:00', status: 'available' },
+        { time: '19:00', status: 'available' }
+      ]
+    }
+  ];
+
+  // 我的预约记录
+  myBookings: MyBooking[] = [
+    {
+      id: 101,
+      venueId: 1,
+      venueName: '室内篮球馆',
+      venueImage: 'https://images.unsplash.com/photo-1546519638-68e109498ee3?ixlib=rb-1.2.1&auto=format&fit=crop&w=200&q=60',
+      date: '2023-12-06',
+      time: '18:00',
+      status: 'upcoming',
+      price: '¥20'
+    }
+  ];
+
+  selectedVenue: Venue | null = null;
+  isVenueModalVisible: boolean = false;
+
+  bookVenue(venue: Venue): void {
+    this.selectedVenue = venue;
+    this.isVenueModalVisible = true;
+  }
+
+  closeVenueDetail(): void {
+    this.isVenueModalVisible = false;
+    setTimeout(() => {
+      this.selectedVenue = null;
+    }, 300);
+  }
+
+  handleVenueBooking(event: {venueId: number, time: string}): void {
+    const venue = this.venues.find(v => v.id === event.venueId);
+    if (venue) {
+      // Create new booking
+      const newBooking: MyBooking = {
+        id: Math.floor(Math.random() * 10000),
+        venueId: venue.id,
+        venueName: venue.name,
+        venueImage: venue.image,
+        date: '2023-12-06', // Assume today for demo
+        time: event.time,
+        status: 'upcoming',
+        price: venue.price.split('/')[0]
+      };
+      
+      this.myBookings.unshift(newBooking);
+      
+      alert(`预约成功!\n场馆:${venue.name}\n时间:${event.time}\n请准时前往。`);
+      this.closeVenueDetail();
+    }
+  }
+
   // 当前选择的食物进行热量计算
   selectedFoods: { item: FoodItem, quantity: number }[] = [];
   searchFoodQuery: string = '';
@@ -196,24 +436,91 @@ export class SchoolServicesComponent {
     const team = this.exerciseTeams.find(t => t.id === teamId);
     if (team && team.currentMembers < team.maxMembers) {
       team.currentMembers += 1;
+      
+      // Add to my teams if not exists
+      if (!this.myTeams.find(t => t.id === team.id)) {
+        this.myTeams.push({
+          id: team.id,
+          name: team.name,
+          role: 'member'
+        });
+      }
+
+      this.closeTeamDetail();
       alert(`已成功加入${team.name}!`);
     } else if (team) {
       alert('队伍人数已满,请选择其他队伍!');
     }
   }
 
-  // 打开意见反馈
+  // 帮助与反馈弹窗状态
+  isFaqModalVisible: boolean = false;
+  isFeedbackModalVisible: boolean = false;
+
+  openFaq(): void {
+    console.log('Opening FAQ');
+    this.isFaqModalVisible = true;
+    this.cdr.detectChanges();
+  }
+
+  closeFaq(): void {
+    this.isFaqModalVisible = false;
+    this.cdr.detectChanges();
+  }
+
   openFeedback(): void {
-    this.router.navigate(['/feedback']);
+    console.log('Opening Feedback');
+    this.isFeedbackModalVisible = true;
+    this.cdr.detectChanges();
   }
 
-  // 查看常见问题
-  viewFAQs(): void {
-    this.router.navigate(['/faqs']);
+  closeFeedback(): void {
+    this.isFeedbackModalVisible = false;
+    this.cdr.detectChanges();
   }
 
   // 格式化评分显示
   formatRating(rating: number): string {
     return '★'.repeat(Math.floor(rating)) + '☆'.repeat(5 - Math.floor(rating));
   }
-}
+
+  // ... existing code ...
+  // 当前选择的餐厅详情
+  selectedDiningDetail: any = null;
+  isDetailOpen: boolean = false;
+
+  // 打开餐厅详情
+  openDiningDetail(option: DiningOption): void {
+    // 模拟更多详情数据
+    this.selectedDiningDetail = {
+      ...option,
+      status: Math.random() > 0.5 ? 'crowded' : 'idle',
+      crowdLevel: Math.floor(Math.random() * 80) + 10, // 10-90%
+      menuItems: [
+        { name: '考伯沙拉', calories: 350, price: 18.0, image: 'https://images.unsplash.com/photo-1546069901-ba9599a7e63c?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60' },
+        { name: '杂粮饭套餐', calories: 500, price: 14.0, image: 'https://images.unsplash.com/photo-1585937421612-70a008356f36?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60' },
+        { name: '低脂鸡胸肉', calories: 200, price: 12.0, image: 'https://images.unsplash.com/photo-1532550907401-a500c9a57435?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60' },
+        { name: '鲜榨果蔬汁', calories: 120, price: 8.0, image: 'https://images.unsplash.com/photo-1610970881699-44a5587cabec?ixlib=rb-1.2.1&auto=format&fit=crop&w=500&q=60' }
+      ]
+    };
+    this.isDetailOpen = true;
+  }
+
+  // 关闭餐厅详情
+  closeDiningDetail(): void {
+    this.isDetailOpen = false;
+    setTimeout(() => {
+      this.selectedDiningDetail = null;
+    }, 300); // 等待动画结束
+  }
+
+  // 导航到餐厅
+  navigateToDining(): void {
+    alert('正在为您规划步行路线...');
+  }
+
+  // 去点餐
+  orderFood(): void {
+    alert('即将跳转到点餐小程序...');
+  }
+}

+ 71 - 0
campus_health_app/frontend/campus-health-app/src/app/modules/school-services/school-services.types.ts

@@ -0,0 +1,71 @@
+export interface DiningOption {
+  id: number;
+  name: string;
+  location: string;
+  distance: string;
+  rating: number;
+  healthyOptions: string[];
+  imageUrl: string;
+}
+
+export interface FoodItem {
+  id: number;
+  name: string;
+  calories: number;
+  protein: number;
+  carbs: number;
+  fat: number;
+  category: string;
+}
+
+export interface ExerciseTeam {
+  id: number;
+  name: string;
+  activity: string;
+  time: string;
+  location: string;
+  currentMembers: number;
+  maxMembers: number;
+  description: string;
+  captain: string;
+}
+
+export interface TrainingPlan {
+  id: number;
+  teamId: number;
+  day: string;
+  date: string;
+  weekday: string;
+  title: string;
+  time: string;
+  location: string;
+  intensity: number;
+  tags: string[];
+  status: 'completed' | 'pending' | 'upcoming';
+}
+
+export interface MyTeam {
+  id: number;
+  name: string;
+  role: 'captain' | 'member';
+}
+
+export interface Venue {
+  id: number;
+  name: string;
+  image: string;
+  price: string;
+  tags: string[];
+  timeSlots: { time: string; status: 'full' | 'available' }[];
+}
+
+export interface MyBooking {
+  id: number;
+  venueId: number;
+  venueName: string;
+  venueImage: string;
+  date: string;
+  time: string;
+  status: 'upcoming' | 'completed' | 'cancelled';
+  price: string;
+}

+ 152 - 5
campus_health_app/frontend/campus-health-app/src/app/modules/user-center/user-center.component.html

@@ -31,31 +31,178 @@
     <!-- 账号设置选项 -->
     <div class="account-settings">
       <h3>账号设置</h3>
-      <div class="setting-item">
+      <div class="setting-item" (click)="changePassword()">
         <span class="setting-icon">🔒</span>
         <span class="setting-text">修改密码</span>
+        <span class="setting-arrow">></span>
       </div>
-      <div class="setting-item">
+      <div class="setting-item" (click)="bindPhone()">
         <span class="setting-icon">📱</span>
         <span class="setting-text">绑定手机</span>
+        <span class="setting-arrow">></span>
       </div>
-      <div class="setting-item">
+      <div class="setting-item" (click)="bindEmail()">
         <span class="setting-icon">📧</span>
         <span class="setting-text">绑定邮箱</span>
+        <span class="setting-arrow">></span>
       </div>
     </div>
 
     <!-- 系统设置选项 -->
     <div class="system-settings">
       <h3>系统设置</h3>
-      <div class="setting-item">
+      <div class="setting-item" (click)="toggleNotifications()">
         <span class="setting-icon">🔔</span>
         <span class="setting-text">消息通知</span>
+        <span class="setting-arrow">></span>
       </div>
-      <div class="setting-item">
+      <div class="setting-item" (click)="showAbout()">
         <span class="setting-icon">ℹ️</span>
         <span class="setting-text">关于我们</span>
+        <span class="setting-arrow">></span>
       </div>
     </div>
   </main>
+
+  <!-- 修改密码模态框 -->
+  <div class="modal-overlay" *ngIf="showPasswordModal" (click)="closeAllModals()">
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>修改密码</h3>
+        <button class="close-btn" (click)="closeAllModals()">×</button>
+      </div>
+      <div class="modal-body">
+        <div class="form-group">
+          <label>当前密码</label>
+          <input type="password" [(ngModel)]="passwordForm.currentPassword" placeholder="请输入当前密码">
+        </div>
+        <div class="form-group">
+          <label>新密码</label>
+          <input type="password" [(ngModel)]="passwordForm.newPassword" placeholder="请输入新密码">
+        </div>
+        <div class="form-group">
+          <label>确认新密码</label>
+          <input type="password" [(ngModel)]="passwordForm.confirmPassword" placeholder="请再次输入新密码">
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn-cancel" (click)="closeAllModals()">取消</button>
+        <button class="btn-confirm" (click)="submitPasswordChange()">确认修改</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 绑定手机模态框 -->
+  <div class="modal-overlay" *ngIf="showPhoneModal" (click)="closeAllModals()">
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>绑定手机</h3>
+        <button class="close-btn" (click)="closeAllModals()">×</button>
+      </div>
+      <div class="modal-body">
+        <div class="form-group">
+          <label>手机号码</label>
+          <input type="tel" [(ngModel)]="phoneForm.phone" placeholder="请输入手机号码" maxlength="11">
+        </div>
+        <div class="form-group">
+          <label>验证码</label>
+          <div class="verification-input">
+            <input type="text" [(ngModel)]="phoneForm.verificationCode" placeholder="请输入验证码">
+            <button class="send-code-btn" (click)="sendPhoneCode()">发送验证码</button>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn-cancel" (click)="closeAllModals()">取消</button>
+        <button class="btn-confirm" (click)="submitPhoneBinding()">确认绑定</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 绑定邮箱模态框 -->
+  <div class="modal-overlay" *ngIf="showEmailModal" (click)="closeAllModals()">
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>绑定邮箱</h3>
+        <button class="close-btn" (click)="closeAllModals()">×</button>
+      </div>
+      <div class="modal-body">
+        <div class="form-group">
+          <label>邮箱地址</label>
+          <input type="email" [(ngModel)]="emailForm.email" placeholder="请输入邮箱地址">
+        </div>
+        <div class="form-group">
+          <label>验证码</label>
+          <div class="verification-input">
+            <input type="text" [(ngModel)]="emailForm.verificationCode" placeholder="请输入验证码">
+            <button class="send-code-btn" (click)="sendEmailCode()">发送验证码</button>
+          </div>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn-cancel" (click)="closeAllModals()">取消</button>
+        <button class="btn-confirm" (click)="submitEmailBinding()">确认绑定</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 消息通知设置模态框 -->
+  <div class="modal-overlay" *ngIf="showNotificationModal" (click)="closeAllModals()">
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>消息通知设置</h3>
+        <button class="close-btn" (click)="closeAllModals()">×</button>
+      </div>
+      <div class="modal-body">
+        <div class="notification-item">
+          <span>运动提醒</span>
+          <label class="switch">
+            <input type="checkbox" [(ngModel)]="notificationSettings.exerciseReminder">
+            <span class="slider"></span>
+          </label>
+        </div>
+        <div class="notification-item">
+          <span>健康小贴士</span>
+          <label class="switch">
+            <input type="checkbox" [(ngModel)]="notificationSettings.healthTips">
+            <span class="slider"></span>
+          </label>
+        </div>
+        <div class="notification-item">
+          <span>系统通知</span>
+          <label class="switch">
+            <input type="checkbox" [(ngModel)]="notificationSettings.systemNotice">
+            <span class="slider"></span>
+          </label>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn-cancel" (click)="closeAllModals()">取消</button>
+        <button class="btn-confirm" (click)="saveNotificationSettings()">保存设置</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- 关于我们模态框 -->
+  <div class="modal-overlay" *ngIf="showAboutModal" (click)="closeAllModals()">
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <div class="modal-header">
+        <h3>关于我们</h3>
+        <button class="close-btn" (click)="closeAllModals()">×</button>
+      </div>
+      <div class="modal-body">
+        <div class="about-content">
+          <h4>校园健康管理系统</h4>
+          <p>版本: v1.0.0</p>
+          <p>开发团队: 虚拟现实VR现代产业学院</p>
+          <p>联系邮箱: support@campus-health.edu.cn</p>
+          <br>
+          <p>本系统致力于为大学生提供全面的健康管理服务,包括运动记录、体重管理、饮食监控等功能。</p>
+        </div>
+      </div>
+      <div class="modal-footer">
+        <button class="btn-confirm" (click)="closeAllModals()">确定</button>
+      </div>
+    </div>
+  </div>
 </div>

+ 250 - 5
campus_health_app/frontend/campus-health-app/src/app/modules/user-center/user-center.component.scss

@@ -3,7 +3,7 @@
 .user-center-container {
   display: flex;
   flex-direction: column;
-  height: 100vh;
+  min-height: 100%;
   background-color: #f5f7fa;
   color: #333;
 }
@@ -13,8 +13,7 @@
 // 主内容区域
 .user-center-main {
   flex: 1;
-  padding: 2rem;
-  overflow-y: auto;
+  padding: 1rem 1rem 2rem;
 }
 
 // 用户资料卡片
@@ -94,10 +93,14 @@
 .setting-item {
   display: flex;
   align-items: center;
-  padding: 0.8rem 0;
+  padding: 1rem 0;
   border-bottom: 1px solid #f1f3f4;
   cursor: pointer;
   transition: background-color 0.2s ease;
+  border-radius: 4px;
+  margin: 0 -0.5rem;
+  padding-left: 0.5rem;
+  padding-right: 0.5rem;
 }
 
 .setting-item:hover {
@@ -109,17 +112,259 @@
 }
 
 .setting-icon {
-  font-size: 1.1rem;
+  font-size: 1.2rem;
   margin-right: 1rem;
   color: #5f6368;
 }
 
 .setting-text {
+  flex: 1;
   font-size: 0.95rem;
   color: #202124;
 }
 
+.setting-arrow {
+  font-size: 1rem;
+  color: #9aa0a6;
+  margin-left: auto;
+}
+
 .setting-status {
   font-size: 0.9rem;
   color: #5f6368;
+}
+
+// 模态框样式
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.modal-content {
+  background-color: #fff;
+  border-radius: 12px;
+  width: 90%;
+  max-width: 400px;
+  max-height: 80vh;
+  overflow-y: auto;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 1.5rem;
+  border-bottom: 1px solid #e8eaed;
+}
+
+.modal-header h3 {
+  margin: 0;
+  font-size: 1.2rem;
+  color: #202124;
+}
+
+.close-btn {
+  background: none;
+  border: none;
+  font-size: 1.5rem;
+  color: #5f6368;
+  cursor: pointer;
+  padding: 0;
+  width: 24px;
+  height: 24px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.close-btn:hover {
+  color: #202124;
+}
+
+.modal-body {
+  padding: 1.5rem;
+}
+
+.modal-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 1rem;
+  padding: 1rem 1.5rem;
+  border-top: 1px solid #e8eaed;
+}
+
+// 表单样式
+.form-group {
+  margin-bottom: 1.5rem;
+}
+
+.form-group label {
+  display: block;
+  margin-bottom: 0.5rem;
+  font-size: 0.9rem;
+  color: #202124;
+  font-weight: 500;
+}
+
+.form-group input {
+  width: 100%;
+  padding: 0.75rem;
+  border: 1px solid #dadce0;
+  border-radius: 6px;
+  font-size: 0.9rem;
+  transition: border-color 0.2s ease;
+  box-sizing: border-box;
+}
+
+.form-group input:focus {
+  outline: none;
+  border-color: #01847F;
+  box-shadow: 0 0 0 2px rgba(1, 132, 127, 0.1);
+}
+
+.verification-input {
+  display: flex;
+  gap: 0.5rem;
+}
+
+.verification-input input {
+  flex: 1;
+}
+
+.send-code-btn {
+  background-color: #01847F;
+  color: white;
+  border: none;
+  border-radius: 6px;
+  padding: 0.75rem 1rem;
+  font-size: 0.85rem;
+  cursor: pointer;
+  white-space: nowrap;
+  transition: background-color 0.2s ease;
+}
+
+.send-code-btn:hover {
+  background-color: #016c68;
+}
+
+// 按钮样式
+.btn-cancel {
+  background-color: #f8f9fa;
+  color: #5f6368;
+  border: 1px solid #dadce0;
+  border-radius: 6px;
+  padding: 0.75rem 1.5rem;
+  font-size: 0.9rem;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.btn-cancel:hover {
+  background-color: #e8eaed;
+}
+
+.btn-confirm {
+  background-color: #01847F;
+  color: white;
+  border: none;
+  border-radius: 6px;
+  padding: 0.75rem 1.5rem;
+  font-size: 0.9rem;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+}
+
+.btn-confirm:hover {
+  background-color: #016c68;
+}
+
+// 通知设置样式
+.notification-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 1rem 0;
+  border-bottom: 1px solid #f1f3f4;
+}
+
+.notification-item:last-child {
+  border-bottom: none;
+}
+
+.notification-item span {
+  font-size: 0.95rem;
+  color: #202124;
+}
+
+// 开关样式
+.switch {
+  position: relative;
+  display: inline-block;
+  width: 44px;
+  height: 24px;
+}
+
+.switch input {
+  opacity: 0;
+  width: 0;
+  height: 0;
+}
+
+.slider {
+  position: absolute;
+  cursor: pointer;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: #ccc;
+  transition: 0.3s;
+  border-radius: 24px;
+}
+
+.slider:before {
+  position: absolute;
+  content: "";
+  height: 18px;
+  width: 18px;
+  left: 3px;
+  bottom: 3px;
+  background-color: white;
+  transition: 0.3s;
+  border-radius: 50%;
+}
+
+input:checked + .slider {
+  background-color: #01847F;
+}
+
+input:checked + .slider:before {
+  transform: translateX(20px);
+}
+
+// 关于我们内容样式
+.about-content {
+  text-align: center;
+}
+
+.about-content h4 {
+  margin: 0 0 1rem 0;
+  color: #01847F;
+  font-size: 1.1rem;
+}
+
+.about-content p {
+  margin: 0.5rem 0;
+  color: #5f6368;
+  font-size: 0.9rem;
+  line-height: 1.5;
 }

+ 143 - 5
campus_health_app/frontend/campus-health-app/src/app/modules/user-center/user-center.component.ts

@@ -1,25 +1,58 @@
 import { Component } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { Router, RouterModule } from '@angular/router';
+import { FormsModule } from '@angular/forms';
 import { ThemeService } from '../../core/services/theme.service';
 import { PageHeaderComponent } from '../../shared/page-header/page-header.component';
 
 @Component({
   selector: 'app-user-center',
   standalone: true,
-  imports: [CommonModule, RouterModule, PageHeaderComponent],
+  imports: [CommonModule, RouterModule, PageHeaderComponent, FormsModule],
   templateUrl: './user-center.component.html',
   styleUrl: './user-center.component.scss'
 })
 export class UserCenterComponent {
   // 用户基本信息
   userInfo = {
-    name: '王刚',
-    studentId: '0235711',
+    name: '郑茹丹',
+    studentId: '0231111',
     college: '虚拟现实VR现代产业学院',
     major: '软件工程(VR)',
-    grade: '2021级',
-    avatar: '👤'
+    grade: '2023级',
+    avatar: '👤',
+    phone: '138****5678',
+    email: 'wang.gang@example.com'
+  };
+
+  // 模态框状态
+  showPasswordModal = false;
+  showPhoneModal = false;
+  showEmailModal = false;
+  showNotificationModal = false;
+  showAboutModal = false;
+
+  // 表单数据
+  passwordForm = {
+    currentPassword: '',
+    newPassword: '',
+    confirmPassword: ''
+  };
+
+  phoneForm = {
+    phone: '',
+    verificationCode: ''
+  };
+
+  emailForm = {
+    email: '',
+    verificationCode: ''
+  };
+
+  notificationSettings = {
+    exerciseReminder: true,
+    healthTips: true,
+    systemNotice: true
   };
 
   constructor(private router: Router, public themeService: ThemeService) {}
@@ -29,4 +62,109 @@ export class UserCenterComponent {
     this.router.navigate(['/dashboard']);
   }
 
+  // 修改密码
+  changePassword(): void {
+    this.showPasswordModal = true;
+  }
+
+  // 绑定手机
+  bindPhone(): void {
+    this.showPhoneModal = true;
+  }
+
+  // 绑定邮箱
+  bindEmail(): void {
+    this.showEmailModal = true;
+  }
+
+  // 消息通知设置
+  toggleNotifications(): void {
+    this.showNotificationModal = true;
+  }
+
+  // 关于我们
+  showAbout(): void {
+    this.showAboutModal = true;
+  }
+
+  // 关闭所有模态框
+  closeAllModals(): void {
+    this.showPasswordModal = false;
+    this.showPhoneModal = false;
+    this.showEmailModal = false;
+    this.showNotificationModal = false;
+    this.showAboutModal = false;
+  }
+
+  // 提交密码修改
+  submitPasswordChange(): void {
+    if (this.passwordForm.newPassword !== this.passwordForm.confirmPassword) {
+      alert('新密码和确认密码不一致');
+      return;
+    }
+    if (this.passwordForm.newPassword.length < 6) {
+      alert('密码长度至少6位');
+      return;
+    }
+    
+    // 前端模拟
+    alert('密码修改成功');
+    this.passwordForm = { currentPassword: '', newPassword: '', confirmPassword: '' };
+    this.closeAllModals();
+  }
+
+  // 发送手机验证码
+  sendPhoneCode(): void {
+    if (!this.phoneForm.phone || this.phoneForm.phone.length !== 11) {
+      alert('请输入正确的手机号码');
+      return;
+    }
+    // 前端模拟
+    alert('验证码已发送到手机');
+  }
+
+  // 提交手机绑定
+  submitPhoneBinding(): void {
+    if (!this.phoneForm.verificationCode) {
+      alert('请输入验证码');
+      return;
+    }
+    
+    // 前端模拟
+    this.userInfo.phone = this.phoneForm.phone;
+    alert('手机绑定成功');
+    this.phoneForm = { phone: '', verificationCode: '' };
+    this.closeAllModals();
+  }
+
+  // 发送邮箱验证码
+  sendEmailCode(): void {
+    if (!this.emailForm.email || !this.emailForm.email.includes('@')) {
+      alert('请输入正确的邮箱地址');
+      return;
+    }
+    // 前端模拟
+    alert('验证码已发送到邮箱');
+  }
+
+  // 提交邮箱绑定
+  submitEmailBinding(): void {
+    if (!this.emailForm.verificationCode) {
+      alert('请输入验证码');
+      return;
+    }
+    
+    // 前端模拟
+    this.userInfo.email = this.emailForm.email;
+    alert('邮箱绑定成功');
+    this.emailForm = { email: '', verificationCode: '' };
+    this.closeAllModals();
+  }
+
+  // 保存通知设置
+  saveNotificationSettings(): void {
+    // 前端模拟
+    alert('通知设置已保存');
+    this.closeAllModals();
+  }
 }