Jelajahi Sumber

训练计划添加

wup55 6 bulan lalu
induk
melakukan
62fbd3e4c9

+ 86 - 75
src/app/tab2/plan-creation-modal.component.html

@@ -1,77 +1,88 @@
 <ion-header>
-    <ion-toolbar>
-      <ion-buttons slot="start">
-        <ion-button (click)="cancel()">
-          <ion-icon slot="icon-only" name="close"></ion-icon>
-        </ion-button>
-      </ion-buttons>
-      <ion-title>创建新计划</ion-title>
-      <ion-buttons slot="end">
-        <ion-button (click)="create()" [disabled]="!newPlan.name">
-          <ion-icon slot="icon-only" name="save"></ion-icon>
-        </ion-button>
-      </ion-buttons>
-    </ion-toolbar>
-  </ion-header>
-  
-  <ion-content class="ion-padding">
-    <ion-item>
-      <ion-input 
-        label="计划名称" 
-        labelPlacement="floating" 
-        placeholder="例如:5公里入门计划"
-        [(ngModel)]="newPlan.name"
-      ></ion-input>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-button (click)="cancel()">
+        <ion-icon slot="icon-only" name="close"></ion-icon>
+      </ion-button>
+    </ion-buttons>
+    <ion-title>创建新计划</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="create()" [disabled]="!newPlan.name">
+        <ion-icon slot="icon-only" name="save"></ion-icon>
+      </ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <ion-item>
+    <ion-input 
+      label="计划名称" 
+      labelPlacement="floating" 
+      placeholder="例如:5公里入门计划"
+      [(ngModel)]="newPlan.name"
+    ></ion-input>
+  </ion-item>
+
+  <ion-item>
+    <ion-textarea 
+      label="计划描述" 
+      labelPlacement="floating" 
+      placeholder="描述你的训练计划"
+      [(ngModel)]="newPlan.description"
+      autoGrow="true"
+    ></ion-textarea>
+  </ion-item>
+
+  <!-- 修复日期选择器绑定 -->
+  <ion-item>
+    <ion-label>开始日期</ion-label>
+    <ion-datetime 
+      presentation="date"
+      [min]="todayDate"
+      [value]="newPlan.startDate"
+      (ionChange)="dateChanged($event)"
+    ></ion-datetime>
+  </ion-item>
+
+  <ion-item>
+    <ion-select 
+      label="持续时间" 
+      labelPlacement="floating"
+      [(ngModel)]="newPlan.duration"
+    >
+      <ion-select-option value="2">2周</ion-select-option>
+      <ion-select-option value="4">4周</ion-select-option>
+      <ion-select-option value="8">8周</ion-select-option>
+      <ion-select-option value="12">12周</ion-select-option>
+    </ion-select>
+  </ion-item>
+
+  <ion-item>
+    <ion-select 
+      label="难度级别" 
+      labelPlacement="floating"
+      [(ngModel)]="newPlan.difficulty"
+    >
+      <ion-select-option value="beginner">初级</ion-select-option>
+      <ion-select-option value="intermediate">中级</ion-select-option>
+      <ion-select-option value="advanced">高级</ion-select-option>
+    </ion-select>
+  </ion-item>
+
+  <ion-item>
+    <ion-label>训练目标</ion-label>
+  </ion-item>
+
+  <div class="goals-container">
+    <ion-item *ngFor="let goal of fitnessGoals">
+      <ion-icon [name]="goal.icon" slot="start" color="primary"></ion-icon>
+      <ion-label>{{ goal.name }}</ion-label>
+      <ion-checkbox 
+        slot="end" 
+        [checked]="newPlan.goals.includes(goal.value)"
+        (ionChange)="toggleGoal(goal.value)"
+      ></ion-checkbox>
     </ion-item>
-  
-    <ion-item>
-      <ion-textarea 
-        label="计划描述" 
-        labelPlacement="floating" 
-        placeholder="描述你的训练计划"
-        [(ngModel)]="newPlan.description"
-        autoGrow="true"
-      ></ion-textarea>
-    </ion-item>
-  
-    <ion-item>
-      <ion-select 
-        label="持续时间" 
-        labelPlacement="floating"
-        [(ngModel)]="newPlan.duration"
-      >
-        <ion-select-option value="2">2周</ion-select-option>
-        <ion-select-option value="4">4周</ion-select-option>
-        <ion-select-option value="8">8周</ion-select-option>
-        <ion-select-option value="12">12周</ion-select-option>
-      </ion-select>
-    </ion-item>
-  
-    <ion-item>
-      <ion-select 
-        label="难度级别" 
-        labelPlacement="floating"
-        [(ngModel)]="newPlan.difficulty"
-      >
-        <ion-select-option value="beginner">初级</ion-select-option>
-        <ion-select-option value="intermediate">中级</ion-select-option>
-        <ion-select-option value="advanced">高级</ion-select-option>
-      </ion-select>
-    </ion-item>
-  
-    <ion-item>
-      <ion-label>训练目标</ion-label>
-    </ion-item>
-  
-    <div class="goals-container">
-      <ion-item *ngFor="let goal of fitnessGoals">
-        <ion-icon [name]="goal.icon" slot="start" color="primary"></ion-icon>
-        <ion-label>{{ goal.name }}</ion-label>
-        <ion-checkbox 
-          slot="end" 
-          [checked]="newPlan.goals.includes(goal.value)"
-          (ionChange)="toggleGoal(goal.value)"
-        ></ion-checkbox>
-      </ion-item>
-    </div>
-  </ion-content>
+  </div>
+</ion-content>

+ 41 - 16
src/app/tab2/plan-creation-modal.component.ts

@@ -1,12 +1,26 @@
+import { CommonModule } from '@angular/common';
 import { Component } from '@angular/core';
-import { FormsModule } from '@angular/forms'; // 关键修复
+import { FormsModule } from '@angular/forms';
 import { ModalController } from '@ionic/angular/standalone';
-import { IonHeader, IonToolbar, IonTitle, IonButtons, 
-         IonButton, IonIcon, IonContent, IonItem, 
-         IonInput, IonTextarea, IonSelect, IonSelectOption,
-         IonLabel, IonCheckbox } from '@ionic/angular/standalone';
+import { 
+  IonHeader, IonToolbar, IonTitle, IonButtons, 
+  IonButton, IonIcon, IonContent, IonItem, 
+  IonInput, IonTextarea, IonSelect, IonSelectOption,
+  IonLabel, IonCheckbox, IonDatetime
+} from '@ionic/angular/standalone';
 import { addIcons } from 'ionicons';
