Forráskód Böngészése

feat : add come new pages and components

xukang 4 hónapja
szülő
commit
7650ade0c0
24 módosított fájl, 1488 hozzáadás és 285 törlés
  1. 39 0
      TFPower-app/src/app/tab2/edit-plan-modal/edit-plan-modal.component.html
  2. 0 0
      TFPower-app/src/app/tab2/edit-plan-modal/edit-plan-modal.component.scss
  3. 24 0
      TFPower-app/src/app/tab2/edit-plan-modal/edit-plan-modal.component.spec.ts
  4. 44 0
      TFPower-app/src/app/tab2/edit-plan-modal/edit-plan-modal.component.ts
  5. 144 91
      TFPower-app/src/app/tab2/tab2.page.html
  6. 81 30
      TFPower-app/src/app/tab2/tab2.page.scss
  7. 85 37
      TFPower-app/src/app/tab2/tab2.page.ts
  8. 7 8
      TFPower-app/src/app/tab2/test-page/test-page.component.html
  9. 52 6
      TFPower-app/src/app/tab2/test-page/test-page.component.ts
  10. BIN
      TFPower-app/src/assets/images/ache.jpg
  11. BIN
      TFPower-app/src/assets/images/action.png
  12. BIN
      TFPower-app/src/assets/images/coach1.jpg
  13. BIN
      TFPower-app/src/assets/images/coach2.jpg
  14. 377 0
      TFPower-app/src/lib/ncloud.ts
  15. 26 0
      TFPower-app/src/lib/user/modal-user-edit/modal-user-edit.component.html
  16. 0 0
      TFPower-app/src/lib/user/modal-user-edit/modal-user-edit.component.scss
  17. 22 0
      TFPower-app/src/lib/user/modal-user-edit/modal-user-edit.component.spec.ts
  18. 65 0
      TFPower-app/src/lib/user/modal-user-edit/modal-user-edit.component.ts
  19. 38 0
      TFPower-app/src/lib/user/modal-user-login/modal-user-login.component.html
  20. 0 0
      TFPower-app/src/lib/user/modal-user-login/modal-user-login.component.scss
  21. 22 0
      TFPower-app/src/lib/user/modal-user-login/modal-user-login.component.spec.ts
  22. 92 0
      TFPower-app/src/lib/user/modal-user-login/modal-user-login.component.ts
  23. 350 95
      TFPower-server/lib/migration/data.js
  24. 20 18
      TFPower-server/lib/migration/import-data.js

+ 39 - 0
TFPower-app/src/app/tab2/edit-plan-modal/edit-plan-modal.component.html

@@ -0,0 +1,39 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-title>编辑计划</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="dismiss()">关闭</ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>修改计划内容</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-item>
+        <ion-label>日期</ion-label>
+        <ion-input>{{ plan.get('date') }}</ion-input>
+      </ion-item>
+
+      <ion-label>训练计划</ion-label>
+      <ion-col size="1.5" *ngFor="let task of plan.get('trainingItems'); let i = index" class="plan-column">
+        {{ task.item || '' }}: {{task.sets}} x {{task.reps}}
+      </ion-col>
+
+
+
+      <ion-item>
+        <ion-label></ion-label>
+        <ion-input [(ngModel)]="plan.trainingItems" placeholder="输入训练项目"></ion-input>
+      </ion-item>
+      <ion-item>
+        <ion-label>状态</ion-label>
+        <ion-toggle [(ngModel)]="plan.completed" color="success"></ion-toggle>
+      </ion-item>
+      <ion-button expand="full" color="primary" (click)="saveChanges()">保存修改</ion-button>
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 0 - 0
TFPower-app/src/app/tab2/edit-plan-modal/edit-plan-modal.component.scss


