Browse Source

update:some new pages

xukang 4 months ago
parent
commit
b8f1ebb103
24 changed files with 1311 additions and 20 deletions
  1. 4 1
      TFPower-app/angular.json
  2. 1 0
      TFPower-app/src/app/app.routes.ts
  3. 86 10
      TFPower-app/src/app/tab2/tab2.page.html
  4. 141 0
      TFPower-app/src/app/tab2/tab2.page.scss
  5. 67 6
      TFPower-app/src/app/tab2/tab2.page.ts
  6. 23 0
      TFPower-app/src/app/tab2/tag-input/tag-input.component.html
  7. 36 0
      TFPower-app/src/app/tab2/tag-input/tag-input.component.scss
  8. 24 0
      TFPower-app/src/app/tab2/tag-input/tag-input.component.spec.ts
  9. 39 0
      TFPower-app/src/app/tab2/tag-input/tag-input.component.ts
  10. 9 0
      TFPower-app/src/app/tab2/test-chat-panel/test-chat-panel.component.html
  11. 3 0
      TFPower-app/src/app/tab2/test-chat-panel/test-chat-panel.component.scss
  12. 22 0
      TFPower-app/src/app/tab2/test-chat-panel/test-chat-panel.component.spec.ts
  13. 243 0
      TFPower-app/src/app/tab2/test-chat-panel/test-chat-panel.component.ts
  14. 109 0
      TFPower-app/src/app/tab2/test-page/test-page.component.html
  15. 38 0
      TFPower-app/src/app/tab2/test-page/test-page.component.scss
  16. 22 0
      TFPower-app/src/app/tab2/test-page/test-page.component.spec.ts
  17. 113 0
      TFPower-app/src/app/tab2/test-page/test-page.component.ts
  18. 3 2
      TFPower-app/src/app/tabs/tabs.page.html
  19. 2 1
      TFPower-app/src/app/tabs/tabs.page.ts
  20. 6 0
      TFPower-app/src/app/tabs/tabs.routes.ts
  21. BIN
      TFPower-app/src/assets/images/wisefitness-high-resolution-logo.png
  22. 97 0
      TFPower-server/lib/migration/data.js
  23. 58 0
      TFPower-server/lib/migration/import-data.js
  24. 165 0
      TFPower-server/lib/ncloud.js

+ 4 - 1
TFPower-app/angular.json