-import { close, save, barbell, body, walk, flame, bicycle } from 'ionicons/icons';
+import { close, save, barbell, body, walk, flame, bicycle, calendar } from 'ionicons/icons';
+
+// 定义新计划对象的接口
+interface NewPlan {
+  name: string;
+  description: string;
+  duration: number;
+  difficulty: string;
+  goals: string[];
+  icon: string;
+  startDate: string; // 确保包含这个属性
+}
 
 @Component({
   selector: 'app-plan-creation-modal',
@@ -14,26 +28,38 @@ import { close, save, barbell, body, walk, flame, bicycle } from 'ionicons/icons
   styleUrls: ['./plan-creation-modal.component.scss'],
   standalone: true,
   imports: [
-    // 添加 FormsModule 以支持 ngModel
+    CommonModule,
     FormsModule,
-    
-    // Ionic组件
     IonHeader, IonToolbar, IonTitle, IonButtons, IonButton, IonIcon,
     IonContent, IonItem, IonInput, IonTextarea, IonSelect, IonSelectOption,
-    IonLabel, IonCheckbox
+    IonLabel, IonCheckbox, IonDatetime
   ]
 })
 export class PlanCreationModalComponent {
-  // 修复类型问题:明确指定goals为字符串数组
-  newPlan = {
+  // 使用接口定义 newPlan
+  newPlan: NewPlan = {
     name: '',
     description: '',
     duration: 4,
     difficulty: 'beginner',
-    goals: [] as string[], // 明确指定为字符串数组
-    icon: 'fitness'
+    goals: [] as string[],
+    icon: 'fitness',
+    startDate: new Date().toISOString().split('T')[0] // 只保留日期部分
   };
 
+  // 获取当前日期字符串 (YYYY-MM-DD)
+  get todayDate(): string {
+    return new Date().toISOString().split('T')[0];
+  }
+
+  // 日期变更处理方法
+  dateChanged(event: any) {
+    const value = event?.detail?.value;
+    if (value) {
+      this.newPlan.startDate = value.split('T')[0]; // 确保只保存日期部分
+    }
+  }
+
   fitnessGoals = [
     { name: '跑步', value: 'running', icon: 'walk' },
     { name: '减脂', value: 'fat_loss', icon: 'flame' },
@@ -43,7 +69,7 @@ export class PlanCreationModalComponent {
   ];
 
   constructor(private modalCtrl: ModalController) {
-    addIcons({ close, save, barbell, body, walk, flame, bicycle });
+    addIcons({ close, save, barbell, body, walk, flame, bicycle, calendar });
   }
 
   cancel() {
@@ -54,7 +80,6 @@ export class PlanCreationModalComponent {
     this.modalCtrl.dismiss(this.newPlan, 'confirm');
   }
 
-  // 修复类型问题:明确goal为字符串
   toggleGoal(goal: string) {
     const index = this.newPlan.goals.indexOf(goal);
     if (index > -1) {

+ 94 - 53
src/app/tab2/tab2.page.html

@@ -1,23 +1,27 @@
 <ion-header [translucent]="true">
-  <ion-toolbar>
-    <ion-title>训练计划</ion-title>
+  <ion-toolbar class="toolbar-gradient">
+    <ion-title class="header-title">我的训练计划</ion-title>
     <ion-buttons slot="end">
-      <ion-button>
-        <ion-icon name="notifications-outline" slot="icon-only"></ion-icon>
+      <ion-button class="icon-btn">
+        <ion-icon name="notifications-outline" slot="icon-only" color="light"></ion-icon>
       </ion-button>
     </ion-buttons>
   </ion-toolbar>
 </ion-header>
 
-<ion-content [fullscreen]="true">
+<ion-content [fullscreen]="true" class="content-bg">
 
   <!-- 加载状态 -->
   <div *ngIf="isLoading" class="loading-container">
-    <ion-skeleton-text animated style="width: 80%; height: 30px;"></ion-skeleton-text>
-    <ion-skeleton-text animated style="width: 60%; height: 20px;"></ion-skeleton-text>
-    <ion-list>
-      <ion-item *ngFor="let item of [1,2,3]">
-        <ion-avatar slot="start">
+    <div class="skeleton-card">
+      <ion-skeleton-text animated style="width: 80%; height: 30px;"></ion-skeleton-text>
+      <ion-skeleton-text animated style="width: 60%; height: 20px; margin-top: 10px;"></ion-skeleton-text>
+    </div>
+    
+    <div class="skeleton-card" style="margin-top: 20px;">
+      <ion-skeleton-text animated style="width: 100%; height: 20px; margin-bottom: 15px;"></ion-skeleton-text>
+      <ion-item *ngFor="let item of [1,2,3]" class="skeleton-item">
+        <ion-avatar slot="start" class="skeleton-avatar">
           <ion-skeleton-text animated></ion-skeleton-text>
         </ion-avatar>
         <ion-label>
@@ -25,35 +29,39 @@
           <p><ion-skeleton-text animated style="width: 80%"></ion-skeleton-text></p>
         </ion-label>
       </ion-item>
-    </ion-list>
+    </div>
   </div>
 
   <!-- 内容区域 -->
-  <div *ngIf="!isLoading">
+  <div *ngIf="!isLoading" class="content-area">
     <!-- 本周训练概览 -->
-    <ion-card class="weekly-card">
-      <ion-card-header>
-        <ion-card-title>
-          <ion-icon name="calendar-number"></ion-icon>
+    <ion-card class="weekly-card glass-card">
+      <ion-card-header class="card-header-primary">
+        <ion-card-title class="card-title">
+          <ion-icon name="calendar-number" class="card-icon"></ion-icon>
           本周训练
         </ion-card-title>
         <ion-card-subtitle>已完成 {{completedWorkouts}}/{{totalWorkouts}} 次训练</ion-card-subtitle>
       </ion-card-header>
 
-      <ion-card-content>
+      <ion-card-content class="card-content">
         <div class="progress-container">
+          <div class="progress-header">
+            <span>完成进度</span>
+            <span>{{completionRate * 100 | number:'1.0-0'}}%</span>
+          </div>
           <ion-progress-bar [value]="completionRate" color="primary"></ion-progress-bar>
-          <div class="progress-text">{{completionRate * 100 | number:'1.0-0'}}% 完成度</div>
         </div>
 
         <ion-grid class="day-grid">
-          <ion-row>
-            <ion-col *ngFor="let day of weekDays" [class.active]="day.active">
+          <ion-row class="day-row">
+            <ion-col *ngFor="let day of weekDays" [class.active]="day.active" class="day-col">
               <div class="day-name">{{day.shortName}}</div>
-              <ion-icon
-                [name]="day.trained ? 'checkmark-circle' : 'ellipse-outline'"
-                [color]="day.trained ? 'success' : 'medium'">
-              </ion-icon>
+              <ion-badge *ngIf="day.active" color="primary">
+                <ion-icon [name]="day.trained ? 'checkmark-circle' : 'ellipse-outline'"></ion-icon>
+              </ion-badge>
+              <ion-icon *ngIf="!day.active" [name]="day.trained ? 'checkmark-circle' : 'ellipse-outline'"
+                [color]="day.trained ? 'success' : 'medium'"></ion-icon>
             </ion-col>
           </ion-row>
         </ion-grid>
@@ -61,56 +69,84 @@
     </ion-card>
 
     <!-- 我的计划列表 -->
-    <ion-list-header>
-      <ion-label>我的计划</ion-label>
-      <ion-button fill="clear" size="small" (click)="refreshPlans()">
-        <ion-icon name="refresh" slot="start"></ion-icon>
-        刷新
-      </ion-button>
+    <ion-list-header class="section-header">
+      <div class="header-content">
+        <div>
+          <ion-label class="section-label">我的计划</ion-label>
+          <ion-note>当前计划数量: {{myPlans.length}}</ion-note>
+        </div>
+        <ion-button fill="clear" size="small" class="refresh-btn" (click)="refreshPlans()">
+          <ion-icon name="refresh" slot="start"></ion-icon>
+          刷新
+        </ion-button>
+      </div>
     </ion-list-header>
 
-    <ion-list lines="none">
-      <ion-item *ngFor="let item of myPlans" [detail]="true" (click)="openPlan(item)">
-        <ion-avatar slot="start">
-          <ion-icon [name]="item.icon" color="primary"></ion-icon>
-        </ion-avatar>
-        <ion-label>
-          <h3>{{item.name}}</h3>
-          <p>{{item.progress}}% 完成 · 下次训练: {{item.nextTime}}</p>
+    <ion-list class="plan-list" lines="none">
+      <ion-item *ngFor="let item of myPlans" [detail]="true" (click)="openPlan(item)" class="plan-item">
+        <div class="plan-icon" [style.background]="getPlanColor(item.difficultyLevel)">
+          <ion-icon [name]="item.icon" color="light"></ion-icon>
+        </div>
+        <ion-label class="plan-details">
+          <div class="plan-header">
+            <h3>{{item.name}}</h3>
+            <ion-badge [color]="getBadgeColor(item.difficultyLevel)" class="difficulty-badge">
+              {{getDifficultyText(item.difficultyLevel)}}
+            </ion-badge>
+          </div>
+          <div class="plan-stats">
+            <span class="progress-tag">
+              <ion-icon name="bar-chart-outline"></ion-icon>
+              {{item.progress}}% 完成
+            </span>
+            <span class="next-tag">
+              <ion-icon name="time-outline"></ion-icon>
+              下次: {{item.nextTime}}
+            </span>
+          </div>
           <p *ngIf="item.description" class="plan-description">{{item.description}}</p>
           
           <!-- 任务摘要 -->
           <div class="task-summary">
-            <ion-chip *ngFor="let task of getPlanTasks(item.id)" color="light">
+            <ion-chip *ngFor="let task of getPlanTasks(item.id)" [color]="getChipColor(item.difficultyLevel)">
               <ion-icon name="footsteps" slot="start"></ion-icon>
               <ion-label>{{task.get('taskName')}}</ion-label>
             </ion-chip>
           </div>
         </ion-label>
-        <ion-badge slot="end" color="light">{{item.remaining}}天</ion-badge>
+        <ion-badge slot="end" class="days-badge" [color]="item.remaining < 5 ? 'danger' : 'success'">
+          {{item.remaining}}天
+        </ion-badge>
       </ion-item>
     </ion-list>
 
     <div class="empty-state" *ngIf="!isLoading && myPlans.length === 0">
-  
+      <div class="empty-icon">
+        <ion-icon name="calendar-clear-outline"></ion-icon>
+      </div>
       <h3>还没有训练计划</h3>
-      <p>创建一个个性化计划开始你的跑步旅程</p>
-      <ion-button expand="block" fill="solid" color="primary" (click)="createNewPlan()">
+      <p>创建个性化计划,开启您的健身旅程</p>
+      <ion-button expand="block" fill="solid" color="primary" class="create-btn" (click)="createNewPlan()">
         <ion-icon slot="start" name="add"></ion-icon>
         创建新计划
       </ion-button>
     </div>
-
- </div>
+  </div>
+  
   <!-- AI咨询 -->
-  <ion-list-header>
-    <ion-label>私教咨询(免费)</ion-label>
+  <ion-list-header class="section-header">
+    <ion-label class="section-label">私教咨询(免费)</ion-label>
+    <ion-note>专业建议,助您突破平台期</ion-note>
   </ion-list-header>
 
-  <ion-card class="coach-card running-theme">
+  <ion-card class="coach-card running-theme glass-card">
       <div class="avatar-container">
         <img src="/assets/avatars/R-C.jpg" alt="跑步教练" class="coach-avatar" />
         <div class="sport-badge">跑步专家</div>
+        <div class="rating-badge">
+          <ion-icon name="star" color="warning"></ion-icon>
+          <ion-label>4.9</ion-label>
+        </div>
       </div>
 
       <ion-card-header>
@@ -128,12 +164,17 @@
             <div class="stat-value">50+</div>
             <div class="stat-label">学员PB</div>
           </div>
+          <div class="stat-item">
+            <div class="stat-value">5000+</div>
+            <div class="stat-label">学员好评</div>
+          </div>
         </div>
         
         <div class="specialties-section">
-          <ion-note color="medium" class="section-title">擅长运动</ion-note>
+          <ion-note color="medium" class="section-title">擅长领域</ion-note>
           <div class="specialties-container">
             <ion-chip *ngFor="let specialty of ['长跑', '间歇训练', '跑姿矫正']" class="specialty-chip">
+              <ion-icon name="checkmark-circle-outline" slot="start"></ion-icon>
               <ion-label>{{ specialty }}</ion-label>
             </ion-chip>
           </div>
@@ -141,15 +182,15 @@
 
         <ion-button (click)="openConsult()" expand="block" shape="round" class="consult-btn">
           <ion-icon name="chatbubble-ellipses" slot="start"></ion-icon>
-          咨询
+          立即咨询
         </ion-button>
       </ion-card-content>
     </ion-card>
 
   <!-- 底部添加按钮 -->
-  <ion-fab vertical="bottom" horizontal="end" slot="fixed">
-    <ion-fab-button (click)="createNewPlan()">
-      <ion-icon name="add"></ion-icon>
+  <ion-fab vertical="bottom" horizontal="end" slot="fixed" class="fab-container">
+    <ion-fab-button (click)="createNewPlan()" class="main-fab">
+      <ion-icon name="add" class="fab-icon"></ion-icon>
     </ion-fab-button>
   </ion-fab>
 </ion-content>

+ 429 - 248
src/app/tab2/tab2.page.scss

@@ -1,347 +1,528 @@
+/* Tab2 页面美化 */
+ion-content.content-bg {
+  --background: #f8f9fa;
+  background: linear-gradient(180deg, #e3f2fd 0%, #f8f9fa 100%);
+}
 
-// 教练卡片
-/* coach-card.component.scss */
-.coach-card {
-  padding-top: 60px;
-  max-width: 350px;
-  margin: 16px auto;
-  border-radius: 16px;
-  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
-  overflow: visible;
+/* 工具栏渐变样式 */
+.toolbar-gradient {
+  --background: linear-gradient(to right, #3a7bd5, #00d2ff);
+  --color: white;
   
-  .avatar-container {
-    display: flex;
-    justify-content: center;
-    margin-top: -50px;
-  }
-  
-  .coach-avatar {
-    width: 100px;
-    height: 100px;
-    border-radius: 50%;
-    object-fit: cover;
-    border: 4px solid white;
-    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  ion-title.header-title {
+    font-weight: 600;
+    letter-spacing: 0.5px;
+    text-align: center;
   }
   
-  .coach-name {
-    text-align: center;
-    font-weight: bold;
-    font-size: 1.5rem;
-    margin-top: 8px;
-    color: #2c3e50;
+  .icon-btn {
+    --padding-end: 0;
+    --padding-start: 0;
   }
+}
+
+/* 卡片玻璃效果 */
+.glass-card {
+  border-radius: 16px;
+  box-shadow: 0 8px 32px rgba(31, 38, 135, 0.1);
+  background: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(10px);
+  border: 1px solid rgba(255, 255, 255, 0.5);
+  overflow: visible;
+}
+
+/* 加载状态美化 */
+.loading-container {
+  padding: 16px;
   
-  .coach-title {
-    text-align: center;
-    font-size: 0.9rem;
-    color: #7f8c8d;
+  .skeleton-card {
+    background: white;
+    border-radius: 16px;
+    padding: 16px;
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
+    margin-bottom: 16px;
   }
   
-  .specialties-section {
-    margin: 20px 0;
-    
-    .section-title {
-      display: block;
-      margin-bottom: 8px;
-      font-size: 0.9rem;
-    }
-    
-    .specialties-container {
-      display: flex;
-      flex-wrap: wrap;
-      gap: 8px;
-      justify-content: center;
-    }
+  .skeleton-item {
+    --padding-start: 0;
+    --inner-padding-end: 0;
     
-    .specialty-chip {
-      --background: #f1f5f9;
-      --color: #334155;
+    .skeleton-avatar {
+      width: 50px;
+      height: 50px;
+      border-radius: 12px;
     }
   }
-  
-  .consult-btn {
-    --background: linear-gradient(135deg, #3b82f6, #6366f1);
-    --background-hover: #4f46e5;
-    --background-activated: #4f46e5;
-    --color: white;
-    margin-top: 16px;
-    font-weight: 600;
-  }
 }
 
-/* 全局卡片样式 */
-ion-card {
-  --background: #ffffff;
-  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
-  border-radius: 12px;
-  margin: 16px;
+/* 内容区域统一间距 */
+.content-area {
+  padding: 12px;
 }
 
 /* 本周训练卡片 */
 .weekly-card {
-  ion-card-header {
-    padding-bottom: 8px;
+  margin-bottom: 24px;
+  background: rgba(255, 255, 255, 0.92); // 半透明白色背景
+  .card-header-primary {
+    background: linear-gradient(to right, #4b6cb7, #182848);
+    color: white;
+    border-radius: 16px 16px 0 0;
+    padding: 16px;
     
-    ion-card-title {
-      font-size: 18px;
+    .card-title {
       display: flex;
       align-items: center;
-      
-      ion-icon {
-        margin-right: 8px;
-        font-size: 20px;
-        color: var(--ion-color-primary);
-      }
+      font-size: 1.3rem;
+      font-weight: 600;
+      letter-spacing: 0.5px;
+    }
+    
+    .card-icon {
+      margin-right: 10px;
+      font-size: 1.6rem;
     }
     
     ion-card-subtitle {
-      font-size: 14px;
-      color: var(--ion-color-medium);
+      color: rgba(255, 255, 255, 0.8);
+      font-size: 0.9rem;
     }
   }
   
   .progress-container {
-    margin: 12px 0;
+    margin-bottom: 20px;
+    
+    .progress-header {
+      display: flex;
+      justify-content: space-between;
+      margin-bottom: 6px;
+      font-size: 0.9rem;
+      color: #5a5a5a;
+      font-weight: 500;
+    }
     
     .progress-text {
+      margin-top: 8px;
       text-align: right;
-      font-size: 12px;
-      color: var(--ion-color-medium);
-      margin-top: 4px;
+      font-size: 0.85rem;
+      font-weight: 500;
+      color: #3a7bd5;
     }
   }
   
   .day-grid {
-    padding: 0;
+    margin-top: 15px;
     
-    ion-col {
-      text-align: center;
-      padding: 8px 4px;
-      
-      .day-name {
-        font-size: 12px;
-        color: var(--ion-color-medium);
-        margin-bottom: 4px;
-      }
-      
-      ion-icon {
-        font-size: 20px;
-      }
-      
-      &.active {
-        .day-name {
-          color: var(--ion-color-primary);
-          font-weight: bold;
-        }
-      }
+    .day-row {
+      justify-content: space-between;
     }
-  }
-}
-
-/* 推荐计划 */
-.plan-slides {
-  display: flex;
-  overflow-x: auto;
-  padding: 0 16px 16px;
-  
-  .plan-card {
-    min-width: 200px;
-    margin-right: 16px;
     
-    .plan-icon-container {
+    .day-col {
       display: flex;
-      justify-content: center;
-      padding: 16px;
-      
-      .plan-icon {
-        font-size: 48px;
-        color: var(--ion-color-primary);
-        padding: 12px;
-        background: rgba(var(--ion-color-primary-rgb), 0.1);
-        border-radius: 50%;
-      }
-    }
-    
-    ion-card-header {
-      padding: 0 12px 12px;
+      flex-direction: column;
+      align-items: center;
+      padding: 8px 5px;
       
-      ion-badge {
-        position: absolute;
-        top: 10px;
-        right: 10px;
+      &.active {
+        background: rgba(58, 123, 213, 0.1);
+        border-radius: 12px;
       }
       
-      ion-card-title {
-        font-size: 16px;
-        margin-bottom: 4px;
+      .day-name {
+        font-size: 1rem;
+        font-weight: 500;
+        margin-bottom: 8px;
+        color: #3a3a3a;
       }
       
-      ion-card-subtitle {
-        font-size: 12px;
-        display: flex;
-        align-items: center;
+      ion-badge {
+        padding: 8px;
+        border-radius: 50%;
         
         ion-icon {
-          margin-right: 4px;
-          font-size: 14px;
+          font-size: 1.4rem;
         }
       }
+      
+      ion-icon {
+        font-size: 1.5rem;
+      }
     }
   }
 }
 
-/* 我的计划列表 */
-ion-list {
+/* 节标题美化 */
+.section-header {
+  margin: 24px 0 16px;
+  
+  .section-label {
+    font-size: 1.2rem;
+    font-weight: 600;
+    color: #1a237e; // 深蓝色
+    font-weight: 600;
+    display: block;
+    margin-bottom: 4px;
+  }
+  
+  .header-content {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+  }
+  
+  ion-note {
+    font-size: 0.9rem;
+    color: #455a64; // 深灰蓝色
+  }
+  
+  .refresh-btn {
+    --color: #3a7bd5;
+    --background-hover: rgba(58, 123, 213, 0.1);
+  }
+}
+
+/* 计划列表美化 */
+.plan-list {
   background: transparent;
-  padding: 0;
   
-  ion-item {
-    --padding-start: 16px;
-    --padding-end: 16px;
+  .plan-item {
+    --padding-start: 0;
     --inner-padding-end: 0;
-    --background: #ffffff;
-    margin-bottom: 8px;
+    --inner-padding-top: 16px;
+    --inner-padding-bottom: 16px;
+    margin-bottom: 16px;
+    background:  rgba(58, 123, 213, 0.15);;
+    border-radius: 16px;
+    overflow: visible;
     
-    ion-avatar {
-      width: 40px;
-      height: 40px;
-      background: rgba(var(--ion-color-primary-rgb), 0.1);
+    .plan-icon {
+      width: 50px;
+      height: 50px;
+      border-radius: 12px;
       display: flex;
       align-items: center;
       justify-content: center;
+      margin-left: 16px;
+      margin-right: 16px;
       
       ion-icon {
-        font-size: 20px;
+        font-size: 1.5rem;
       }
     }
     
-    ion-label {
-      h3 {
-        font-weight: 500;
-        margin-bottom: 4px;
+    .plan-details {
+      padding-right: 8px;
+      
+      .plan-header {
+        display: flex;
+        align-items: center;
+        margin-bottom: 8px;
+        
+        h3 {
+          font-size: 1.1rem;
+          font-weight: 600;
+          margin-right: 10px;
+          margin-top: 0;
+          margin-bottom: 0;
+          color: #00a0fd;  
+          font-weight: 700; // 加粗增强可读性
+        }
+        
+        .difficulty-badge {
+          font-size: 0.7rem;
+          font-weight: 600;
+          letter-spacing: 0.5px;
+          padding: 4px 8px;
+        }
       }
       
-      p {
-        font-size: 12px;
-        color: var(--ion-color-medium);
+      .plan-stats {
+        display: flex;
+        margin-bottom: 8px;
+        
+        .progress-tag, .next-tag {
+          display: flex;
+          align-items: center;
+          font-weight: 500;
+          background: rgba(58, 123, 213, 0.15); // 更明显的背景色
+          border-radius: 12px;
+          padding: 4px 8px;
+          font-size: 0.85rem;
+          color: #444; // 深灰色
+          margin-right: 10px;
+          
+          ion-icon {
+            margin-right: 4px;
+            font-size: 0.9rem;
+          }
+        }
+      }
+      
+      .plan-description {
+        color: #555; // 加深描述文字颜色
+        font-size: 0.95rem;
+        line-height: 1.4;
+        margin-top: 8px;
+        margin-bottom: 8px;
+      }
+      .plan-details > * {
+        text-shadow: 0 1px 1px rgba(255, 255, 255, 0.8);
+      .task-summary {
+        margin-top: 10px;
+        
+        ion-chip {
+          margin: 0 6px 6px 0;
+          font-size: 0.8rem;
+          padding: 4px 12px;
+          border-radius: 50px;
+          --color: #333; // 芯片文字颜色加深
+          font-weight: 500; // 加粗
+          ion-icon {
+            font-size: 0.9rem;
+            margin-right: 4px;
+          }
+        }
       }
     }
-    
-    ion-badge {
-      font-weight: normal;
+  }
+    .days-badge {
+      margin-right: 16px;
+      font-weight: 600;
+      font-size: 1rem;
+      padding: 8px 12px;
+      border-radius: 50px;
+      min-width: 50px;
+      text-align: center;
+      --color: white; // 文字改为白色确保可读
+      font-weight: 600;
     }
   }
 }
 
-/* 空状态 */
+/* 空状态美化 */
 .empty-state {
   text-align: center;
   padding: 40px 20px;
   
-  ion-icon {
-    font-size: 48px;
-    color: var(--ion-color-medium);
-    margin-bottom: 16px;
+  .empty-icon {
+    width: 80px;
+    height: 80px;
+    background: rgba(58, 123, 213, 0.1);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 0 auto 20px;
+    
+    ion-icon {
+      font-size: 2.5rem;
+      color: #3a7bd5;
+    }
+  }
+  
+  h3 {
+    font-size: 1.3rem;
+    color: #212121; // 加深标题
+    margin-bottom: 8px;
   }
   
   p {
-    color: var(--ion-color-medium);
+    color: #555; // 加深描述
+    font-size: 1rem;
     margin-bottom: 20px;
+    max-width: 300px;
+    margin-left: auto;
+    margin-right: auto;
+  }
+  
+  .create-btn {
+    max-width: 280px;
+    margin: 0 auto;
+    font-weight: 600;
+    border-radius: 12px;
+    --box-shadow: 0 4px 16px rgba(58, 123, 213, 0.3);
   }
-}
-.deepseek-input-container {
-  padding: 0 16px 16px;
-}
-
-.deepseek-input {
-  display: flex;
-  align-items: center;
-  border: 1px solid #e5e7eb;
-  border-radius: 12px;
-  padding: 8px 12px;
-  background: white;
-  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
-}
-
-.deepseek-textarea {
-  flex: 1;
-  --background: transparent;
-  font-size: 14px;
-  --padding-start: 0;
-  --padding-end: 0;
 }
 
-.send-button {
-  --padding-start: 8px;
-  --padding-end: 8px;
-  --color: #6b47d6;
-}
-@media (max-width: 768px) {
-  .coach-card {
-    max-width: 100%;
-    margin: 10px;
+/* 教练卡片美化 */
+.coach-card.running-theme {
+  background: linear-gradient(to bottom right, #4b6cb7, #182848);
+  color: white;
+  border-radius: 16px;
+  
+  .avatar-container {
+    position: relative;
+    
+    .coach-avatar {
+      width: 100%;
+      height: 160px;
+      object-fit: cover;
+      border-radius: 16px 16px 0 0;
+    }
+    
+    .sport-badge {
+      position: absolute;
+      top: 16px;
+      right: 16px;
+      background: rgba(255, 255, 255, 0.9);
+      color: #3a7bd5;
+      padding: 4px 12px;
+      border-radius: 30px;
+      font-weight: 600;
+      font-size: 0.9rem;
+    }
+    
+    .rating-badge {
+      position: absolute;
+      bottom: 16px;
+      right: 16px;
+      display: flex;
+      align-items: center;
+      background: rgba(255, 255, 255, 0.9);
+      color: #f39c12;
+      padding: 4px 12px;
+      border-radius: 30px;
+      font-weight: 600;
+      font-size: 0.9rem;
+      
+      ion-icon {
+        margin-right: 4px;
+      }
+    }
   }
   
-  .stats-grid {
-    flex-direction: column;
-    gap: 10px;
+  ion-card-header {
+    padding: 16px;
+    
+    .coach-name {
+      font-size: 1.4rem;
+      font-weight: 700;
+    }
+    
+    .coach-title {
+      font-size: 1rem;
+      color: #ddd;
+      font-weight: 500;
+    }
   }
-}
-
-/* 任务摘要样式 */
-.task-summary {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 8px;
-  margin-top: 10px;
   
-  ion-chip {
-    --background: #f1f5f9;
-    --color: #334155;
-    font-size: 0.8rem;
-    height: 24px;
+  ion-card-content {
+    padding: 0 16px 20px;
     
-    ion-icon {
-      color: #1890ff;
-      font-size: 14px;
+    .stats-grid {
+      display: flex;
+      justify-content: space-between;
+      margin: 20px 0;
+      
+      .stat-item {
+        text-align: center;
+        flex: 1;
+        
+        .stat-value {
+          font-size: 1.5rem;
+          font-weight: 700;
+          margin-bottom: 4px;
+        }
+        
+        .stat-label {
+          font-size: 0.85rem;
+          opacity: 0.9;
+        }
+      }
+    }
+    
+    .specialties-section {
+      margin: 20px 0;
+      
+      .section-title {
+        font-size: 1rem;
+        display: block;
+        margin-bottom: 10px;
+        opacity: 0.8;
+      }
+      
+      .specialties-container {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+        
+        .specialty-chip {
+          background: rgba(255, 255, 255, 0.2);
+          color: white;
+          font-size: 0.85rem;
+          
+          ion-icon {
+            color: #00d2ff;
+          }
+        }
+      }
+    }
+    
+    .consult-btn {
+      --background: white;
+      --color: #3a7bd5;
+      --box-shadow: none;
+      font-weight: 600;
+      margin-top: 10px;
+      --padding-top: 15px;
+      --padding-bottom: 15px;
+      --border-radius: 12px;
+      
+      ion-icon {
+        color: #3a7bd5;
+      }
     }
   }
 }
-
-/* 计划描述 */
-.plan-description {
-  font-size: 0.9rem;
-  color: #666;
-  margin: 6px 0;
-}
-
-.empty-state {
-  text-align: center;
-  padding: 40px 20px;
+/* 添加新的卡片样式类 */
+.card-content-box {
+  background: #ffffff;  // 纯白背景确保对比度
+  border-radius: 16px;
+  padding: 16px;
+  margin-bottom: 16px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
   
-  .empty-image {
-    width: 150px;
-    height: 150px;
-    margin-bottom: 20px;
-    opacity: 0.7;
+  .plan-header h3 {
+    color: #1a237e;  // 深蓝色标题
+    font-size: 1.2rem;
+    font-weight: 700;
+    margin-bottom: 8px;
   }
+}
+/* FAB 按钮美化 */
+.fab-container {
+  margin-bottom: 20px;
+  margin-right: 20px;
   
-  h3 {
-    font-size: 1.2rem;
-    font-weight: 600;
-    color: #333;
-    margin-bottom: 10px;
+  .main-fab {
+    --background: linear-gradient(to right, #3a7bd5, #00d2ff);
+    --background-activated: linear-gradient(to right, #2a5ba5, #00b2e0);
+    --border-radius: 16px;
+    width: 60px;
+    height: 60px;
+    box-shadow: 0 6px 20px rgba(58, 123, 213, 0.4);
+    
+    .fab-icon {
+      font-size: 1.8rem;
+    }
+  }
+}
+
+/* 不同难度等级的颜色 */
+.plan-item .difficulty-badge {
+  &.beginner-badge {
+    --background: #27ae60;
+    --color: white;
   }
   
-  p {
-    color: #666;
-    margin-bottom: 25px;
-    font-size: 0.95rem;
+  &.intermediate-badge {
+    --background: #f39c12;
+    --color: white;
   }
   
-  ion-button {
-    max-width: 300px;
-    margin: 0 auto;
+  &.advanced-badge {
+    --background: #e74c3c;
+    --color: white;
   }
 }

+ 94 - 47
src/app/tab2/tab2.page.ts

@@ -11,13 +11,14 @@ import { addIcons } from 'ionicons';
 import {
   notificationsOutline, calendarNumber, checkmarkCircle, ellipseOutline,
   timeOutline, barbell, body, walk, add, refresh, fitness,
-  flame, bicycle, trophy, ellipsisHorizontal, footsteps, footstepsOutline, chatbubbleEllipses } from 'ionicons/icons';
+  flame, bicycle, trophy, ellipsisHorizontal, footsteps, footstepsOutline, chatbubbleEllipses, barChartOutline, calendarClearOutline, star, checkmarkCircleOutline } from 'ionicons/icons';
 
 // 引用fmode-ng智能体组件
 import { ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
 import Parse from "parse";
 import { CloudObject, CloudQuery, CloudUser } from 'src/lib/ncloud';
 
+
 // 导入计划创建模态框组件
 import { PlanCreationModalComponent } from './plan-creation-modal.component';
 
@@ -42,27 +43,27 @@ export class Tab2Page {
     let options: ChatPanelOptions = {
       roleId: "2DXJkRsjXK", // 预设,无需更改
       onChatInit: (chat: FmodeChat) => {
-        // 更新为跑步教练信息
+       
         chat.role.set("name", "陈驰");
         chat.role.set("title", "跑步教练");
         chat.role.set("desc", "前马拉松运动员,10年跑步训练经验");
         chat.role.set("tags", ['长跑', '间歇训练', '跑姿矫正']);
         chat.role.set("avatar", "/assets/avatars/runner-coach.jpg");
         
-        // 更新提示词为跑步专项
+     
         chat.role.set("prompt", `
 # 角色设定
 您是专业跑步教练陈驰,35岁,前国家马拉松队员。专注于跑步技术、训练计划和伤病预防。
 `);
         
-        // 更新对话分类为跑步主题
+       
         const promptCates = [
           { img: "/assets/icon/distance.png", name: "长跑训练" },
           { img: "/assets/icon/speed.png", name: "速度提升" },
           { img: "/assets/icon/injury.png", name: "伤痛预防" }
         ];
         
-        // 更新对话提示内容
+        
         const promptList = [
           {
             cate: "长跑训练",
@@ -148,7 +149,7 @@ export class Tab2Page {
   constructor(
     private modalCtrl: ModalController,
   ) {
-    addIcons({notificationsOutline,calendarNumber,refresh,footsteps,add,chatbubbleEllipses,checkmarkCircle,ellipseOutline,timeOutline,barbell,body,walk,fitness,flame,bicycle,trophy,ellipsisHorizontal,footstepsOutline});
+    addIcons({notificationsOutline,calendarNumber,refresh,barChartOutline,timeOutline,footsteps,calendarClearOutline,add,star,checkmarkCircleOutline,chatbubbleEllipses,checkmarkCircle,ellipseOutline,barbell,body,walk,fitness,flame,bicycle,trophy,ellipsisHorizontal,footstepsOutline});
     
     // 初始化周数据
     this.weekDays = this.generateWeekDays();
@@ -238,29 +239,36 @@ export class Tab2Page {
 
   async loadTrainingPlans() {
     if (!this.currentUser?.id) return;
-
-    const query = new CloudQuery('TrainingPlan');
-    query.equalTo('user', this.currentUser.toPointer());
-
-    const plans = await query.find();
-    
-    this.myPlans = plans.map(plan => ({
-      id: plan.id,
-      name: plan.get('planName') || '未命名计划',
-      progress: this.calculatePlanProgress(plan),
-      nextTime: this.getNextTrainingTime(plan),
-      remaining: this.getRemainingDays(plan),
-      icon: this.getPlanIcon(plan.get('fitnessGoals')),
-      description: plan.get('description'),
-      durationInWeeks: plan.get('durationInWeeks'),
-      difficultyLevel: plan.get('difficultyLevel')
-    }));
-
-    // 加载每个计划的任务
-    for (const plan of plans) {
-      if (typeof plan.id === "string") {
-        await this.loadPlanTasks(plan.id);
+  
+    try {
+      const query = new CloudQuery('TrainingPlan');
+      query.equalTo('user', this.currentUser.toPointer());
+      
+      // 确保只加载属于当前用户的计划
+      const plans = await query.find();
+      
+      this.myPlans = plans.map(plan => ({
+        id: plan.id,
+        name: plan.get('planName') || '未命名计划',
+        progress: this.calculatePlanProgress(plan),
+        nextTime: this.getNextTrainingTime(plan),
+        remaining: this.getRemainingDays(plan),
+        icon: this.getPlanIcon(plan.get('fitnessGoals')),
+        description: plan.get('description'),
+        durationInWeeks: plan.get('durationInWeeks'),
+        difficultyLevel: plan.get('difficultyLevel'),
+        startDate: plan.get('startDate') // 添加起始日期
+      }));
+  
+      // 加载每个计划的任务
+      for (const plan of plans) {
+        if (typeof plan.id === "string") {
+          await this.loadPlanTasks(plan.id);
+        }
       }
+    } catch (error) {
+      console.error('加载训练计划失败:', error);
+      this.showToast('加载训练计划失败,请重试');
     }
   }
 
@@ -294,8 +302,16 @@ export class Tab2Page {
   }
 
   getRemainingDays(plan: CloudObject): number {
-    // 模拟实现 - 实际应根据计划duration和开始日期计算
-    return Math.floor(Math.random() * 30) + 1;
+    const startDate = plan.get('startDate');
+    if (!startDate) return 0;
+    
+    const endDate = new Date(startDate);
+    const weeks = plan.get('durationInWeeks') || 4;
+    endDate.setDate(endDate.getDate() + weeks * 7);
+    
+    const today = new Date();
+    const diffInTime = endDate.getTime() - today.getTime();
+    return Math.ceil(diffInTime / (1000 * 3600 * 24));
   }
 
   getPlanIcon(goals: string[] = []): string {
@@ -338,40 +354,71 @@ export class Tab2Page {
   // 保存新计划
   async saveNewPlan(planData: any) {
     try {
-      // 确保当前用户存在
       if (!this.currentUser || !this.currentUser.id) {
         throw new Error('用户未登录,无法创建计划');
       }
-
-      // 创建新的训练计划对象
-      const TrainingPlan = Parse.Object.extend('TrainingPlan');
-      const newPlan = new TrainingPlan();
+  
+      const newPlan = new CloudObject('TrainingPlan');
       
-      // 设置计划属性
-      newPlan.set('planName', planData.name);
-      newPlan.set('description', planData.description);
-      newPlan.set('durationInWeeks', parseInt(planData.duration));
-      newPlan.set('difficultyLevel', planData.difficulty);
-      newPlan.set('fitnessGoals', planData.goals);
+      // 确保日期格式正确
+      const startDate = planData.startDate;
+      const formattedDate = startDate ? startDate : new Date().toISOString();
       
-      // 关联当前用户
-      newPlan.set('user', this.currentUser.toPointer());
+      newPlan.set({
+        planName: planData.name,
+        description: planData.description,
+        durationInWeeks: parseInt(planData.duration),
+        difficultyLevel: planData.difficulty,
+        fitnessGoals: planData.goals,
+        user: this.currentUser.toPointer(),
+        startDate: formattedDate
+      });
       
-      // 保存到后端
       await newPlan.save();
-      
-      // 显示成功消息
       this.showToast('计划创建成功!');
     } catch (error: any) {
       console.error('创建计划失败:', error);
       this.showToast(error.message || '创建计划失败,请重试');
     }
   }
-
   // 显示Toast消息
   async showToast(message: string) {
     // 实际项目中应该使用Ionic的ToastController
     // 这里简化实现为alert
     alert(message);
   }
+   // 根据难度级别获取徽章颜色
+   getBadgeColor(difficulty: string): string {
+    return difficulty.toLowerCase() + '-badge';
+  }
+
+  // 获取难度文本
+  getDifficultyText(difficulty: string): string {
+    const map: Record<string, string> = {
+      'beginner': '初级',
+      'intermediate': '中级',
+      'advanced': '高级'
+    };
+    return map[difficulty.toLowerCase()] || difficulty;
+  }
+
+  // 根据计划难度获取卡片颜色
+  getPlanColor(difficulty: string): string {
+    const map: Record<string, string> = {
+      'beginner': 'linear-gradient(to right, #27ae60, #2ecc71)',
+      'intermediate': 'linear-gradient(to right, #f39c12, #f1c40f)',
+      'advanced': 'linear-gradient(to right, #e74c3c, #c0392b)'
+    };
+    return map[difficulty.toLowerCase()] || '#3a7bd5';
+  }
+
+  // 获取任务标签颜色
+  getChipColor(difficulty: string): string {
+    const map: Record<string, string> = {
+      'beginner': 'success',
+      'intermediate': 'warning',
+      'advanced': 'danger'
+    };
+    return map[difficulty.toLowerCase()] || 'primary';
+  }
 }

+ 106 - 109
src/app/tab4/tab4.page.html

@@ -79,11 +79,11 @@
     <!-- 卡片区域 -->
     <ion-card class="data-card">
       <ion-card-header>
-        <ion-card-title>我的数据</ion-card-title>
+        <ion-card-title>本周运动</ion-card-title>
       </ion-card-header>
       <ion-card-content>
         <div class="data-content">
-          <div class="data-value">90分钟</div>
+          <div class="data-value">{{ currentUser.stats.hours }}分钟</div>
           <div class="data-label">本周运动</div>
         </div>
         <div class="data-chart">
@@ -102,7 +102,7 @@
       </ion-card-header>
       <ion-card-content>
         <div class="data-content">
-          <div class="data-value">5,400分钟</div>
+          <div class="data-value">{{ currentUser.stats.hours * 60 }}分钟</div>
           <div class="data-label">累计运动时长</div>
         </div>
         <div class="data-chart">
@@ -166,117 +166,114 @@
   }
 
   <!-- 登录模态框 -->
-  <!-- 登录模态框 -->
-<ion-modal [isOpen]="showLoginModal">
-  <ng-template>
-    <ion-header>
-      <ion-toolbar color="primary">
-        <ion-title>用户登录</ion-title>
-        <ion-buttons slot="end">
-          <ion-button (click)="closeModals()">
-            <ion-icon name="close-outline"></ion-icon>
+  <ion-modal [isOpen]="showLoginModal">
+    <ng-template>
+      <ion-header>
+        <ion-toolbar color="primary">
+          <ion-title>用户登录</ion-title>
+          <ion-buttons slot="end">
+            <ion-button (click)="closeModals()">
+              <ion-icon name="close-outline"></ion-icon>
+            </ion-button>
+          </ion-buttons>
+        </ion-toolbar>
+      </ion-header>
+      <ion-content class="auth-content">
+        <div class="auth-container">
+          <!-- 邮箱输入 -->
+          <div class="input-item">
+            <label class="input-label">邮箱</label>
+            <ion-input type="email" [(ngModel)]="loginForm.email" placeholder="请输入您的邮箱"></ion-input>
+          </div>
+          
+          <!-- 密码输入 -->
+          <div class="input-item">
+            <label class="input-label">密码</label>
+            <ion-input type="password" [(ngModel)]="loginForm.password" placeholder="请输入密码"></ion-input>
+          </div>
+          
+          <!-- 错误信息 -->
+          @if (authError) {
+            <div class="error-message">
+              <ion-icon name="warning-outline"></ion-icon>
+              {{ authError }}
+            </div>
+          }
+          
+          <!-- 登录按钮 -->
+          <ion-button expand="block" color="primary" (click)="handleLogin()">
+            <ion-icon slot="start" name="log-in-outline"></ion-icon>
+            登录
           </ion-button>
-        </ion-buttons>
-      </ion-toolbar>
-    </ion-header>
-    <ion-content class="auth-content">
-      <div class="auth-container">
-        <!-- 邮箱输入 -->
-        <div class="input-item">
-          <label class="input-label">邮箱</label>
-          <ion-input type="email" [(ngModel)]="loginForm.email" placeholder="请输入您的邮箱"></ion-input>
-        </div>
-        
-        <!-- 密码输入 -->
-        <div class="input-item">
-          <label class="input-label">密码</label>
-          <ion-input type="password" [(ngModel)]="loginForm.password" placeholder="请输入密码"></ion-input>
-        </div>
-        
-        <!-- 错误信息 -->
-        @if (authError) {
-          <div class="error-message">
-            <ion-icon name="warning-outline"></ion-icon>
-            {{ authError }}
+          
+          <!-- 页脚链接 -->
+          <div class="auth-footer">
+            <p>还没有账号?<a (click)="openRegister()">立即注册</a></p>
           </div>
-        }
-        
-        <!-- 登录按钮 -->
-        <ion-button expand="block" color="primary" (click)="handleLogin()">
-          <ion-icon slot="start" name="log-in-outline"></ion-icon>
-          登录
-        </ion-button>
-        
-        <!-- 页脚链接 -->
-        <div class="auth-footer">
-          <p>还没有账号?<a (click)="openRegister()">立即注册</a></p>
         </div>
-      </div>
-    </ion-content>
-  </ng-template>
-</ion-modal>
+      </ion-content>
+    </ng-template>
+  </ion-modal>
 
-<!-- 注册模态框 -->
-<ion-modal [isOpen]="showRegisterModal">
-  <ng-template>
-    <ion-header>
-      <ion-toolbar color="primary">
-        <ion-title>用户注册</ion-title>
-        <ion-buttons slot="end">
-          <ion-button (click)="closeModals()">
-            <ion-icon name="close-outline"></ion-icon>
+  <!-- 注册模态框 -->
+  <ion-modal [isOpen]="showRegisterModal">
+    <ng-template>
+      <ion-header>
+        <ion-toolbar color="primary">
+          <ion-title>用户注册</ion-title>
+          <ion-buttons slot="end">
+            <ion-button (click)="closeModals()">
+              <ion-icon name="close-outline"></ion-icon>
+            </ion-button>
+          </ion-buttons>
+        </ion-toolbar>
+      </ion-header>
+      <ion-content class="auth-content">
+        <div class="auth-container">
+          <!-- 昵称输入 -->
+          <div class="input-item">
+            <label class="input-label">昵称</label>
+            <ion-input type="text" [(ngModel)]="registerForm.name" placeholder="设置您的昵称"></ion-input>
+          </div>
+          
+          <!-- 邮箱输入 -->
+          <div class="input-item">
+            <label class="input-label">邮箱</label>
+            <ion-input type="email" [(ngModel)]="registerForm.email" placeholder="请输入您的邮箱"></ion-input>
+          </div>
+          
+          <!-- 密码输入 -->
+          <div class="input-item">
+            <label class="input-label">密码</label>
+            <ion-input type="password" [(ngModel)]="registerForm.password" placeholder="设置密码(至少6位)"></ion-input>
+          </div>
+          
+          <!-- 确认密码输入 -->
+          <div class="input-item">
+            <label class="input-label">确认密码</label>
+            <ion-input type="password" [(ngModel)]="registerForm.confirmPassword" placeholder="请再次输入密码"></ion-input>
+          </div>
+          
+          <!-- 错误信息 -->
+          @if (authError) {
+            <div class="error-message">
+              <ion-icon name="warning-outline"></ion-icon>
+              {{ authError }}
+            </div>
+          }
+          
+          <!-- 注册按钮 -->
+          <ion-button expand="block" color="primary" (click)="handleRegister()">
+            <ion-icon slot="start" name="person-add-outline"></ion-icon>
+            注册
           </ion-button>
-        </ion-buttons>
-      </ion-toolbar>
-    </ion-header>
-    <ion-content class="auth-content">
-      <div class="auth-container">
-        <!-- 昵称输入 -->
-        <div class="input-item">
-          <label class="input-label">昵称</label>
-          <ion-input type="text" [(ngModel)]="registerForm.name" placeholder="设置您的昵称"></ion-input>
-        </div>
-        
-        <!-- 邮箱输入 -->
-        <div class="input-item">
-          <label class="input-label">邮箱</label>
-          <ion-input type="email" [(ngModel)]="registerForm.email" placeholder="请输入您的邮箱"></ion-input>
-        </div>
-        
-        <!-- 密码输入 -->
-        <div class="input-item">
-          <label class="input-label">密码</label>
-          <ion-input type="password" [(ngModel)]="registerForm.password" placeholder="设置密码(至少6位)"></ion-input>
-        </div>
-        
-        <!-- 确认密码输入 -->
-        <div class="input-item">
-          <label class="input-label">确认密码</label>
-          <ion-input type="password" [(ngModel)]="registerForm.confirmPassword" placeholder="请再次输入密码"></ion-input>
-        </div>
-        
-        <!-- 错误信息 -->
-        @if (authError) {
-          <div class="error-message">
-            <ion-icon name="warning-outline"></ion-icon>
-            {{ authError }}
+          
+          <!-- 页脚链接 -->
+          <div class="auth-footer">
+            <p>已有账号?<a (click)="openLogin()">立即登录</a></p>
           </div>
-        }
-        
-        <!-- 注册按钮 -->
-        <ion-button expand="block" color="primary" (click)="handleRegister()">
-          <ion-icon slot="start" name="person-add-outline"></ion-icon>
-          注册
-        </ion-button>
-        
-        <!-- 页脚链接 -->
-        <div class="auth-footer">
-          <p>已有账号?<a (click)="openLogin()">立即登录</a></p>
         </div>
-      </div>
-    </ion-content>
-  </ng-template>
-</ion-modal>
-  
- 
+      </ion-content>
+    </ng-template>
+  </ion-modal>
 </ion-content>

+ 134 - 2
src/app/tab4/tab4.page.scss

@@ -212,8 +212,8 @@ ion-icon {
 }
 
 .error-message {
-  background: var(--ion-color-danger-tint);
-  color: var(--ion-color-danger);
+  background: rgba(255, 0, 0, 0.2);
+  color: #ff6161; /* 错误文字颜色 */
   padding: 12px;
   border-radius: 8px;
   margin: 15px 0;
@@ -223,6 +223,7 @@ ion-icon {
   
   ion-icon {
     margin-right: 8px;
+    color: #ff6161;
   }
 }
 
@@ -238,4 +239,135 @@ ion-icon {
     font-weight: 500;
     cursor: pointer;
   }
+}
+ion-modal {
+  --background: rgba(0, 0, 0, 0.7); /* 半透明黑色背景 */
+  
+  ion-toolbar {
+    --background: var(--ion-color-primary);
+    --color: white;
+  }
+}
+.input-item {
+  margin-bottom: 20px;
+  
+  /* 标签样式 - 确保深色背景下清晰可见 */
+  .input-label {
+    display: block;
+    margin-bottom: 8px;
+    font-weight: 500;
+    color: rgba(255, 255, 255, 0.9); /* 白色带轻微透明度 */
+    font-size: 1rem;
+  }
+  
+  /* 输入框样式 */
+  ion-input {
+    --background: rgba(255, 255, 255, 0.1); /* 半透明白色背景 */
+    --color: white; /* 输入文字颜色 */
+    --padding-start: 15px;
+    --padding-end: 15px;
+    --placeholder-color: rgba(255, 255, 255, 0.6); /* 占位符颜色 */
+    --placeholder-opacity: 1;
+    border-radius: 12px;
+    height: 50px;
+    font-size: 1rem;
+  }
+}
+/* 登录注册模态框样式优化 */
+ion-modal {
+  --background: rgba(0, 0, 0, 0.7); /* 半透明黑色背景 */
+  
+  ion-toolbar {
+    --background: var(--ion-color-primary);
+    --color: white;
+  }
+}
+
+.auth-container {
+  max-width: 500px;
+  margin: 0 auto;
+  padding: 20px 0;
+  
+  /* 输入项容器样式 */
+  .input-item {
+    margin-bottom: 20px;
+    
+    /* 标签样式 - 确保深色背景下清晰可见 */
+    .input-label {
+      display: block;
+      margin-bottom: 8px;
+      font-weight: 500;
+      color: rgba(255, 255, 255, 0.9); /* 白色带轻微透明度 */
+      font-size: 1rem;
+    }
+    
+    /* 输入框样式 */
+    ion-input {
+      --background: rgba(255, 255, 255, 0.1); /* 半透明白色背景 */
+      --color: white; /* 输入文字颜色 */
+      --padding-start: 15px;
+      --padding-end: 15px;
+      --placeholder-color: rgba(255, 255, 255, 0.6); /* 占位符颜色 */
+      --placeholder-opacity: 1;
+      border-radius: 12px;
+      height: 50px;
+      font-size: 1rem;
+    }
+  }
+  
+  /* 错误信息样式 */
+  .error-message {
+    background: rgba(255, 0, 0, 0.2);
+    color: #ff6161; /* 错误文字颜色 */
+    padding: 12px;
+    border-radius: 8px;
+    margin: 15px 0;
+    display: flex;
+    align-items: center;
+    font-size: 0.9rem;
+    
+    ion-icon {
+      margin-right: 8px;
+      color: #ff6161;
+    }
+  }
+  
+  /* 按钮样式 */
+  ion-button {
+    --border-radius: 12px;
+    height: 50px;
+    font-weight: 600;
+    margin-top: 10px;
+    --background: var(--ion-color-primary);
+    --color: white;
+    --background-activated: var(--ion-color-primary-shade);
+    --background-hover: var(--ion-color-primary-tint);
+  }
+  
+  /* 页脚链接样式 */
+  .auth-footer {
+    text-align: center;
+    margin-top: 20px;
+    font-size: 0.95rem;
+    color: rgba(255, 255, 255, 0.7);
+    
+    a {
+      color: var(--ion-color-primary);
+      text-decoration: none;
+      font-weight: 500;
+      cursor: pointer;
+      transition: opacity 0.3s;
+      
+      &:hover {
+        opacity: 0.8;
+      }
+    }
+  }
+}
+
+/* 添加模态框内容区域的渐变背景 */
+.auth-content {
+  background: linear-gradient(135deg, #1a2a6c, #2a3a7c); /* 深色渐变背景 */
+  height: 100%;
+  padding: 20px;
 }

+ 84 - 42
src/app/tab4/tab4.page.ts

@@ -1,4 +1,4 @@
-import { Component } from '@angular/core';
+import { Component, NgZone } from '@angular/core';
 import { 
   IonHeader, IonToolbar, IonTitle, IonContent, 
   IonCard, IonCardHeader, IonCardTitle, IonCardContent, 
@@ -16,7 +16,8 @@ import {
   logOutOutline, closeOutline, checkmarkOutline, warningOutline } from 'ionicons/icons';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { CloudUser } from '../../lib/ncloud';
+import { CloudUser, CloudObject } from '../../lib/ncloud';
+import { Capacitor } from '@capacitor/core';
 
 @Component({
   selector: 'app-tab4',
@@ -73,8 +74,13 @@ export class Tab4Page {
   // 用户服务
   userService = new CloudUser();
 
-  constructor() {
-    addIcons({logOutOutline,logInOutline,timeOutline,calendarOutline,trophyOutline,personAddOutline,documentTextOutline,chevronForwardOutline,heartOutline,schoolOutline,medalOutline,nutritionOutline,walletOutline,giftOutline,peopleOutline,helpCircleOutline,closeOutline,warningOutline,checkmarkOutline});
+  constructor(private ngZone: NgZone) {
+    addIcons({
+      logOutOutline,logInOutline,timeOutline,calendarOutline,trophyOutline,
+      personAddOutline,documentTextOutline,chevronForwardOutline,heartOutline,
+      schoolOutline,medalOutline,nutritionOutline,walletOutline,giftOutline,
+      peopleOutline,helpCircleOutline,closeOutline,warningOutline,checkmarkOutline
+    });
     
     // 检查登录状态
     this.checkLoginStatus();
@@ -82,18 +88,42 @@ export class Tab4Page {
   
   // 检查登录状态
   async checkLoginStatus() {
-    const userData = localStorage.getItem("NCloud/dev/User");
-    if (userData) {
-      const user = JSON.parse(userData);
+    // 检查是否在移动设备上运行
+    const isMobile = Capacitor.isNativePlatform();
+    
+    // 尝试获取当前用户
+    const currentUser = await this.userService.current();
+    
+    if (this.userService.id) {
       this.isLoggedIn = true;
-      this.currentUser = {
-        ...this.currentUser,
-        name: user.name || '运动达人',
-        email: user.email
-      };
+      this.updateUserProfile();
+    } else if (isMobile && currentUser) {
+      // 在移动设备上需要额外处理当前用户状态
+      this.ngZone.run(() => {
+        this.isLoggedIn = true;
+        this.updateUserProfile();
+      });
+    } else {
+      this.isLoggedIn = false;
     }
   }
   
+  // 更新用户资料
+  updateUserProfile() {
+    this.currentUser = {
+      name: this.userService.get('name') || '健身爱好者',
+      email: this.userService.get('email') || '未设置邮箱',
+      stats: {
+        following: this.userService.get('following') || 0,
+        followers: this.userService.get('followers') || 0,
+        cheers: this.userService.get('cheers') || 0,
+        hours: this.userService.get('totalHours') || 0,
+        days: this.userService.get('activeDays') || 0,
+        medals: this.userService.get('medals') || 0
+      }
+    };
+  }
+  
   // 打开登录模态框
   openLogin() {
     this.showLoginModal = true;
@@ -134,19 +164,17 @@ export class Tab4Page {
       
       if (user) {
         this.isLoggedIn = true;
-        this.currentUser = {
-          ...this.currentUser,
-          name: user.data['name'] || '运动达人',
-          email: user.data['email'] || ''
-        };
-    
+        this.updateUserProfile();
         this.closeModals();
       } else {
         this.authError = '登录失败,请检查邮箱和密码';
       }
-    } catch (error) {
+    } catch (error: any) {
       this.authError = '登录过程中发生错误';
-      console.error(error);
+      if (error.message) {
+        this.authError = error.message;
+      }
+      console.error('登录错误:', error);
     }
   }
   
@@ -162,48 +190,62 @@ export class Tab4Page {
       return;
     }
     
+    if (this.registerForm.password.length < 6) {
+      this.authError = '密码至少需要6位字符';
+      return;
+    }
+    
     try {
       const user = await this.userService.signUp(
         this.registerForm.email,
         this.registerForm.password,
         {
           name: this.registerForm.name,
-          email: this.registerForm.email
+          email: this.registerForm.email,
+          following: 0,
+          followers: 0,
+          cheers: 0,
+          totalHours: 0,
+          activeDays: 0,
+          medals: 0
         }
       );
       
       if (user) {
         this.isLoggedIn = true;
-        this.currentUser = {
-          ...this.currentUser,
-          name: this.registerForm.name,
-          email: this.registerForm.email
-        };
+        this.updateUserProfile();
         this.closeModals();
       } else {
         this.authError = '注册失败,请稍后再试';
       }
-    } catch (error) {
+    } catch (error: any) {
       this.authError = '注册过程中发生错误';
-      console.error(error);
+      if (error.message) {
+        this.authError = error.message;
+      }
+      console.error('注册错误:', error);
     }
   }
   
   // 退出登录
   async logout() {
-    await this.userService.logout();
-    this.isLoggedIn = false;
-    this.currentUser = {
-      name: '运动达人',
-      email: '',
-      stats: {
-        following: 128,
-        followers: 356,
-        cheers: 1024,
-        hours: 90,
-        days: 120,
-        medals: 5
-      }
-    };
+    try {
+      await this.userService.logout();
+      this.isLoggedIn = false;
+      this.currentUser = {
+        name: '运动达人',
+        email: '',
+        stats: {
+          following: 128,
+          followers: 356,
+          cheers: 1024,
+          hours: 90,
+          days: 120,
+          medals: 5
+        }
+      };
+    } catch (error) {
+      console.error('登出错误:', error);
+    }
   }
 }

TEMPAT SAMPAH
src/assets/avatars/R-C.jpg


TEMPAT SAMPAH
src/assets/avatars/avatar1.jpg


TEMPAT SAMPAH
src/assets/avatars/avatar2.jpg


TEMPAT SAMPAH
src/assets/avatars/avatar3.jpg


TEMPAT SAMPAH
src/assets/avatars/distance.jpeg


TEMPAT SAMPAH
src/assets/avatars/jiaolian1.jpg