+ 24 - 0
TFPower-app/src/app/tab2/edit-plan-modal/edit-plan-modal.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { IonicModule } from '@ionic/angular';
+
+import { EditPlanModalComponent } from './edit-plan-modal.component';
+
+describe('EditPlanModalComponent', () => {
+  let component: EditPlanModalComponent;
+  let fixture: ComponentFixture<EditPlanModalComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      declarations: [ EditPlanModalComponent ],
+      imports: [IonicModule.forRoot()]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(EditPlanModalComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 44 - 0
TFPower-app/src/app/tab2/edit-plan-modal/edit-plan-modal.component.ts

@@ -0,0 +1,44 @@
+import { Component, Input } from '@angular/core';
+import { ModalController } from '@ionic/angular/standalone';
+import { IonCol, IonHeader, IonToolbar, IonTitle, IonButtons, IonButton, IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonItem, IonLabel, IonInput, IonToggle } from '@ionic/angular/standalone';
+import { FormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+
+@Component({
+  selector: 'app-edit-plan-modal',
+  templateUrl: './edit-plan-modal.component.html',
+  styleUrls: ['./edit-plan-modal.component.scss'],
+  standalone: true,
+  imports: [
+    IonCol,
+    IonHeader,
+    IonToolbar,
+    IonTitle,
+    IonButtons,
+    IonButton,
+    IonContent,
+    IonCard,
+    IonCardHeader,
+    IonCardTitle,
+    IonCardContent,
+    IonItem,
+    IonLabel,
+    IonInput,
+    IonToggle,
+    FormsModule,
+    CommonModule
+  ]
+})
+export class EditPlanModalComponent {
+  @Input() plan: any;  // 接收传入的计划数据
+
+  constructor(private modalCtrl: ModalController) { }
+
+  saveChanges() {
+    this.modalCtrl.dismiss(this.plan); // 保存修改并返回
+  }
+
+  dismiss() {
+    this.modalCtrl.dismiss(); // 关闭模态框
+  }
+}

+ 144 - 91
TFPower-app/src/app/tab2/tab2.page.html

@@ -22,111 +22,164 @@
 
   <!-- 打卡 -->
   <div *ngIf="selectedTab === 'checkin'" class="checkin-container">
-    <p class="section-title">今天的打卡任务</p>
-    <p>在这里打卡,开始你的一天健身之旅吧!</p>
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>今天的打卡任务</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <p>在这里打卡,开始你的一天健身之旅吧!</p>
+      </ion-card-content>
+    </ion-card>
   </div>
 
   <!-- 计划 -->
   <div *ngIf="selectedTab === 'plan'" class="plan-container">
-    <p class="section-title">我的本周计划</p>
-    <div class="plan-table">
-      <ion-grid class="table">
-        <ion-row>
-          <ion-col size="2" class="grid-header">日期</ion-col>
-          <ion-col size="2" class="grid-header">训练部位</ion-col>
-          <ion-col size="1" class="grid-header">训练项目1</ion-col>
-          <ion-col size="1" class="grid-header">训练项目2</ion-col>
-          <ion-col size="1" class="grid-header">训练项目3</ion-col>
-          <ion-col size="1" class="grid-header">训练项目4</ion-col>
-          <ion-col size="2" class="grid-header">完成状态</ion-col>
-          <ion-col size="2" class="grid-header">操作</ion-col> <!-- 操作列 -->
-        </ion-row>
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>我的本周计划</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <div class="plan-table">
+          <ion-grid class="table">
+            <ion-row>
+              <ion-col size="1" class="grid-header">日期</ion-col>
+              <ion-col size="1" class="grid-header">部位</ion-col>
+              <ion-col size="1.5" class="grid-header">项目1</ion-col>
+              <ion-col size="1.5" class="grid-header">项目2</ion-col>
+              <ion-col size="1.5" class="grid-header">项目3</ion-col>
+              <ion-col size="1.5" class="grid-header">项目4</ion-col>
+              <ion-col size="1" class="grid-header">状态</ion-col>
+              <ion-col size="2" class="grid-header">操作</ion-col> <!-- 操作列 -->
+            </ion-row>
 
-        <!-- 绑定计划数据 -->
-        <ion-row *ngFor="let day of weekPlan">
-          <ion-col size="2" class="plan-column">{{ day.date }}</ion-col>
-          <ion-col size="2" class="plan-column">{{ day.bodyPart }}</ion-col>
+            <!-- 绑定计划数据 -->
+            <ion-row *ngFor="let day of planList">
+              <ion-col size="1" class="plan-column">{{ day.get('date') }}</ion-col>
+              <ion-col size="1" class="plan-column">{{ day.get('trainingPart') }}</ion-col>
 
-          <!-- 显示每个任务,确保即使为空也占位 -->
-          <ion-col size="1" *ngIf="day.tasks[0]" class="plan-column">{{ day.tasks[0] || '' }}</ion-col>
-          <ion-col size="1" class="plan-column">{{ day.tasks[1] || '' }}</ion-col>
-          <ion-col size="1" class="plan-column">{{ day.tasks[2] || '' }}</ion-col>
-          <ion-col size="1" class="plan-column">{{ day.tasks[3] || '' }}</ion-col>
+              <!-- 显示每个训练项目,确保即使为空也占位 -->
+              <ion-col size="1.5" *ngFor="let task of day.get('trainingItems'); let i = index" class="plan-column">
+                {{ task.item || '' }}: {{task.sets}} x {{task.reps}}
+              </ion-col>
 
-          <ion-col size="2" class="plan-column">
-            <ion-toggle [(ngModel)]="day.completed" color="success"></ion-toggle>
-          </ion-col>
+              <ion-col size="1" class="plan-column">
+                <ion-toggle [(ngModel)]="day.completed" color="success"></ion-toggle>
+              </ion-col>
 
-          <!-- 修改和删除按钮 -->
-          <ion-col size="2" class="ion-text-center">
-            <ion-buttons>
-              <ion-button color="success" (click)="editPlan(day)">
-                <ion-icon slot="start" name="create"></ion-icon> 修改
-              </ion-button>
-              <ion-button color="danger" (click)="deletePlan(day)">
-                <ion-icon slot="start" name="trash"></ion-icon> 删除
-              </ion-button>
-            </ion-buttons>
-          </ion-col>
-        </ion-row>
-      </ion-grid>
-    </div>
+              <!-- 修改和删除按钮 -->
+              <ion-col size="2" class="ion-text-center">
+                <ion-buttons>
+                  <ion-button color="success" (click)="editPlan(day)">
+                    <ion-icon slot="start" name="create"></ion-icon> 修改
+                  </ion-button>
+                  <ion-button color="danger" (click)="deletePlan(day)">
+                    <ion-icon slot="start" name="trash"></ion-icon> 删除
+                  </ion-button>
+                </ion-buttons>
+              </ion-col>
+            </ion-row>
+          </ion-grid>
+        </div>
 
-    <ion-button expand="full" color="primary" (click)="goToPage('test-page')">重新生成计划</ion-button>
+        <ion-button expand="full" color="primary" (click)="goToPage('test-page')">重新生成计划</ion-button>
+      </ion-card-content>
+    </ion-card>
   </div>
 
   <!-- 问诊 -->
-  <div *ngIf="selectedTab === 'consultation'">
-    <p class="section-title">健康问诊</p>
-    <p>有任何不适,可以随时询问!</p>
+  <div *ngIf="selectedTab === 'consultation'" class="consult">
+    <!-- 健康问诊卡片 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-card-title>健康问诊</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        <p>有任何不适,可以随时询问!</p>
+
+        <!-- 健身建议和疼痛咨询左右分布 -->
+        <ion-row>
+          <!-- 健身建议 -->
+          <ion-col size="6" class="ion-text-center">
+            <ion-img src="../../assets/images/action.png" alt="健身建议"></ion-img>
+            <ion-button expand="full" color="secondary" (click)="doPoemTask()">生成健身动作</ion-button>
+          </ion-col>
+
+          <!-- 疼痛咨询 -->
+          <ion-col size="6" class="ion-text-center">
+            <ion-img src="../../assets/images/ache.jpg" alt="疼痛咨询"></ion-img>
+            <ion-button expand="full" color="tertiary" (click)="doInqueryTask()">疼?点这里!</ion-button>
+          </ion-col>
+        </ion-row>
+
+        <!-- 任务区域 -->
+        <ion-card>
+          <ion-card-header>
+            <ion-card-title>健康任务</ion-card-title>
+          </ion-card-header>
+          <ion-card-content>
+            <div *ngFor="let step of taskList">
+              <ion-item>
+                <!-- 待开始 -->
+                <ion-icon *ngIf="step.progress === 0 && !step.error" name="radio-button-off-outline"></ion-icon>
+                <!-- 进行中 -->
+                <ion-icon *ngIf="step.progress !== 0 && step.progress !== 1" name="reload-outline"></ion-icon>
+                <!-- 已完成 -->
+                <ion-icon *ngIf="step.progress === 1" name="checkmark-circle-outline"></ion-icon>
+                <!-- 已出错 -->
+                <ion-icon *ngIf="step.error" name="close-circle-outline"></ion-icon>
+                {{ step.title }}
+                <span *ngIf="step.progress">{{ step.progress * 100 | number:'2.0-0' }}%</span>
+                <span *ngIf="step.error" style="color:red;">{{ step.error }}</span>
+              </ion-item>
+            </div>
+          </ion-card-content>
+        </ion-card>
 
-    <!-- 动作纠正按钮 -->
-    <ion-button (click)="doPoemTask()">生成健身动作</ion-button>
-    <ion-button (click)="doInqueryTask()">疼?点这里!</ion-button>
-    <div>
-      @for(step of taskList;track step.title;)
-      {
-      <div>
-        <!-- 待开始 -->
-        @if(step.progress==0 && !step.error){
-        <ion-icon name="radio-button-off-outline"></ion-icon>
-        }
-        <!-- 进行中 -->
-        @if(step.progress!=0 && step.progress!=1){
-        <ion-icon name="reload-outline"></ion-icon>
-        }
-        <!-- 已完成 -->
-        @if(step.progress==1){
-        <ion-icon name="checkmark-circle-outline"></ion-icon>
-        }
-        <!-- 已出错 -->
-        @if(step.error){
-        <ion-icon name="close-circle-outline"></ion-icon>
-        }
-        {{step.title}} @if(step.progress){<span>{{step.progress * 100 | number:"2.0-0"}}%</span>}
+        <!-- 图片展示 -->
+        <ion-card *ngIf="shareData.images">
+          <ion-card-header>
+            <ion-card-title>相关图片</ion-card-title>
+          </ion-card-header>
+          <ion-card-content>
+            <div *ngFor="let imageUrl of shareData.images">
+              <img [src]="imageUrl" alt="诊断图片" style="width: 100%; height: 200px; object-fit: cover;" />
+            </div>
+          </ion-card-content>
+        </ion-card>
 
-        @if(step.error){
-        <span style="color:red;">{{step.error}}</span>
-        }
-      </div>
-      }
-    </div>
-    @if(shareData.images) {
-    @for(imageUrl of shareData.images;track imageUrl){
-    <img [src]="imageUrl" alt="" srcset="">
-    }
-    }
-    @if(shareData.diagResult){
-    <h1>{{shareData.diagResult.title}}</h1>
-    <h2>{{shareData.diagResult.desc}}</h2>
-    <p>{{shareData.diagResult.content}}</p>
-    }
+        <!-- 诊断结果 -->
+        <ion-card *ngIf="shareData.diagResult">
+          <ion-card-header>
+            <ion-card-title>{{ shareData.diagResult.title }}</ion-card-title>
+          </ion-card-header>
+          <ion-card-content>
+            <h2>{{ shareData.diagResult.desc }}</h2>
+            <p>{{ shareData.diagResult.content }}</p>
+          </ion-card-content>
+        </ion-card>
 
-    <!-- AI教练按钮 -->
-    <h1>组件:直接弹出的聊天组件</h1>
-    <ion-button (click)="openChat()">开始新聊天</ion-button>
-    <ion-button (click)="restoreChat('yHEHqMQDNv')">恢复会话</ion-button>
-    <h1>示例:门诊问诊的智能体示例(ChatPanel组件)</h1>
-    <ion-button (click)="openInquiry()">进入门诊</ion-button>
+        <!-- AI教练互动 -->
+        <ion-card>
+          <ion-card-header>
+            <ion-card-title>顶级教练专区</ion-card-title>
+            <ion-card-subtitle>教练简介</ion-card-subtitle>
+          </ion-card-header>
+          <ion-card-content>
+            <ion-list>
+              <ion-item (click)="openInquiry(coach)" *ngFor="let coach of coachList" lines="none">
+                <ion-thumbnail slot="start">
+                  <img src="../../assets/images/coach1.jpg" alt="coach.get('name')">
+                </ion-thumbnail>
+                <div class="coach-info">
+                  <h3>{{ coach.get('name') }}({{ coach.get('age') }}岁)</h3>
+                  <p>擅长领域:{{ coach.get('specialize')}}</p>
+                  <p>WiseFitness俱乐部</p>
+                </div>
+              </ion-item>
+            </ion-list>
+          </ion-card-content>
+        </ion-card>
+      </ion-card-content>
+    </ion-card>
   </div>
 </ion-content>

+ 81 - 30
TFPower-app/src/app/tab2/tab2.page.scss

@@ -1,3 +1,4 @@
+//顶部栏部分
 ion-header {
   position: fixed;
   z-index: 10; /* 确保它在其他内容之上 */
@@ -41,20 +42,31 @@ ion-content {
   background-color: #f9f9f9;
   padding-top: 56px;
 }
-
+// plan部分css
 /* 给表格增加阴影和圆角 */
 .plan-table ion-grid {
   margin-top: 20px;
-  border-radius: 10px;
   box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  width: 100%; 
+
+}
+/* 计划表格的列间距和背景 */
+.plan-table ion-row {
+  margin-bottom: 1px;
+}
+
+/* 表格行的底部间距 */
+.plan-table ion-col {
+  margin-bottom: 1px;
+  border-bottom: 1px solid #ddd;
+  
 }
 
 /* 表格的列 */
 .plan-column {
   background-color: #ffffff;
-  padding: 10px;
-  border: 1px solid #ddd;
-  border-radius: 8px;
+  padding: 12px;
+  border-bottom: 1px solid #ddd;
   text-align: center;
 }
 
@@ -64,26 +76,31 @@ ion-content {
   background-color: #f0f0f0;
   text-align: center;
   padding: 12px;
-  border: 1px solid #ddd;
-  border-radius: 8px;
+  border-bottom: 1px solid #ddd;
+}
+
+.plan-table ion-button {  
+  margin-right: 10px; 
+  text-align: center; 
+  justify-content: center; 
 }
-ion-button[color="success"]::part(native)
+.plan-table ion-button[color="success"]::part(native)
 {
    justify-content: center;
   align-items: center;
-color: black !important; /* 强制修改文本颜色为黑色 */
+color: white !important; /* 强制修改文本颜色为黑色 */
 }
-ion-button[color="danger"]::part(native)
+.plan-table ion-button[color="danger"]::part(native)
 {
    justify-content: center;
   align-items: center;
-color: black !important; /* 强制修改文本颜色为黑色 */
+color: white !important; /* 强制修改文本颜色为黑色 */
 }
 /* 按钮 */
-ion-button[color="success"] {
-  background-color: #4CAF50; /* 绿色背景 */
-  border-radius: 8px;
-  padding: 5px 10px;
+.plan-table ion-button[color="success"] {
+  background-color: #59beec; /* 绿色背景 */
+  border-radius: 4px;
+  padding: 5px 5px;
   font-weight: bold;
    justify-content: center;
   align-items: center;
@@ -91,25 +108,25 @@ ion-button[color="success"] {
   transition: background-color 0.3s ease, transform 0.3s ease;
 }
 
-ion-button[color="success"]:hover {
-  background-color: #45a049; /* 悬停时颜色变深 */
+.plan-table ion-button[color="success"]:hover {
+  background-color: #48a7cc; /* 悬停时颜色变深 */
   transform: scale(1.05); /* 悬停时按钮放大 */
 }
 
-ion-button[color="danger"] {
+.plan-table ion-button[color="danger"] {
   background-color: #f44336; /* 红色背景 */
-  border-radius: 8px;
-  padding: 5px 10px;
+  border-radius: 4px;
+  padding: 5px 5px;
   font-weight: bold;
   box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
   transition: background-color 0.3s ease, transform 0.3s ease;
 }
-ion-button[color="danger"]:hover {
+.plan-table ion-button[color="danger"]:hover {
   background-color: #d32f2f; /* 悬停时颜色变深 */
   transform: scale(1.05); /* 悬停时按钮放大 */
 }
 /* 优化按钮在行中的显示位置 */
-ion-buttons {
+.plan-table ion-buttons {
   display: flex;
   justify-content: center;
   align-items: center;
@@ -123,19 +140,53 @@ ion-buttons {
   margin-bottom: 20px;
 }
 
+/* 问诊部分的设计 */
+/* 确保图片等大 */
+.consult .ion-text-center ion-img {
+  width: 100%; /* 设置宽度为 100% */
+  height: 200px; /* 设置固定高度,确保两张图片等高 */
+  object-fit: cover; /* 保持图片比例,同时裁剪图片以适应区域 */
+}
+.consult ion-item {
+  margin-bottom: 12px; /* 增加医生卡片之间的间距 */
+  padding: 12px; /* 调整卡片内边距 */
+  border-radius: 8px; /* 圆角设计 */
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 添加卡片阴影 */
+  background-color: #ffffff; /* 卡片背景色 */
+}
+
+.consult ion-thumbnail {
+  width: 60px; /* 缩略图宽度 */
+  height: 60px; /* 缩略图高度 */
+  border-radius: 50%; /* 圆形头像 */
+}
+
+.consult .coach-info {
+  margin-left: 12px; /* 缩略图与文字的间距 */
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.consult .coach-info h3 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.consult .coach-info p {
+  margin: 4px 0;
+  font-size: 14px;
+  color: #666; /* 字体颜色 */
+}
+
+
 /* 打卡部分的设计 */
 .checkin-container p {
   font-size: 18px;
   color: #555;
 }
 
-/* 计划表格的列间距和背景 */
-.plan-table ion-row {
-  margin-bottom: 5px;
-}
 
-/* 表格行的底部间距 */
-.plan-table ion-col {
-  margin-bottom: 2px;
-}
+
 

+ 85 - 37
TFPower-app/src/app/tab2/tab2.page.ts

@@ -1,10 +1,10 @@
 import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
 import { Router } from '@angular/router';
 import { addIcons } from 'ionicons';
-import { checkmarkCircle, calendar, helpCircle } from 'ionicons/icons';
+import { checkmarkCircle, trash, calendar, helpCircle, create } from 'ionicons/icons';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { IonButtons, IonItem, IonList, IonHeader, IonIcon, IonToolbar, IonContent, IonSegment, IonSegmentButton, IonGrid, IonRow, IonCol, IonButton, IonLabel, IonBadge, IonInput, ModalController } from '@ionic/angular/standalone';
+import { IonThumbnail, IonCardSubtitle, IonImg, IonCard, IonButtons, IonItem, IonList, IonHeader, IonIcon, IonToolbar, IonContent, IonSegment, IonSegmentButton, IonGrid, IonRow, IonCol, IonButton, IonLabel, IonBadge, IonInput, ModalController, IonCardTitle, IonCardContent, IonCardHeader } from '@ionic/angular/standalone';
 import { FmodeChatCompletion, ImagineWork, DalleOptions, ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from "fmode-ng";
 import { AgentTaskStep } from './agent/agent.task';
 import { TaskPoemPictureDesc } from './agent/tasks/poem/poem-desc';
@@ -13,12 +13,21 @@ import { startTask } from './agent/agent.start';
 import { TaskInqueryUserStory } from './agent/tasks/poem/inquiry/1.inquiry-user-story';
 import { TaskInqueryDoctorQuestion } from './agent/tasks/poem/inquiry/2.inquiry-doctor-question';
 import { TaskInqueryUserAnswer } from './agent/tasks/poem/inquiry/3.inquiry-user-answer';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { EditPlanModalComponent } from './edit-plan-modal/edit-plan-modal.component';
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
   styleUrls: ['tab2.page.scss'],
   standalone: true,
   imports: [
+    IonThumbnail,
+    IonCardSubtitle,
+    IonImg,
+    IonCard,
+    IonCardTitle,
+    IonCardHeader,
+    IonCardContent,
     IonButtons,
     IonItem,
     IonList,
@@ -41,34 +50,58 @@ import { TaskInqueryUserAnswer } from './agent/tasks/poem/inquiry/3.inquiry-user
 })
 export class Tab2Page implements OnInit {
   selectedTab: string = 'checkin';  // 默认选中的tab
-  weekPlan: any[] = [
-    { date: '周一', bodyPart: '胸部', tasks: ['卧推', '哑铃飞鸟', '俯卧撑', '仰卧起坐'], completed: false },
-    { date: '周二', bodyPart: '背部', tasks: ['引体向上', '划船', '拉力器背肌'], completed: false },
-    { date: '周三', bodyPart: '腿部', tasks: ['深蹲', '腿推', '腿弯举'], completed: false },
-    { date: '周四', bodyPart: '肩部', tasks: ['肩推', '侧平举', '前平举'], completed: false },
-    { date: '周五', bodyPart: '腹部', tasks: ['仰卧起坐', '卷腹', 'Russian twist'], completed: false }
+  planList: any[] = [
+  ];
+  coachList: any[] = [
   ];
 
   constructor(private router: Router, private modalCtrl: ModalController, private cdr: ChangeDetectorRef) {
-    addIcons({ checkmarkCircle, calendar, helpCircle });
+    addIcons({ checkmarkCircle, calendar, helpCircle, trash, create });
+  }
+  async loadPlanList() {
+    let query = new CloudQuery("fitPlan");
+    this.planList = await query.find();
+    console.log(this.planList);
+  }
+  async loadCoachList() {
+    let query = new CloudQuery("Coach");
+    this.coachList = await query.find();
+    console.log(this.planList);
   }
-
   ngOnInit() {
     // 初始时不需要强制触发视图更新
+    this.loadPlanList()
+    this.loadCoachList()
   }
   editPlan(day: any) {
     console.log('编辑计划:', day);
-    // 这里可以打开一个编辑页面或者弹窗,修改该计划
-    // 示例:打开编辑页面
-    // this.navCtrl.navigateForward('/edit-plan', { queryParams: { plan: day } });
+
+    // 创建一个弹出框
+    this.modalCtrl.create({
+      component: EditPlanModalComponent, // 这里你需要定义一个编辑计划的 modal 组件
+      componentProps: { plan: day } // 将计划内容传递给弹出框
+    }).then(modal => {
+      modal.present();
+
+      modal.onDidDismiss().then((result) => {
+        // 如果返回的数据包含修改后的计划,就更新计划列表
+        if (result.data) {
+          const updatedPlan = result.data;
+          const index = this.planList.findIndex(item => item.id === updatedPlan.id);
+          if (index !== -1) {
+            this.planList[index] = updatedPlan;
+          }
+        }
+      });
+    });
   }
 
   // 删除计划
   deletePlan(day: any) {
     console.log('删除计划:', day);
-    const index = this.weekPlan.indexOf(day);
+    const index = this.planList.indexOf(day);
     if (index !== -1) {
-      this.weekPlan.splice(index, 1);
+      this.planList.splice(index, 1);
     }
   }
   //问诊区域
@@ -109,46 +142,61 @@ export class Tab2Page implements OnInit {
     startTask(InquireServiceTaskList)
   }
   // 聊天页面
-  openInquiry() {
+  openInquiry(coach: CloudObject) {
     localStorage.setItem("company", "E4KpGvTEto")
+    let consult = new CloudObject("fitConsultation")
+    let now = new Date();
+    let dateStr = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`
+    consult.set({
+      title: `交流记录${dateStr}-${coach?.get("name")}`,
+      coach: coach.toPointer(),
+      depart: {
+        __type: "Pointer",
+        className: "Coach",
+        objectId: coach.get("depart")?.objectId
+      },
+    })
     let options: ChatPanelOptions = {
       roleId: "2DXJkRsjXK",
       onChatInit: (chat: FmodeChat) => {
         console.log("onChatInit");
         console.log("预设角色", chat.role);
-        chat.role.set("name", "晓晓");
-        chat.role.set("title", "全科医生");
-        chat.role.set("desc", "一名亲切和蔼的门诊全科主任医生,晓晓,年龄36岁");
-        chat.role.set("tags", ["全科", "门诊"]);
-        chat.role.set("avatar", "https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/aigc/imagine/Q4Zif7fTbK-0.png")
+        chat.role.set("name", coach?.get("name"));
+        chat.role.set("title", "职业教练");
+        chat.role.set("tags", coach?.get("specialize"));
+        chat.role.set("avatar", "../../assets/images/coach2.jpg")
         chat.role.set("prompt", `
 # 角色设定
-您是一名亲切和蔼的专业的全科医生,晓晓,年龄36岁,需要完成一次完整的门诊服务
+您是${coach?.get("name")},年龄${coach?.get("age")},特长为${coach?.get("specialize")},要完成一次教练与学员之间的锻炼部位交流
 
 # 对话环节
-0.导诊(根据用户基本情况,引导挂号合适的科室)
-1.预设的问询方式(感冒问呼吸、肚子疼叩诊)
-- 打招呼,以用户自述为主
-- 当信息充足时候,确认用户症状对应的科室,并进入下一个环节
-2.拓展的问询细节
-例如:用户反映呼吸不畅,拓展出:是否咳嗽;是否感觉痛或者痒等其他需要的问题。
-- 当问询细节补充完成后进入下一个环节
-3.初步的诊断结果,并且同时列出检查检验项目
-初步诊断:确定需要有哪些进一步检查
-检查检验:获取医学客观数据
-- 等待用户提交客观数据,进入下一阶段
-4.给出诊断方案并给出处方
-- 完成处方时,请在消息结尾附带: [完成]
+0.导诊(根据用户基本情况,引导选择合适的训练计划) 
+1.预设的问询方式(根据学员自述情况进行引导)
 
+- 打招呼,以学员自述为主
+- 当信息充足时,确认学员的目标与需求,并进入下一个环节 
+2.拓展的问询细节 
+例如:学员反映想要锻炼腹肌,拓展出:目前的锻炼频率;饮食习惯;是否有受伤历史等其他需要的问题。
+- 当问询细节补充完成后进入下一个环节 
+3.初步的训练方案,并且同时列出需要的器械与注意事项 初步方案:确定需要的训练动作与频率 器械与注意事项:获取训练所需的器械信息
+- 等待学员确认并准备器械,进入下一阶段 
+4.给出详细的训练计划并提供指导
+- 完成训练计划时,请在消息结尾附带: [交流完成]
 # 开始话语
-当您准备好了,可以以一个医生的身份,向来访的用户打招呼。`);
+当您准备好了,可以以一个健身教练的身份,向来访的学员打招呼。
+
+${coach?.get("name")}:你好!欢迎来到健身房,我是${coach?.get("name")}教练。今天你想要专注锻炼哪个部位呢?或者有什么具体的健身目标吗?`);
       },
       onMessage: (chat: FmodeChat, message: FmodeChatMessage) => {
         console.log("onMessage", message)
         let content: any = message?.content
         if (typeof content == "string") {
-          if (content?.indexOf("[完成]") > -1) {
+          if (content?.indexOf("[交流完成]") > -1) {
             console.log("门诊已完成")
+            consult.set({
+              content: content // 处方内容
+            })
+            consult.save();
           }
         }
       },

+ 7 - 8
TFPower-app/src/app/tab2/test-page/test-page.component.html

@@ -94,14 +94,13 @@
           <ion-card-title>健身计划内容</ion-card-title>
         </ion-card-header>
         <ion-card-content>
-          @if(isGenerating)
-          {
-          <p>{{ generatedPlan }}</p>
-          }
-          @if(!isGenerating)
-          {
-          <fm-markdown-preview class="content-style" [content]="generatedPlan"></fm-markdown-preview>
-          }
+          <div *ngIf="isGenerating">
+            <ion-spinner name="crescent"></ion-spinner>
+            <p>生成中...</p>
+          </div>
+          <div *ngIf="!isGenerating">
+            <fm-markdown-preview class="content-style" [content]="generatedPlan"></fm-markdown-preview>
+          </div>
         </ion-card-content>
       </ion-card>
     </div>

+ 52 - 6
TFPower-app/src/app/tab2/test-page/test-page.component.ts

@@ -6,6 +6,8 @@ import { TagInputComponent } from '../tag-input/tag-input.component';
 import { addIcons } from 'ionicons';
 import { barbellOutline, personOutline, square, alarmOutline } from 'ionicons/icons';
 import { FmodeChatCompletion, MarkdownPreviewModule } from 'fmode-ng';
+import { LoadingController } from '@ionic/angular';
+import { AlertController } from '@ionic/angular/standalone';
 
 @Component({
   selector: 'app-test-page',
@@ -28,7 +30,9 @@ export class TestPageComponent implements OnInit {
   isGenerating: boolean = false;  // 是否正在生成计划
   messageList: any[] = [];  // 存储消息列表
 
-  constructor(private toastController: ToastController) {
+  constructor(private toastController: ToastController,
+    private loadingController: LoadingController,
+    private alertController: AlertController) {
     addIcons({ personOutline, barbellOutline, alarmOutline, square });
   }
 
@@ -81,10 +85,15 @@ export class TestPageComponent implements OnInit {
       await this.showToast('身高或体重为空,无法生成个人健身计划');
       return; // 退出函数,不继续生成健身计划
     }
-
+    const loading = await this.loadingController.create({
+      message: '生成计划中...',
+      duration: 30000 // 加载时长可以根据实际需求调整
+    });
+    await loading.present();
     this.generatedPlan = '';  // 清空之前的结果
     this.messageList = [];  // 清空旧的消息列表,避免重复内容
     this.isGenerating = true;
+
     console.log('生成健身计划', {
       tags: this.selectedTags,
       exercisePreference: this.exercisePreference,
@@ -102,12 +111,49 @@ export class TestPageComponent implements OnInit {
       { role: "system", content: '' },
       { role: "user", content: prompt }
     ]);
+
     messageList.sendCompletion().subscribe((message: any) => {
-      console.log(message.content)
-      this.generatedPlan = message.content
+      console.log(message.content);
+      this.generatedPlan = message.content;
+
       if (message?.complete) {
-        this.isGenerating = false
+        this.isGenerating = false;
+        loading.dismiss();
+
+        // 弹出框确认生成的计划
+        this.showConfirmationAlert();
       }
-    })
+    });
+  }
+  async showConfirmationAlert() {
+    const alert = await this.alertController.create({
+      header: '确认生成计划',
+      message: `您的健身计划是:<br><pre>${this.generatedPlan}</pre>`,
+      buttons: [
+        {
+          text: '放弃',
+          role: 'cancel',
+          handler: () => {
+            console.log('用户选择放弃');
+          }
+        },
+        {
+          text: '确认',
+          handler: () => {
+            // 这里可以调用 API 将数据存到数据库
+            console.log('用户确认', this.generatedPlan);
+            this.savePlanToDatabase(this.generatedPlan);
+          }
+        }
+      ]
+    });
+    await alert.present();
+  }
+
+  // 假设有一个保存计划到数据库的方法
+  savePlanToDatabase(plan: string) {
+    // 这里是伪代码,根据你后端的实际 API 进行调整
+    // 例如:调用后台接口将健身计划存到数据库
+    console.log('计划保存到数据库', plan);
   }
 }

BIN
TFPower-app/src/assets/images/ache.jpg


BIN
TFPower-app/src/assets/images/action.png


BIN
TFPower-app/src/assets/images/coach1.jpg


BIN
TFPower-app/src/assets/images/coach2.jpg


+ 377 - 0
TFPower-app/src/lib/ncloud.ts

@@ -0,0 +1,377 @@
+// CloudObject.ts
+export class CloudObject {
+    className: string;
+    id: string | null = null;
+    createdAt: any;
+    updatedAt: any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = null;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    queryParams: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    include(...fileds: string[]) {
+        this.queryParams["include"] = fileds;
+    }
+    greaterThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        this.queryParams["where"][key] = value;
+    }
+
+    async get(id: string) {
+        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        return json || {};
+    }
+
+    async find() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        let queryStr = ``
+        Object.keys(this.queryParams).forEach(key => {
+            let paramStr = JSON.stringify(this.queryParams[key]);
+            if (key == "include") {
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if (queryStr) {
+                url += `${key}=${paramStr}`;
+            } else {
+                url += `&${key}=${paramStr}`;
+            }
+        })
+        // if (Object.keys(this.queryParams["where"]).length) {
+
+        // }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item: any) => this.dataToObj(item))
+        return objList || [];
+    }
+
+    async first() {
+        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists: any): CloudObject {
+        let existsObject = new CloudObject(this.className);
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if (userCacheStr) {
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken: string | null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        return this;
+        // const response = await fetch(`http://dev.fmode.cn:1337/parse/users/me`, {
+        //     headers: {
+        //         "x-parse-application-id": "dev",
+        //         "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+        //     },
+        //     method: "GET"
+        // });
+
+        // const result = await response?.json();
+        // if (result?.error) {
+        //     console.error(result?.error);
+        //     return null;
+        // }
+        // return result;
+    }
+
+    /** 登录 */
+    async login(username: string, password: string): Promise<CloudUser | null> {
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/login`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify({ username, password }),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/logout`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken
+            },
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return false;
+        }
+
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = null;
+        this.sessionToken = null;
+        this.data = {};
+        return true;
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(`http://dev.fmode.cn:1337/parse/users`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(userData),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        return this;
+    }
+
+    override async save() {
+        let method = "POST";
+        let url = `http://dev.fmode.cn:1337/parse/users`;
+
+        // 更新用户信息
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        let data: any = JSON.parse(JSON.stringify(this.data))
+        delete data.createdAt
+        delete data.updatedAt
+        delete data.ACL
+        delete data.objectId
+        const body = JSON.stringify(data);
+        let headersOptions: any = {
+            "content-type": "application/json;charset=UTF-8",
+            "x-parse-application-id": "dev",
+            "x-parse-session-token": this.sessionToken, // 添加sessionToken以进行身份验证
+        }
+        const response = await fetch(url, {
+            headers: headersOptions,
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(this.data))
+        return this;
+    }
+}

+ 26 - 0
TFPower-app/src/lib/user/modal-user-edit/modal-user-edit.component.html

@@ -0,0 +1,26 @@
+<!-- 用户登录状态 -->
+<ion-card>
+  <ion-card-header>
+    <ion-card-title>
+      用户名:{{currentUser?.get("username")}}
+    </ion-card-title>
+    <ion-card-subtitle>请输入您的详细资料</ion-card-subtitle>
+   </ion-card-header>
+ <ion-card-content>
+
+   <ion-item>
+     <ion-input [value]="userData['realname']" (ionChange)="userDataChange('realname',$event)" label="姓名" placeholder="请您输入真实姓名"></ion-input>
+   </ion-item>
+   <ion-item>
+     <ion-input type="number" [value]="userData['age']" (ionChange)="userDataChange('age',$event)" label="年龄" placeholder="请您输入年龄"></ion-input>
+    </ion-item>
+  <ion-item>
+     <ion-input [value]="userData['gender']" (ionChange)="userDataChange('gender',$event)" label="性别" placeholder="请您输入男/女"></ion-input>
+    </ion-item>
+
+   <ion-button expand="block" (click)="save()">保存</ion-button>
+   <ion-button expand="block" (click)="cancel()">取消</ion-button>
+ 
+
+</ion-card-content>
+</ion-card>

+ 0 - 0
TFPower-app/src/lib/user/modal-user-edit/modal-user-edit.component.scss


+ 22 - 0
TFPower-app/src/lib/user/modal-user-edit/modal-user-edit.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { ModalUserEditComponent } from './modal-user-edit.component';
+
+describe('ModalUserEditComponent', () => {
+  let component: ModalUserEditComponent;
+  let fixture: ComponentFixture<ModalUserEditComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [ModalUserEditComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ModalUserEditComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 65 - 0
TFPower-app/src/lib/user/modal-user-edit/modal-user-edit.component.ts

@@ -0,0 +1,65 @@
+import { Input, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonInput, IonItem, IonSegment, IonSegmentButton, IonLabel } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-modal-user-edit',
+  templateUrl: './modal-user-edit.component.html',
+  styleUrls: ['./modal-user-edit.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
+    IonInput,IonItem,
+    IonSegment,IonSegmentButton,IonLabel
+  ],
+})
+export class ModalUserEditComponent  implements OnInit {
+
+  currentUser:CloudUser|undefined
+  userData:any = {}
+  userDataChange(key:string,ev:any){
+    let value = ev?.detail?.value
+    if(value){
+      this.userData[key] = value
+    }
+  }
+  constructor(private modalCtrl:ModalController) { 
+    this.currentUser = new CloudUser();
+    this.userData = this.currentUser.data;
+  }
+
+  ngOnInit() {}
+
+  async save(){
+    Object.keys(this.userData).forEach(key=>{
+      if(key=="age"){
+        this.userData[key] = Number(this.userData[key])
+      }
+    })
+
+    this.currentUser?.set(this.userData)
+    await this.currentUser?.save()
+    this.modalCtrl.dismiss(this.currentUser,"confirm")
+  }
+  cancel(){
+    this.modalCtrl.dismiss(null,"cancel")
+
+  }
+}
+
+export async function openUserEditModal(modalCtrl:ModalController):Promise<CloudUser|null>{
+  const modal = await modalCtrl.create({
+    component: ModalUserEditComponent,
+    breakpoints:[0.7,1.0],
+    initialBreakpoint:0.7
+  });
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+  return null
+}

+ 38 - 0
TFPower-app/src/lib/user/modal-user-login/modal-user-login.component.html

@@ -0,0 +1,38 @@
+<!-- 用户登录状态 -->
+<ion-card>
+     <ion-card-header>
+       <ion-card-title>
+        <ion-segment [value]="type" (ionChange)="typeChange($event)">
+          <ion-segment-button value="login">
+            <ion-label>登录</ion-label>
+          </ion-segment-button>
+          <ion-segment-button value="signup">
+            <ion-label>注册</ion-label>
+          </ion-segment-button>
+        </ion-segment>
+       </ion-card-title>
+       <ion-card-subtitle>请输入账号密码</ion-card-subtitle>
+      </ion-card-header>
+    <ion-card-content>
+
+      <ion-item>
+        <ion-input [value]="username" (ionChange)="usernameChange($event)" label="账号" placeholder="请您输入账号/手机号"></ion-input>
+      </ion-item>
+      <ion-item>
+        <ion-input [value]="password" (ionChange)="passwordChange($event)" label="密码" type="password" value="password"></ion-input>
+      </ion-item>
+      @if(type=="signup"){
+        <ion-item>
+          <ion-input [value]="password2" (ionChange)="password2Change($event)" label="密码二次" type="password" value="password"></ion-input>
+        </ion-item>
+      }
+    
+      @if(type=="login"){
+        <ion-button expand="block" (click)="login()">登录</ion-button>
+      }
+      @if(type=="signup"){
+        <ion-button expand="block" (click)="signup()">注册</ion-button>
+      }
+
+  </ion-card-content>
+</ion-card>

+ 0 - 0
TFPower-app/src/lib/user/modal-user-login/modal-user-login.component.scss


+ 22 - 0
TFPower-app/src/lib/user/modal-user-login/modal-user-login.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { ModalUserLoginComponent } from './modal-user-login.component';
+
+describe('ModalUserLoginComponent', () => {
+  let component: ModalUserLoginComponent;
+  let fixture: ComponentFixture<ModalUserLoginComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [ModalUserLoginComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(ModalUserLoginComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 92 - 0
TFPower-app/src/lib/user/modal-user-login/modal-user-login.component.ts

@@ -0,0 +1,92 @@
+import { Input, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonInput, IonItem, IonSegment, IonSegmentButton, IonLabel } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-modal-user-login',
+  templateUrl: './modal-user-login.component.html',
+  styleUrls: ['./modal-user-login.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
+    IonInput,IonItem,
+    IonSegment,IonSegmentButton,IonLabel
+  ],
+})
+export class ModalUserLoginComponent  implements OnInit {
+  @Input()
+  type:"login"|"signup" = "login"
+  typeChange(ev:any){
+    this.type = ev?.detail?.value || ev?.value || 'login'
+  }
+  username:string = ""
+  usernameChange(ev:any){
+    console.log(ev)
+    this.username = ev?.detail?.value
+  }
+  password:string = ""
+  passwordChange(ev:any){
+    this.password = ev?.detail?.value
+  }
+  password2:string = ""
+  password2Change(ev:any){
+    this.password2 = ev?.detail?.value
+  }
+  constructor(private modalCtrl:ModalController) { }
+
+  ngOnInit() {}
+
+  async login(){
+    if(!this.username || !this.password){
+      console.log("请输入完整")
+      return
+    }
+    let user:any = new CloudUser();
+    user = await user.login(this.username,this.password);
+    if(user?.id){
+       this.modalCtrl.dismiss(user,"confirm")
+    }else{
+      console.log("登录失败")
+    }
+  }
+
+  async signup(){
+    if(!this.username || !this.password || !this.password2){
+      console.log("请输入完整")
+      return
+    }
+    if(this.password!=this.password2){
+      console.log("两次密码不符,请修改")
+      return
+    }
+
+    let user:any = new CloudUser();
+    user = await user.signUp(this.username,this.password);
+    if(user){
+      this.type = "login"
+      console.log("注册成功请登录")
+    }
+  }
+
+}
+
+
+export async function openUserLoginModal(modalCtrl:ModalController,type:"login"|"signup"="login"):Promise<CloudUser|null>{
+  const modal = await modalCtrl.create({
+    component: ModalUserLoginComponent,
+    componentProps:{
+      type:type
+    },
+    breakpoints:[0.5,0.7],
+    initialBreakpoint:0.5
+  });
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+  return null
+}

+ 350 - 95
TFPower-server/lib/migration/data.js

@@ -1,97 +1,352 @@
-module.exports.DoctorList = [
-    {
-      "objectId": "doc001",
-      "name": "张伟",
-      "title": "主任医师",
-      "desc": "拥有20年内科临床经验,擅长心血管疾病的治疗",
-      "gender": "男",
-      "age": 45,
-      "specialty": "内科",
-      "qualifications": ["医学博士,内科专科医生"],
-      "depart": {
-        "objectId": "dept001"
-      }
-    },
-    {
-      "objectId": "doc002",
-      "avatar":"https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/aigc/imagine/Q4Zif7fTbK-0.png",
-      "name": "李娜",
-      "title": "主任医师",
-      "desc": "外科领域专家,擅长微创手术",
-      "gender": "女",
-      "age": 50,
-      "specialty": "外科",
-      "qualifications": ["外科专科医生,硕士研究生"],
-      "depart": {
-        "objectId": "dept002"
-      }
-    },
-    {
-      "objectId": "doc003",
-      "name": "王芳",
-      "title": "主任医师",
-      "desc": "儿童健康专家,擅长儿童生长发育",
-      "gender": "女",
-      "age": 40,
-      "specialty": "儿科",
-      "qualifications": ["儿科专科医生,医学硕士"],
-      "depart": {
-        "objectId": "dept003"
-      }
-    },
-    {
-      "objectId": "doc004",
-      "name": "刘强",
-      "title": "主任医师",
-      "desc": "妇产科专家,专注于高危妊娠管理",
-      "gender": "男",
-      "age": 48,
-      "specialty": "妇产科",
-      "qualifications": ["妇产科专科医生,博士研究生"],
-      "depart": {
-        "objectId": "dept004"
-      }
-    },
-    {
-      "objectId": "doc005",
-      "name": "陈静",
-      "title": "主任医师",
-      "desc": "神经科专家,擅长癫痫和头痛的治疗",
-      "gender": "女",
-      "age": 42,
-      "specialty": "神经科",
-      "qualifications": ["神经科专科医生,医学博士"],
-      "depart": {
-        "objectId": "dept005"
-      }
-    }
-  ]
+// module.exports.DoctorList = [
+//     {
+//       "objectId": "doc001",
+//       "name": "张伟",
+//       "title": "主任医师",
+//       "desc": "拥有20年内科临床经验,擅长心血管疾病的治疗",
+//       "gender": "男",
+//       "age": 45,
+//       "specialty": "内科",
+//       "qualifications": ["医学博士,内科专科医生"],
+
+//     },
+//     {
+//       "objectId": "doc002",
+//       "avatar":"https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/aigc/imagine/Q4Zif7fTbK-0.png",
+//       "name": "李娜",
+//       "title": "主任医师",
+//       "desc": "外科领域专家,擅长微创手术",
+//       "gender": "女",
+//       "age": 50,
+//       "specialty": "外科",
+//       "qualifications": ["外科专科医生,硕士研究生"],
+//       "depart": {
+//         "objectId": "dept002"
+//       }
+//     },
+//     {
+//       "objectId": "doc003",
+//       "name": "王芳",
+//       "title": "主任医师",
+//       "desc": "儿童健康专家,擅长儿童生长发育",
+//       "gender": "女",
+//       "age": 40,
+//       "specialty": "儿科",
+//       "qualifications": ["儿科专科医生,医学硕士"],
+//       "depart": {
+//         "objectId": "dept003"
+//       }
+//     },
+//     {
+//       "objectId": "doc004",
+//       "name": "刘强",
+//       "title": "主任医师",
+//       "desc": "妇产科专家,专注于高危妊娠管理",
+//       "gender": "男",
+//       "age": 48,
+//       "specialty": "妇产科",
+//       "qualifications": ["妇产科专科医生,博士研究生"],
+//       "depart": {
+//         "objectId": "dept004"
+//       }
+//     },
+//     {
+//       "objectId": "doc005",
+//       "name": "陈静",
+//       "title": "主任医师",
+//       "desc": "神经科专家,擅长癫痫和头痛的治疗",
+//       "gender": "女",
+//       "age": 42,
+//       "specialty": "神经科",
+//       "qualifications": ["神经科专科医生,医学博士"],
+//       "depart": {
+//         "objectId": "dept005"
+//       }
+//     }
+//   ]
+
+// module.exports.DepartList = [
 
-module.exports.DepartList = [
+//       {
+//         "objectId": "dept001",
+//         "name": "内科",
+//         "desc": "负责内科疾病的诊断和治疗"
+//       },
+//       {
+//         "objectId": "dept002",
+//         "name": "外科",
+//         "desc": "负责外科手术和相关疾病的治疗"
+//       },
+//       {
+//         "objectId": "dept003",
+//         "name": "儿科",
+//         "desc": "专注于儿童疾病的预防和治疗"
+//       },
+//       {
+//         "objectId": "dept004",
+//         "name": "妇产科",
+//         "desc": "负责女性生殖系统及相关疾病的治疗"
+//       },
+//       {
+//         "objectId": "dept005",
+//         "name": "神经科",
+//         "desc": "专注于神经系统疾病的诊断和治疗"
+//       }
+//     ]
+// module.exports.DoctorList = [
+//     {
+//       "objectId": "doc001",
+//       "name": "张伟",
+//       "title": "主任医师",
+//       "desc": "拥有20年内科临床经验,擅长心血管疾病的治疗",
+//       "gender": "男",
+//       "age": 45,
+//       "specialty": "内科",
+//       "qualifications": ["医学博士,内科专科医生"],
+//       "depart": {
+//         "objectId": "dept001"
+//       }
+//     },
+//     {
+//       "objectId": "doc002",
+//       "avatar":"https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/aigc/imagine/Q4Zif7fTbK-0.png",
+//       "name": "李娜",
+//       "title": "主任医师",
+//       "desc": "外科领域专家,擅长微创手术",
+//       "gender": "女",
+//       "age": 50,
+//       "specialty": "外科",
+//       "qualifications": ["外科专科医生,硕士研究生"],
+//       "depart": {
+//         "objectId": "dept002"
+//       }
+//     },
+//     {
+//       "objectId": "doc003",
+//       "name": "王芳",
+//       "title": "主任医师",
+//       "desc": "儿童健康专家,擅长儿童生长发育",
+//       "gender": "女",
+//       "age": 40,
+//       "specialty": "儿科",
+//       "qualifications": ["儿科专科医生,医学硕士"],
+//       "depart": {
+//         "objectId": "dept003"
+//       }
+//     },
+//     {
+//       "objectId": "doc004",
+//       "name": "刘强",
+//       "title": "主任医师",
+//       "desc": "妇产科专家,专注于高危妊娠管理",
+//       "gender": "男",
+//       "age": 48,
+//       "specialty": "妇产科",
+//       "qualifications": ["妇产科专科医生,博士研究生"],
+//       "depart": {
+//         "objectId": "dept004"
+//       }
+//     },
+//     {
+//       "objectId": "doc005",
+//       "name": "陈静",
+//       "title": "主任医师",
+//       "desc": "神经科专家,擅长癫痫和头痛的治疗",
+//       "gender": "女",
+//       "age": 42,
+//       "specialty": "神经科",
+//       "qualifications": ["神经科专科医生,医学博士"],
+//       "depart": {
+//         "objectId": "dept005"
+//       }
+//     }
+//   ]
 
-      {
-        "objectId": "dept001",
-        "name": "内科",
-        "desc": "负责内科疾病的诊断和治疗"
-      },
-      {
-        "objectId": "dept002",
-        "name": "外科",
-        "desc": "负责外科手术和相关疾病的治疗"
-      },
-      {
-        "objectId": "dept003",
-        "name": "儿科",
-        "desc": "专注于儿童疾病的预防和治疗"
-      },
-      {
-        "objectId": "dept004",
-        "name": "妇产科",
-        "desc": "负责女性生殖系统及相关疾病的治疗"
-      },
-      {
-        "objectId": "dept005",
-        "name": "神经科",
-        "desc": "专注于神经系统疾病的诊断和治疗"
-      }
-    ]
+// module.exports.DepartList = [
+
+//       {
+//         "objectId": "dept001",
+//         "name": "内科",
+//         "desc": "负责内科疾病的诊断和治疗"
+//       },
+//       {
+//         "objectId": "dept002",
+//         "name": "外科",
+//         "desc": "负责外科手术和相关疾病的治疗"
+//       },
+//       {
+//         "objectId": "dept003",
+//         "name": "儿科",
+//         "desc": "专注于儿童疾病的预防和治疗"
+//       },
+//       {
+//         "objectId": "dept004",
+//         "name": "妇产科",
+//         "desc": "负责女性生殖系统及相关疾病的治疗"
+//       },
+//       {
+//         "objectId": "dept005",
+//         "name": "神经科",
+//         "desc": "专注于神经系统疾病的诊断和治疗"
+//       }
+//     ]
+module.exports.UserList = [
+  {
+    "objectId": "user001",
+    "name": "张伟",
+    "gender": "男",
+    "age": 30,
+    "height": 175, // 身高(厘米)  
+    "weight": 70,  // 体重(千克)  
+    "bmi": 22.86,  // 体重指数  
+    "fitnessGoals": ["增肌", "提高耐力"], // 健身目标  
+    "phone": "13800000001",
+    "email": "zhangwei@example.com",
+    "depart": {
+      "objectId": "plan001"
+    }
+  },
+  {
+    "objectId": "user002",
+    "name": "李娜",
+    "gender": "女",
+    "age": 28,
+    "height": 160,
+    "weight": 55,
+    "bmi": 21.48,
+    "fitnessGoals": ["减脂", "塑形"],
+    "phone": "13800000002",
+    "email": "lina@example.com",
+    "depart": {
+      "objectId": "plan002"
+    }
+  },
+  {
+    "objectId": "user003",
+    "name": "王芳",
+    "gender": "女",
+    "age": 35,
+    "height": 165,
+    "weight": 60,
+    "bmi": 22.04,
+    "fitnessGoals": ["增强力量"],
+    "phone": "13800000003",
+    "email": "wangfang@example.com",
+    "depart": {
+      "objectId": "plan003"
+    }
+  },
+  {
+    "objectId": "user004",
+    "name": "刘强",
+    "gender": "男",
+    "age": 40,
+    "height": 180,
+    "weight": 80,
+    "bmi": 24.69,
+    "fitnessGoals": ["提高灵活性", "增强耐力"],
+    "phone": "13800000004",
+    "email": "liuqiang@example.com",
+    "depart": {
+      "objectId": "plan004"
+    }
+  },
+  {
+    "objectId": "user005",
+    "name": "陈静",
+    "gender": "女",
+    "age": 25,
+    "height": 158,
+    "weight": 50,
+    "bmi": 20.03,
+    "fitnessGoals": ["保持健康"],
+    "phone": "13800000005",
+    "email": "chenjing@example.com",
+    "depart": {
+      "objectId": "plan005"
+    }
+  }
+];
+module.exports.PlanList = [
+  {
+    "objectId": "plan001",
+    "date": "2024-12-20", // 计划日期  
+    "trainingPart": "胸部", // 训练部位  
+    "trainingItems": [
+      { "item": "卧推", "sets": 4, "reps": 10 }, // 训练项目1  
+      { "item": "哑铃飞鸟", "sets": 3, "reps": 12 }, // 训练项目2  
+      { "item": "俯卧撑", "sets": 3, "reps": 15 }, // 训练项目3  
+      { "item": "拉力器扩胸", "sets": 3, "reps": 12 } // 训练项目4  
+    ]
+  },
+  {
+    "objectId": "plan002",
+    "date": "2024-12-22",
+    "trainingPart": "下肢",
+    "trainingItems": [
+      { "item": "深蹲", "sets": 4, "reps": 10 },
+      { "item": "腿举", "sets": 3, "reps": 12 },
+      { "item": "弓步", "sets": 3, "reps": 10 },
+      { "item": "小腿提踵", "sets": 3, "reps": 15 }
+    ]
+  },
+  {
+    "objectId": "plan003",
+    "date": "2024-12-25",
+    "trainingPart": "背部",
+    "trainingItems": [
+      { "item": "引体向上", "sets": 4, "reps": 8 },
+      { "item": "划船", "sets": 3, "reps": 10 },
+      { "item": "背部伸展", "sets": 3, "reps": 12 },
+      { "item": "拉力器划船", "sets": 3, "reps": 12 }
+    ]
+  },
+  {
+    "objectId": "plan004",
+    "date": "2024-12-28",
+    "trainingPart": "全身",
+    "trainingItems": [
+      { "item": "HIIT训练", "sets": 4, "reps": 30 }, // 30秒高强度  
+      { "item": "核心训练", "sets": 3, "reps": 15 },
+      { "item": "有氧运动", "sets": 1, "reps": 20 }, // 20分钟  
+      { "item": "拉伸", "sets": 1, "reps": 10 } // 10分钟  
+    ]
+  },
+  {
+    "objectId": "plan005",
+    "date": "2024-12-30",
+    "trainingPart": "上肢",
+    "trainingItems": [
+      { "item": "哑铃弯举", "sets": 4, "reps": 10 },
+      { "item": "三头肌下压", "sets": 3, "reps": 12 },
+      { "item": "肩推", "sets": 3, "reps": 10 },
+      { "item": "侧平举", "sets": 3, "reps": 12 }
+    ]
+  }
+];
+module.exports.CoachList = [
+  {
+    "objectId": "coach001",
+    "qualification": "国家认证健身教练",
+    "gender": "男",
+    "name": "李明",
+    "specialize": "力量训练",
+    "age": 32
+  },
+  {
+    "objectId": "coach002",
+    "qualification": "国际健身教练认证",
+    "gender": "女",
+    "name": "张婷",
+    "specialize": "灵活性训练",
+    "age": 29
+  },
+  {
+    "objectId": "coach003",
+    "qualification": "高级私人教练",
+    "gender": "男",
+    "name": "王强",
+    "specialize": "综合训练",
+    "age": 38
+  }
+];

+ 20 - 18
TFPower-server/lib/migration/import-data.js

@@ -1,51 +1,53 @@
-const { CloudQuery, CloudObject } = require("../lib/ncloud");
-const { DepartList, DoctorList } = require("./data");
+const { CloudQuery, CloudObject } = require("../ncloud");
+const { UserList, PlanList, CoachList } = require("./data");
 inportDapartAndDoctor()
 
 DataMap = {
-    Doctor:{},
-    Department:{}
+    fitUser: {},
+    fitPlan: {},
+    Coach: {}
 }
 
-async function inportDapartAndDoctor(){
+async function inportDapartAndDoctor() {
     // 导入科室数据
-    let departList = DepartList
+    let departList = CoachList
     for (let index = 0; index < departList.length; index++) {
         let depart = departList[index];
-        depart = await importObject("Department",depart)
+        depart = await importObject("Coach", depart)
     }
+
     // 导入医生数据
-    let doctorList = DoctorList
-    for (let index = 0; index < doctorList.length; index++) {
-        let doctor = doctorList[index];
-        doctor = await importObject("Doctor",doctor)
-    }
+    // let doctorList = PlanList
+    // for (let index = 0; index < doctorList.length; index++) {
+    //     let doctor = doctorList[index];
+    //     doctor = await importObject("fitPlan", doctor)
+    // }
     // console.log(DataMap["Doctor"])
 }
 
-async function importObject(className,data){
+async function importObject(className, data) {
 
     // 查重 srcId 数据源列表中的objectId并非数据库生成的唯一ID,因此需要有一个srcId字段进行记录,并查重
     let query = new CloudQuery(className)
     let srcId = data.objectId
-    query.equalTo("srcId",srcId)
+    query.equalTo("srcId", srcId)
     let importObj = await query.first()
     console.log(importObj)
 
     // 导入
     // 导入前批量处理Pointer类型数据,进行重定向
-    Object.keys(data)?.forEach(key=>{
+    Object.keys(data)?.forEach(key => {
         let field = data[key]
         let srcId = field?.objectId
-        if(srcId){ // 是数组字段
-            if(key=="depart"){
+        if (srcId) { // 是数组字段
+            if (key == "depart") {
                 data[key] = DataMap?.["Department"]?.[srcId]?.toPointer();
             }
         }
     })
 
     // 若未添加,则创建新对象并保存
-    if(!importObj?.id){
+    if (!importObj?.id) {
         importObj = new CloudObject(className)
     }