@@ -126,7 +126,10 @@
     }
   },
   "cli": {
-    "schematicCollections": ["@ionic/angular-toolkit"]
+    "schematicCollections": [
+      "@ionic/angular-toolkit"
+    ],
+    "analytics": "a63e6579-9007-4bce-b474-6f3b834c3a22"
   },
   "schematics": {
     "@ionic/angular-toolkit:component": {

+ 1 - 0
TFPower-app/src/app/app.routes.ts

@@ -5,4 +5,5 @@ export const routes: Routes = [
     path: '',
     loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
   },
+
 ];

+ 86 - 10
TFPower-app/src/app/tab2/tab2.page.html

@@ -1,17 +1,93 @@
-<ion-header [translucent]="true">
+<ion-header sticky>
   <ion-toolbar>
-    <ion-title>
-
-    </ion-title>
+    <ion-segment [(ngModel)]="selectedTab" color="primary" class="custom-segment">
+      <ion-segment-button value="checkin">
+        <ion-icon aria-hidden="true" name="calendar"></ion-icon>
+        打卡
+      </ion-segment-button>
+      <ion-segment-button value="plan">
+        <ion-icon name="checkmark-circle" aria-hidden="true"></ion-icon>
+        计划
+      </ion-segment-button>
+      <ion-segment-button value="consultation">
+        <ion-icon name="help-circle" aria-hidden="true"></ion-icon>
+        问诊
+      </ion-segment-button>
+    </ion-segment>
   </ion-toolbar>
 </ion-header>
 
 <ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">Tab 2</ion-title>
-    </ion-toolbar>
-  </ion-header>
+  <div style="height: 56px;"></div>
+
+  <!-- 打卡 -->
+  <div *ngIf="selectedTab === 'checkin'" class="checkin-container">
+    <p class="section-title">今天的打卡任务</p>
+    <p>在这里打卡,开始你的一天健身之旅吧!</p>
+  </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-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-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="2" 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-button expand="full" color="primary" (click)="goToPage('test-page')">重新生成计划</ion-button>
+  </div>
+
+  <!-- 问诊 -->
+  <div *ngIf="selectedTab === 'consultation'">
+    <p class="section-title">健康问诊</p>
+    <p>有任何不适,可以随时询问!</p>
+
+    <!-- 动作纠正按钮 -->
+    <ion-button expand="full" color="primary" (click)="goToPage('correct-movement')">
+      <ion-icon slot="start" name="medkit"></ion-icon> 动作纠正
+    </ion-button>
 
-  <app-explore-container name="Tab 2 page"></app-explore-container>
+    <!-- AI教练按钮 -->
+    <ion-button expand="full" color="secondary" (click)="goToPage('ai-coach')">
+      <ion-icon slot="start" name="school"></ion-icon> 健身教练
+    </ion-button>
+  </div>
 </ion-content>

+ 141 - 0
TFPower-app/src/app/tab2/tab2.page.scss

@@ -0,0 +1,141 @@
+ion-header {
+  position: fixed;
+  z-index: 10; /* 确保它在其他内容之上 */
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 56px; /* 设置固定头部高度 */
+}
+
+.custom-segment {
+  width: 60%; 
+}
+
+ion-icon {
+  --ionicon-stroke-width: 16px;
+  margin-bottom: 5px;
+}
+
+ion-segment-button::part(indicator-background) {
+  background: #719e8c;
+}
+
+ion-segment-button.md::part(native) {
+  color: #000;
+  font-size: 12px;
+}
+
+.segment-button-checked.md::part(native) {
+  color: #08a391;
+  font-size: 20px;
+}
+
+ion-segment-button.md::part(indicator-background) {
+  height: 2px;
+}
+
+ion-content {
+  padding-top: 56px; /* 给内容区域加上与header相等的顶部间距 */
+}
+ion-content {
+  background-color: #f9f9f9;
+  padding-top: 56px;
+}
+
+/* 给表格增加阴影和圆角 */
+.plan-table ion-grid {
+  margin-top: 20px;
+  border-radius: 10px;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 表格的列 */
+.plan-column {
+  background-color: #ffffff;
+  padding: 10px;
+  border: 1px solid #ddd;
+  border-radius: 8px;
+  text-align: center;
+}
+
+/* 表格的标题 */
+.grid-header {
+  font-weight: bold;
+  background-color: #f0f0f0;
+  text-align: center;
+  padding: 12px;
+  border: 1px solid #ddd;
+  border-radius: 8px;
+}
+ion-button[color="success"]::part(native)
+{
+   justify-content: center;
+  align-items: center;
+color: black !important; /* 强制修改文本颜色为黑色 */
+}
+ion-button[color="danger"]::part(native)
+{
+   justify-content: center;
+  align-items: center;
+color: black !important; /* 强制修改文本颜色为黑色 */
+}
+/* 按钮 */
+ion-button[color="success"] {
+  background-color: #4CAF50; /* 绿色背景 */
+  border-radius: 8px;
+  padding: 5px 10px;
+  font-weight: bold;
+   justify-content: center;
+  align-items: center;
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
+  transition: background-color 0.3s ease, transform 0.3s ease;
+}
+
+ion-button[color="success"]:hover {
+  background-color: #45a049; /* 悬停时颜色变深 */
+  transform: scale(1.05); /* 悬停时按钮放大 */
+}
+
+ion-button[color="danger"] {
+  background-color: #f44336; /* 红色背景 */
+  border-radius: 8px;
+  padding: 5px 10px;
+  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 {
+  background-color: #d32f2f; /* 悬停时颜色变深 */
+  transform: scale(1.05); /* 悬停时按钮放大 */
+}
+/* 优化按钮在行中的显示位置 */
+ion-buttons {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+/* 给每个section加标题 */
+.section-title {
+  font-size: 20px;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20px;
+}
+
+/* 打卡部分的设计 */
+.checkin-container p {
+  font-size: 18px;
+  color: #555;
+}
+
+/* 计划表格的列间距和背景 */
+.plan-table ion-row {
+  margin-bottom: 5px;
+}
+
+/* 表格行的底部间距 */
+.plan-table ion-col {
+  margin-bottom: 2px;
+}
+

+ 67 - 6
TFPower-app/src/app/tab2/tab2.page.ts

@@ -1,16 +1,77 @@
-import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
-import { ExploreContainerComponent } from '../explore-container/explore-container.component';
+import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
+import { Router } from '@angular/router';
+import { addIcons } from 'ionicons';
+import { checkmarkCircle, calendar, helpCircle } 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 } from '@ionic/angular/standalone';
 
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
   styleUrls: ['tab2.page.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent]
+  imports: [
+    IonButtons,
+    IonItem,
+    IonList,
+    IonIcon,
+    FormsModule,
+    CommonModule,
+    IonHeader,
+    IonToolbar,
+    IonContent,
+    IonSegment,
+    IonSegmentButton,
+    IonGrid,
+    IonRow,
+    IonCol,
+    IonButton,
+    IonLabel,
+    IonBadge,
+    IonInput
+  ]
 })
-export class Tab2Page {
+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 }
+  ];
 
-  constructor() {}
+  constructor(private router: Router, private cdr: ChangeDetectorRef) {
+    addIcons({ checkmarkCircle, calendar, helpCircle });
+  }
 
+  ngOnInit() {
+    // 初始时不需要强制触发视图更新
+  }
+  editPlan(day: any) {
+    console.log('编辑计划:', day);
+    // 这里可以打开一个编辑页面或者弹窗,修改该计划
+    // 示例:打开编辑页面
+    // this.navCtrl.navigateForward('/edit-plan', { queryParams: { plan: day } });
+  }
+
+  // 删除计划
+  deletePlan(day: any) {
+    console.log('删除计划:', day);
+    const index = this.weekPlan.indexOf(day);
+    if (index !== -1) {
+      this.weekPlan.splice(index, 1);
+    }
+  }
+
+  // 页面跳转功能
+  goToPage(page: string) {
+    // 更新选中的tab
+    this.selectedTab = page;
+    this.router.navigate([`/tabs/${page}`]);  // 然后再进行路由跳转
+
+    // 手动检测视图变化,确保数据绑定正确
+    this.cdr.detectChanges();
+  }
 }

+ 23 - 0
TFPower-app/src/app/tab2/tag-input/tag-input.component.html

@@ -0,0 +1,23 @@
+<ion-card>
+  <ion-card-header>
+    <ion-card-title>添加标签</ion-card-title>
+  </ion-card-header>
+
+  <ion-card-content>
+    <div class="input-container">
+      <ion-input [(ngModel)]="tagInput" placeholder="例如:减脂,增肌..." (keydown.enter)="addTag()"></ion-input>
+      <ion-button expand="full" (click)="addTag()">添加标签</ion-button>
+    </div>
+
+    <!-- 标签显示区域 -->
+    <div class="tag-container">
+      <ion-chip *ngFor="let tag of tags" class="tag-chip">
+        <ion-label>{{ tag }}</ion-label>
+        <!-- 删除按钮 -->
+        <ion-icon name="close" (click)="removeTag(tag)" class="close-icon"></ion-icon>
+      </ion-chip>
+    </div>
+
+    <p>{{tagInput}}</p>
+  </ion-card-content>
+</ion-card>

+ 36 - 0
TFPower-app/src/app/tab2/tag-input/tag-input.component.scss

@@ -0,0 +1,36 @@
+.input-container {
+  display: flex;
+  align-items: center;
+  gap: 10px; 
+  margin-bottom: 10px; 
+}
+
+.tag-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+  margin-top: 10px;
+}
+
+.tag-chip {
+  display: flex;
+  align-items: center;
+  position: relative; 
+  background-color: var(--ion-color-light);
+}
+
+.close-icon {
+  position: absolute;
+  right: 5px; 
+  top: 50%; 
+  transform: translateY(-50%); 
+  cursor: pointer;
+  font-size: 12px; 
+  color: var(--ion-color-danger); 
+}
+
+ion-chip {
+  --padding-start: 10px;
+  --padding-end: 10px;
+  --border-radius: 20px;
+}

+ 24 - 0
TFPower-app/src/app/tab2/tag-input/tag-input.component.spec.ts

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

+ 39 - 0
TFPower-app/src/app/tab2/tag-input/tag-input.component.ts

@@ -0,0 +1,39 @@
+import { Component, Output, EventEmitter } from '@angular/core';
+import { IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonInput, IonButton, IonChip, IonLabel, IonIcon } from '@ionic/angular/standalone';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { addIcons } from 'ionicons';
+import { close, barbellOutline, personOutline, square, alarmOutline } from 'ionicons/icons';
+@Component({
+  selector: 'app-tag-input',
+  templateUrl: './tag-input.component.html',
+  styleUrls: ['./tag-input.component.scss'],
+  standalone: true,
+  imports: [IonCard, IonCardHeader, IonCardTitle, IonCardContent, IonInput, IonButton, IonChip, IonLabel, IonIcon, CommonModule, FormsModule]
+})
+
+export class TagInputComponent {
+  tags: string[] = [];  // 标签列表
+  tagInput: string = '';  // 输入框的值
+
+  // 定义一个事件输出,类型为string[]
+  @Output() tagsChanged: EventEmitter<string[]> = new EventEmitter<string[]>();
+
+  // 添加标签方法
+  addTag() {
+    if (this.tagInput && !this.tags.includes(this.tagInput)) {
+      this.tags.push(this.tagInput);  // 将标签添加到数组
+      this.tagsChanged.emit(this.tags);  // 发射更新后的标签列表
+      this.tagInput = '';  // 清空输入框
+    }
+  }
+
+  // 删除标签方法
+  removeTag(tag: string) {
+    this.tags = this.tags.filter(t => t !== tag);  // 从标签数组中移除
+    this.tagsChanged.emit(this.tags);  // 发射更新后的标签列表
+  }
+  constructor() {
+    addIcons({ close, personOutline, barbellOutline, alarmOutline, square });
+  }
+}

+ 9 - 0
TFPower-app/src/app/tab2/test-chat-panel/test-chat-panel.component.html

@@ -0,0 +1,9 @@
+<app-chat-panel *ngIf="leftButtons?.length&&modelList?.length" #chatComp 
+[roleId]="roleId" 
+[chatId]="chatId" 
+[leftButtons]="leftButtons" 
+[modelList]="modelList" 
+[isDirect]="isDirect"
+[hideModalSelect]="hideModalSelect"
+[hideInputPreview]="hideInputPreview"
+></app-chat-panel>

+ 3 - 0
TFPower-app/src/app/tab2/test-chat-panel/test-chat-panel.component.scss

@@ -0,0 +1,3 @@
+app-chat-panel {
+    height: 100vh;
+}

+ 22 - 0
TFPower-app/src/app/tab2/test-chat-panel/test-chat-panel.component.spec.ts

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

+ 243 - 0
TFPower-app/src/app/tab2/test-chat-panel/test-chat-panel.component.ts

@@ -0,0 +1,243 @@
+import { CommonModule } from '@angular/common';
+import { ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { ModalController } from '@ionic/angular/standalone';
+import { ChatPanelComponent } from 'fmode-ng'
+import Parse from "parse";
+import { combineLatest } from 'rxjs';
+
+// 添加Icons
+import { addIcons } from 'ionicons';
+import * as icons from 'ionicons/icons';
+addIcons(icons);
+
+@Component({
+  selector: 'app-test-chat-panel',
+  templateUrl: './test-chat-panel.component.html',
+  styleUrls: ['./test-chat-panel.component.scss'],
+  standalone: true,
+  imports:[
+    CommonModule,
+    ChatPanelComponent,
+  ]
+})
+export class TestChatPanelComponent  implements OnInit {
+  @ViewChild(ChatPanelComponent) chatComp:ChatPanelComponent|undefined
+  leftButtons:any[]=[]
+  modelList:any[]=[]
+  isDirect:boolean=true;
+  hideShare:boolean=true;
+  hideModalSelect:boolean=true;
+  hideInputPreview:boolean = true;
+  chatId:string = ""
+  roleId:string = ""
+  pid:string = ""
+  constructor(
+    private route:ActivatedRoute,
+    private cdRef:ChangeDetectorRef,
+    private modalCtrl:ModalController
+  ) { 
+    combineLatest([this.route.params,this.route.queryParams]).subscribe(async (data:any)=>{
+      let params = data[0] || {}
+
+      this.chatId = params['chatId'] || this.chatId || null;
+      this.roleId = params['roleId'] || this.roleId || null;
+      this.pid = params['pid'] || this.pid || null;
+      console.log("this.pid",this.pid)
+      // 异步加载的后续数据 操作按钮
+      let bint = setInterval(() => {
+        if(this.roleId){
+          clearInterval(bint);
+          return
+        }
+        this.initPanelConfig();
+      }, 2000);
+    })
+  }
+
+
+  ngOnInit() {
+        this.initPanelConfig();
+        // 异步加载的后续数据 提示词
+        let pint = setInterval(() => {
+          if(this.chatComp?.fmodeChat?.promptList?.length){
+            clearInterval(pint);
+            return
+          }
+          this.getChatPrompt();
+        }, 2000);
+
+        // 异步加载的后续数据 采访人物 ChatSession.person
+        let personInt = setInterval(() => {
+          if(this.chatComp?.fmodeChat?.chatSession?.get("person")){
+            clearInterval(personInt)
+          }
+          if(!this.chatComp?.fmodeChat?.chatSession?.get("person")){
+            if(this.pid){
+              this.chatComp?.fmodeChat?.chatSession?.set("person",{type:"Pointer",className:"Person",objectId:this.pid})
+            }
+          }
+        }, 2000);
+  }
+
+  // 初始化聊天面板的设置
+  initPanelConfig(){
+    this.roleId = this.chatComp?.fmodeChat?.chatSession?.get("role")?.id || this.roleId;
+
+    // 按钮自定义
+     this.leftButtons = [
+       // 提示 当角色配置预设提示词时 显示
+       {
+        title:"话题灵感",
+        showTitle:true,
+        icon:"color-wand-outline",
+        onClick:()=>{
+          if(this.chatComp){
+            this.chatComp.fmodeChat.isPromptModalOpen = true
+          }
+        },
+        show:()=>{
+          return this.chatComp?.fmodeChat?.promptList?.length
+        }
+      }
+   ]
+
+      this.leftButtons.push({ // 总结 结束并归档本次对话
+            title:"AI总结对话",
+            showTitle:true,
+            icon:"archive-outline",
+            onClick:()=>{
+              if(this.chatComp){
+                // this.chatComp.fmodeChat.isPromptModalOpen = true
+                if(this.chatComp.fmodeChat){
+                  console.log(JSON.stringify(this.chatComp.fmodeChat.messageList))
+                  // alert("处理对话记录")
+                }
+              }
+              },
+            show:()=>{ 
+              return !this.chatComp?.fmodeChat?.chatSession?.get("story")?.id
+              }
+        })
+
+        this.leftButtons.push({ // 总结 结束并归档本次对话
+          title:"聊天心理分析",
+          showTitle:true,
+          icon:"archive-outline",
+          onClick:()=>{
+            if(this.chatComp){
+              // this.chatComp.fmodeChat.isPromptModalOpen = true
+              if(this.chatComp.fmodeChat){
+                let messageList = JSON.parse(JSON.stringify(this.chatComp.fmodeChat.messageList))
+                messageList = messageList.filter((item:any)=>item.role!="system"&&item?.hidden!=true)
+                let qaContent = messageList.map((item:any)=>{
+                  let roleName = "当前用户"
+                  if(item.role!="user"){
+                    if(this.chatComp&&this.chatComp.fmodeChat.role){
+                      roleName = this.chatComp.fmodeChat.role.get("name");
+                    }else{
+                      roleName = "AI助理"
+                    }
+                  }
+                  return `${roleName}:${item.content}`
+                }
+                ).join("\n")
+                console.log(qaContent)
+                // alert("处理对话记录")
+              }
+            }
+            },
+          show:()=>{ 
+            return !this.chatComp?.fmodeChat?.chatSession?.get("story")?.id
+            }
+      })
+
+    
+
+      setTimeout(()=>{
+          if(this.chatComp&&this.chatComp.fmodeChat){
+            // 自定义左下角操作按钮
+            console.log("左下角操作按钮",this.chatComp.fmodeChat.leftButtons);
+            this.chatComp.fmodeChat.leftButtons = this.leftButtons;
+            
+            // 自定义角色名称
+            console.log("自定义角色",this.chatComp.fmodeChat.role);
+                        this.chatComp.fmodeChat.role.set("name","晓晓");
+            this.chatComp.fmodeChat.role.set("title","心理咨询师");
+            this.chatComp.fmodeChat.role.set("desc","一名亲切和蔼的心理咨询师,晓晓,年龄36岁");
+            this.chatComp.fmodeChat.role.set("tags",["焦虑","抑郁"]);
+            this.chatComp.fmodeChat.role.set("avatar","https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/aigc/imagine/Q4Zif7fTbK-0.png")
+            this.chatComp.fmodeChat.role.set("prompt",`
+# 角色设定
+您是一名亲切和蔼的心理咨询师,晓晓,年龄36岁,需要完成陪来访者聊聊天,随意轻松一些。
+
+# 对话环节
+0.破冰,互相了解,引导用户介绍自己
+1.拓展话题,根据用户的介绍,拓展一些和其心理状态相关的话题
+- 引导,可深入的点,以用户自述为主
+- 当信息充足时候,确认用户心理状态,并进入下一个环节
+2.引导收尾,委婉引导用户结束本次对话
+- 用户同意结束后,结束本次对话,如果依依不舍,可以再陪聊一会儿`);
+//             this.chatComp.fmodeChat.role.set("name","晓晓");
+//             this.chatComp.fmodeChat.role.set("title","主任医师");
+//             this.chatComp.fmodeChat.role.set("desc","一名专业的全科医生,晓晓,年龄36岁");
+//             this.chatComp.fmodeChat.role.set("tags",["呼吸道","感染科"]);
+//             this.chatComp.fmodeChat.role.set("avatar","https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/aigc/imagine/Q4Zif7fTbK-0.png")
+//             this.chatComp.fmodeChat.role.set("prompt",`
+// # 角色设定
+// 您是一名专业的全科医生,晓晓,年龄36岁,需要完成一次完整的门诊服务。
+
+// # 对话环节
+// 0.导诊(根据用户基本情况,引导挂号合适的科室)
+// 1.预设的问询方式(感冒问呼吸、肚子疼叩诊)
+// - 打招呼,以用户自述为主
+// - 当信息充足时候,确认用户症状对应的科室,并进入下一个环节
+// 2.拓展的问询细节
+// 例如:用户反映呼吸不畅,拓展出:是否咳嗽;是否感觉痛或者痒等其他需要的问题。
+// - 当问询细节补充完成后进入下一个环节
+// 3.初步的诊断结果,并且同时列出检查检验项目
+// 初步诊断:确定需要有哪些进一步检查
+// 检查检验:获取医学客观数据
+// - 等待用户提交客观数据,进入下一阶段
+// 4.给出诊断方案并给出处方
+
+// # 开始话语
+// 当您准备好了,可以以一个医生的身份,向来访的用户打招呼。
+//             `);
+
+            this.cdRef.detectChanges();
+          }
+      },1000)
+    
+
+   // 模型自定义
+   let ChatModel = Parse.Object.extend("ChatModel");
+   let model1 = new ChatModel();
+   model1.set({
+       name:"语伴4.5-128k",
+       code:"fmode-4.5-128k",
+       model:"gpt-4o-mini",
+       credit:0.096,
+   })
+  this.modelList = [model1]
+
+
+   console.log("initPanelConfig",this.leftButtons,this.modelList)
+ }
+
+ async getChatPrompt(){
+     let query = new Parse.Query('ChatPrompt')
+     query.notEqualTo('isDeleted', true)
+    //  query.equalTo('company', localStorage.getItem("company"))
+     query.equalTo('role', this.chatComp?.fmodeChat?.role)
+     query.include('role')
+     let promptData = await query.find()
+     if(this.chatComp&&this.chatComp.fmodeChat){
+       this.chatComp.fmodeChat.promptList = promptData
+       this.chatComp.fmodeChat.promptList.forEach((item:any)=>{
+         let cate = item.get('role').get('promptCates').filter((cate:any) => cate.name == item.get('cate'))
+         item.img = cate[0].img
+        })
+      }
+   }
+}

+ 109 - 0
TFPower-app/src/app/tab2/test-page/test-page.component.html

@@ -0,0 +1,109 @@
+<ion-header translucent="true">
+  <ion-toolbar>
+    <ion-title>制定个人健身计划</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <div class="content">
+    <!-- 健身目标选择 -->
+    <div class="module">
+      <h2>请输入您的健身目标</h2>
+      <app-tag-input (tagsChanged)="onTagsChanged($event)"></app-tag-input>
+      <ion-item>
+        <ion-label position="floating">详细描述(可选,越详细计划越清晰哦!)</ion-label>
+        <ion-textarea [value]="goalDescription" (ionInput)="onGoalDescriptionChange($event)"
+          placeholder="例如:我想减脂,增强耐力..." auto-grow="true">
+        </ion-textarea>
+      </ion-item>
+    </div>
+
+    <!-- 偏好设置 -->
+    <div class="module">
+      <h3>偏好设置</h3>
+      <ion-grid>
+        <ion-row>
+          <ion-col size="6">
+            <ion-item>
+              <ion-label>锻炼方式(多选)</ion-label>
+              <ion-select multiple="true" (ionChange)="onExercisePreferenceChange($event)" cancelText="取消" okText="确认">
+                <ion-select-option value="cardio">有氧</ion-select-option>
+                <ion-select-option value="strength">力量</ion-select-option>
+                <ion-select-option value="flexibility">柔韧性</ion-select-option>
+              </ion-select>
+            </ion-item>
+          </ion-col>
+          <ion-col size="6">
+            <ion-item>
+              <ion-label>每周锻炼频率</ion-label>
+              <ion-select (ionChange)="onWorkoutFrequencyChange($event)" cancelText="取消" okText="确认">
+                <ion-select-option value="1">1次</ion-select-option>
+                <ion-select-option value="2">2次</ion-select-option>
+                <ion-select-option value="3">3次</ion-select-option>
+                <ion-select-option value="4">4次</ion-select-option>
+                <ion-select-option value="5">5次</ion-select-option>
+                <ion-select-option value="6">6次</ion-select-option>
+                <ion-select-option value="7">7次</ion-select-option>
+              </ion-select>
+            </ion-item>
+          </ion-col>
+        </ion-row>
+      </ion-grid>
+    </div>
+
+    <!-- 身体数据 -->
+    <div class="module">
+      <h3>身体数据</h3>
+      <ion-grid>
+        <ion-row>
+          <ion-col size="4">
+            <ion-item [class.empty]="!height" [class.filled]="height">
+              <ion-label position="floating">身高(cm)</ion-label>
+              <ion-input type="number" [value]="height" (ionInput)="onHeightChange($event)" placeholder="请输入身高"
+                required>
+              </ion-input>
+            </ion-item>
+          </ion-col>
+          <ion-col size="4">
+            <ion-item [class.empty]="!weight" [class.filled]="weight">
+              <ion-label position="floating">体重(kg)</ion-label>
+              <ion-input type="number" [value]="weight" (ionInput)="onWeightChange($event)" placeholder="请输入体重"
+                required>
+              </ion-input>
+            </ion-item>
+          </ion-col>
+          <ion-col size="4">
+            <ion-item [class.empty]="!age" [class.filled]="age">
+              <ion-label position="floating">年龄(age)</ion-label>
+              <ion-input type="number" [value]="age" (ionInput)="onAgeChange($event)" placeholder="请输入年龄" required>
+              </ion-input>
+            </ion-item>
+          </ion-col>
+        </ion-row>
+      </ion-grid>
+    </div>
+
+    <!-- 生成计划按钮 -->
+    <ion-button expand="full" (click)="generatePlan()" [disabled]="isGenerating">生成健身计划</ion-button>
+
+    <!-- 显示健身计划结果 -->
+    <div *ngIf="generatedPlan" class="result-container">
+      <h3>您的健身计划:</h3>
+      <ion-card>
+        <ion-card-header>
+          <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>
+          }
+        </ion-card-content>
+      </ion-card>
+    </div>
+  </div>
+</ion-content>

+ 38 - 0
TFPower-app/src/app/tab2/test-page/test-page.component.scss

@@ -0,0 +1,38 @@
+.content {
+  padding: 8px;
+}
+h2 {
+  margin-bottom: 8px;
+  font-size: 1.5em;
+}
+h3
+{
+   margin-bottom: 8px;
+  font-size: 1.2em; 
+}
+.module {
+  margin-bottom: 8px;
+  padding: 8px;
+  background-color: #f9f9f9;
+  border-radius: 8px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+ion-grid {
+  margin-bottom: 8px;
+}
+ion-label{
+    transition: transform 0.2s ease;
+}
+ion-input {
+  --padding-top: 20px;
+  --padding-bottom: 2px; 
+  font-size: 14px;
+}
+
+ion-col {
+  padding: 0 8px;
+}
+ion-button {
+  margin-top: 20px;
+  --background: #3880ff;
+}

+ 22 - 0
TFPower-app/src/app/tab2/test-page/test-page.component.spec.ts

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

+ 113 - 0
TFPower-app/src/app/tab2/test-page/test-page.component.ts

@@ -0,0 +1,113 @@
+import { Component, OnInit } from '@angular/core';
+import { IonicModule, ToastController } from '@ionic/angular';
+import { FormsModule } from '@angular/forms';
+import { CommonModule } from '@angular/common';
+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';
+
+@Component({
+  selector: 'app-test-page',
+  templateUrl: './test-page.component.html',
+  styleUrls: ['./test-page.component.scss'],
+  standalone: true,
+  imports: [IonicModule, FormsModule, CommonModule, TagInputComponent,
+    // 引入fm-markdown-preview组件模块
+    MarkdownPreviewModule]  // 添加 CommonModule
+})
+export class TestPageComponent implements OnInit {
+  selectedTags: string[] = [];
+  exercisePreference: string = '';
+  workoutFrequency: string = '';
+  height: number | null = null;
+  weight: number | null = null;
+  age: number | null = null;
+  goalDescription: string = '';
+  generatedPlan: string = '';  // 用来存储生成的健身计划
+  isGenerating: boolean = false;  // 是否正在生成计划
+  messageList: any[] = [];  // 存储消息列表
+
+  constructor(private toastController: ToastController) {
+    addIcons({ personOutline, barbellOutline, alarmOutline, square });
+  }
+
+  ngOnInit() { }
+
+  onTagsChanged(tags: string[]) {
+    this.selectedTags = tags;
+    console.log('当前标签:', this.selectedTags);
+  }
+
+  onGoalDescriptionChange(event: any) {
+    this.goalDescription = event.detail.value;
+  }
+
+  onExercisePreferenceChange(event: any) {
+    this.exercisePreference = event.detail.value;
+  }
+
+  onWorkoutFrequencyChange(event: any) {
+    this.workoutFrequency = event.detail.value;
+  }
+
+  onHeightChange(event: any) {
+    this.height = event.detail.value;
+  }
+
+  onWeightChange(event: any) {
+    this.weight = event.detail.value;
+  }
+
+  onAgeChange(event: any) {
+    this.age = event.detail.value;
+  }
+
+  // 显示toast提示
+  async showToast(message: string) {
+    const toast = await this.toastController.create({
+      message: message,
+      duration: 2000,
+      position: 'top',
+      color: 'danger',
+    });
+    toast.present();
+  }
+
+  async generatePlan() {
+    // 检查身高和体重是否为空
+    if (!this.height || !this.weight) {
+      // 如果为空,显示提示信息
+      await this.showToast('身高或体重为空,无法生成个人健身计划');
+      return; // 退出函数,不继续生成健身计划
+    }
+
+    this.generatedPlan = '';  // 清空之前的结果
+    this.messageList = [];  // 清空旧的消息列表,避免重复内容
+    this.isGenerating = true;
+    console.log('生成健身计划', {
+      tags: this.selectedTags,
+      exercisePreference: this.exercisePreference,
+      workoutFrequency: this.workoutFrequency,
+      height: this.height,
+      weight: this.weight,
+      age: this.age,
+      goalDescription: this.goalDescription
+    });
+
+    let prompt = `您作为一名专业的健身计划定制大师,请帮我根据以下情况制定健身计划(健身计划请给每天的运动标上序号)。关键词:${this.selectedTags.join(",")},目标描述:${this.goalDescription},运动偏好:${this.exercisePreference},
+    健身频率一周:${this.workoutFrequency},身高:${this.height}cm,体重:${this.weight}kg,年龄:${this.age}`;
+
+    let messageList = new FmodeChatCompletion([
+      { role: "system", content: '' },
+      { role: "user", content: prompt }
+    ]);
+    messageList.sendCompletion().subscribe((message: any) => {
+      console.log(message.content)
+      this.generatedPlan = message.content
+      if (message?.complete) {
+        this.isGenerating = false
+      }
+    })
+  }
+}

+ 3 - 2
TFPower-app/src/app/tabs/tabs.page.html

@@ -6,14 +6,15 @@
     </ion-tab-button>
 
     <ion-tab-button tab="tab2" href="/tabs/tab2">
-      <ion-icon name="chatbox-ellipses"></ion-icon>
-      <ion-label>社交</ion-label>
+      <ion-icon name="barbell"></ion-icon>
+      <ion-label>运动</ion-label>
     </ion-tab-button>
 
     <ion-tab-button tab="tab3" href="/tabs/tab3">
       <ion-icon aria-hidden="true" name="people"></ion-icon>
       <ion-label>社区</ion-label>
     </ion-tab-button>
+
     <ion-tab-button tab="tab4" href="/tabs/tab4">
       <ion-icon name="home"></ion-icon>
       <ion-label>我的</ion-label>

+ 2 - 1
TFPower-app/src/app/tabs/tabs.page.ts

@@ -7,7 +7,7 @@ import {
   IonLabel,
 } from '@ionic/angular/standalone';
 import { addIcons } from 'ionicons';
-import { people, balloonSharp, home, chatboxEllipses } from 'ionicons/icons';
+import { people, balloonSharp, home, chatboxEllipses, barbell } from 'ionicons/icons';
 
 @Component({
   selector: 'app-tabs',
@@ -25,6 +25,7 @@ export class TabsPage {
       balloonSharp,
       chatboxEllipses,
       home,
+      barbell,
     });
   }
 }

+ 6 - 0
TFPower-app/src/app/tabs/tabs.routes.ts

@@ -16,6 +16,11 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../tab2/tab2.page').then((m) => m.Tab2Page),
       },
+      {
+        path: 'test-page',
+        loadComponent: () =>
+          import('../tab2/test-page/test-page.component').then((m) => m.TestPageComponent),
+      },
       {
         path: 'tab3',
         loadComponent: () =>
@@ -26,6 +31,7 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../tab4/tab4.page').then((m) => m.Tab4Page),
       },
+
       {
         path: 'todolistpage',
         loadComponent: () =>

BIN
TFPower-app/src/assets/images/wisefitness-high-resolution-logo.png


+ 97 - 0
TFPower-server/lib/migration/data.js

@@ -0,0 +1,97 @@
+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.DepartList = [
+
+      {
+        "objectId": "dept001",
+        "name": "内科",
+        "desc": "负责内科疾病的诊断和治疗"
+      },
+      {
+        "objectId": "dept002",
+        "name": "外科",
+        "desc": "负责外科手术和相关疾病的治疗"
+      },
+      {
+        "objectId": "dept003",
+        "name": "儿科",
+        "desc": "专注于儿童疾病的预防和治疗"
+      },
+      {
+        "objectId": "dept004",
+        "name": "妇产科",
+        "desc": "负责女性生殖系统及相关疾病的治疗"
+      },
+      {
+        "objectId": "dept005",
+        "name": "神经科",
+        "desc": "专注于神经系统疾病的诊断和治疗"
+      }
+    ]

+ 58 - 0
TFPower-server/lib/migration/import-data.js

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

+ 165 - 0
TFPower-server/lib/ncloud.js

@@ -0,0 +1,165 @@
+
+
+class CloudObject{
+    id
+    className
+    data = {}
+    constructor(className){
+        this.className = className
+    }
+    toPointer(){
+        return {"__type":"Pointer","className":this.className,"objectId":this.id}
+    }
+    set(json){
+        Object.keys(json).forEach(key=>{
+            if(["objectId","id","createdAt","updatedAt","ACL"].indexOf(key)>-1){
+                return
+            }
+            this.data[key] = json[key]
+        })
+    }
+    get(key){
+        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"
+        } 
+        let body = JSON.stringify(this.data)
+        let 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"
+          });
+          let result = await response?.json();
+          if(result?.error){
+            console.error(result?.error)
+          }
+          if(result?.objectId){this.id = result?.objectId}
+          return this
+    }
+    async destory(){
+        if(!this.id) return
+        let response = await fetch("http://dev.fmode.cn:1337/parse/classes/Doctor/"+this.id, {
+            "headers": {
+              "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "DELETE",
+            "mode": "cors",
+            "credentials": "omit"
+          });
+          let result = await response?.json();
+          if(result){
+            this.id = null
+        }
+        return true
+    }
+}
+
+class CloudQuery{
+    className
+    constructor(className){
+        this.className = className
+    }
+
+    whereOptions = {}
+    greaterThan(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gt"] = value
+    }
+    greaterThanAndEqualTo(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gte"] = value
+    }
+    lessThan(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lt"] = value
+    }
+    lessThanAndEqualTo(key,value){
+        if(!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lte"] = value
+    }
+    equalTo(key,value){
+        this.whereOptions[key] = value
+    }
+
+    async get(id){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"/"+id+"?"
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        return json || {}
+    }
+    async find(){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"?"
+        
+        if(Object.keys(this.whereOptions)?.length){
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+        }
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        return json?.results || []
+    }
+    async first(){
+        let url = "http://dev.fmode.cn:1337/parse/classes/"+this.className+"?"
+        
+        if(Object.keys(this.whereOptions)?.length){
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+        }
+
+        let response = await fetch(url, {
+            "headers": {
+            "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+            "x-parse-application-id": "dev"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        let exists = json?.results?.[0] || null
+        if(exists){
+            let existsObject = new CloudObject(this.className)
+            existsObject.set(exists)
+            existsObject.id = exists.objectId
+            existsObject.createdAt = exists.createdAt
+            existsObject.updatedAt = exists.updatedAt
+            return existsObject
+        }
+    }
+}
+
+module.exports.CloudObject = CloudObject
+module.exports.CloudQuery = CloudQuery