18879852299-202226701057 hai 6 meses
pai
achega
1ee2fb44ca
Modificáronse 56 ficheiros con 2111 adicións e 162 borrados
  1. 4 1
      .vscode/settings.json
  2. 4 1
      exercise-app/angular.json
  3. 689 26
      exercise-app/package-lock.json
  4. 4 0
      exercise-app/package.json
  5. 3 0
      exercise-app/src/app/app.routes.ts
  6. 8 0
      exercise-app/src/app/edit-rating-star/edit-rating-star.component.html
  7. 10 0
      exercise-app/src/app/edit-rating-star/edit-rating-star.component.scss
  8. 22 0
      exercise-app/src/app/edit-rating-star/edit-rating-star.component.spec.ts
  9. 57 0
      exercise-app/src/app/edit-rating-star/edit-rating-star.component.ts
  10. 3 0
      exercise-app/src/app/edit-tag/REDAME.md
  11. 91 0
      exercise-app/src/app/edit-tag/edit-tag.component.html
  12. 13 0
      exercise-app/src/app/edit-tag/edit-tag.component.scss
  13. 22 0
      exercise-app/src/app/edit-tag/edit-tag.component.spec.ts
  14. 69 0
      exercise-app/src/app/edit-tag/edit-tag.component.ts
  15. 33 0
      exercise-app/src/app/exercise-picture/exercise-picture.component.html
  16. 0 0
      exercise-app/src/app/exercise-picture/exercise-picture.component.scss
  17. 22 0
      exercise-app/src/app/exercise-picture/exercise-picture.component.spec.ts
  18. 69 0
      exercise-app/src/app/exercise-picture/exercise-picture.component.ts
  19. 98 0
      exercise-app/src/app/page-exercise-analysis/class-chat-completion.ts
  20. 22 0
      exercise-app/src/app/page-exercise-analysis/page-exercise-analysis.component.html
  21. 0 0
      exercise-app/src/app/page-exercise-analysis/page-exercise-analysis.component.scss
  22. 22 0
      exercise-app/src/app/page-exercise-analysis/page-exercise-analysis.component.spec.ts
  23. 132 0
      exercise-app/src/app/page-exercise-analysis/page-exercise-analysis.component.ts
  24. 3 0
      exercise-app/src/app/page-rxjs/page-rxjs.component.html
  25. 0 0
      exercise-app/src/app/page-rxjs/page-rxjs.component.scss
  26. 22 0
      exercise-app/src/app/page-rxjs/page-rxjs.component.spec.ts
  27. 19 0
      exercise-app/src/app/page-rxjs/page-rxjs.component.ts
  28. 25 0
      exercise-app/src/app/page-test/page-test.component.html
  29. 0 0
      exercise-app/src/app/page-test/page-test.component.scss
  30. 22 0
      exercise-app/src/app/page-test/page-test.component.spec.ts
  31. 40 0
      exercise-app/src/app/page-test/page-test.component.ts
  32. 22 0
      exercise-app/src/app/page-train-recom/page-train-recom.component.html
  33. 0 0
      exercise-app/src/app/page-train-recom/page-train-recom.component.scss
  34. 22 0
      exercise-app/src/app/page-train-recom/page-train-recom.component.spec.ts
  35. 60 0
      exercise-app/src/app/page-train-recom/page-train-recom.component.ts
  36. 90 90
      exercise-app/src/app/tab1/tab1.page.html
  37. 76 5
      exercise-app/src/app/tab1/tab1.page.ts
  38. 43 12
      exercise-app/src/app/tab2/tab2.page.html
  39. 10 4
      exercise-app/src/app/tab2/tab2.page.ts
  40. 32 12
      exercise-app/src/app/tab3/tab3.page.html
  41. 4 2
      exercise-app/src/app/tab3/tab3.page.ts
  42. 37 0
      exercise-app/src/app/tab4/tab4.page.html
  43. 0 0
      exercise-app/src/app/tab4/tab4.page.scss
  44. 17 0
      exercise-app/src/app/tab4/tab4.page.spec.ts
  45. 23 0
      exercise-app/src/app/tab4/tab4.page.ts
  46. 30 0
      exercise-app/src/app/tab5/tab5.page.html
  47. 0 0
      exercise-app/src/app/tab5/tab5.page.scss
  48. 17 0
      exercise-app/src/app/tab5/tab5.page.spec.ts
  49. 24 0
      exercise-app/src/app/tab5/tab5.page.ts
  50. 11 1
      exercise-app/src/app/tabs/tabs.page.html
  51. 9 8
      exercise-app/src/app/tabs/tabs.page.ts
  52. 31 0
      exercise-app/src/app/tabs/tabs.routes.ts
  53. BIN=BIN
      exercise-app/src/assets/image.png
  54. 19 0
      exercise-app/src/main.ts
  55. 1 0
      exercise-app/tsconfig.json
  56. 5 0
      ionic.config.json

+ 4 - 1
.vscode/settings.json

@@ -4,6 +4,9 @@
   "id": "plantuml",
   "aliases": ["plantuml"],
   "extensions": [".puml", ".plantuml"],
-  "configuration": "plantuml"
+  "configuration": "plantuml",
+  "cSpell.words": [
+    "Recom"
+  ]
   
 }

+ 4 - 1
exercise-app/angular.json

@@ -126,7 +126,10 @@
     }
   },
   "cli": {
-    "schematicCollections": ["@ionic/angular-toolkit"]
+    "schematicCollections": [
+      "@ionic/angular-toolkit"
+    ],
+    "analytics": false
   },
   "schematics": {
     "@ionic/angular-toolkit:component": {

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 689 - 26
exercise-app/package-lock.json


+ 4 - 0
exercise-app/package.json

@@ -21,13 +21,17 @@
     "@angular/platform-browser": "^18.0.0",
     "@angular/platform-browser-dynamic": "^18.0.0",
     "@angular/router": "^18.0.0",
+    "@awesome-cordova-plugins/media-capture": "^6.12.0",
     "@capacitor/app": "6.0.2",
     "@capacitor/core": "6.2.0",
     "@capacitor/haptics": "6.0.2",
     "@capacitor/keyboard": "6.0.3",
     "@capacitor/status-bar": "6.0.2",
     "@ionic/angular": "^8.0.0",
+    "@types/parse": "^3.0.9",
+    "fmode-ng": "^0.0.62",
     "ionicons": "^7.2.1",
+    "parse": "^5.3.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.14.2"

+ 3 - 0
exercise-app/src/app/app.routes.ts

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

+ 8 - 0
exercise-app/src/app/edit-rating-star/edit-rating-star.component.html

@@ -0,0 +1,8 @@
+<div class="star-rating">
+  <ng-container *ngFor="let star of starList; let i = index">
+    <ion-icon 
+      [name]="star ? 'star' : 'star-outline'" 
+      (click)="rate(i)">
+    </ion-icon>
+  </ng-container>
+</div>

+ 10 - 0
exercise-app/src/app/edit-rating-star/edit-rating-star.component.scss

@@ -0,0 +1,10 @@
+.star-rating {
+  display: flex;
+  cursor: pointer;
+
+  ion-icon {
+    font-size: 30px; // 调整星星大小
+    color: gold; // 星星颜色
+    margin-right: 5px; // 星星间距
+  }
+}

+ 22 - 0
exercise-app/src/app/edit-rating-star/edit-rating-star.component.spec.ts

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

+ 57 - 0
exercise-app/src/app/edit-rating-star/edit-rating-star.component.ts

@@ -0,0 +1,57 @@
+import { CommonModule } from '@angular/common';
+import { Component, OnInit } from '@angular/core';
+import {  Input, Output, EventEmitter } from '@angular/core';
+import { IonIcon } from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { star } from 'ionicons/icons';
+import { starOutline } from 'ionicons/icons';
+addIcons({star,starOutline})
+@Component({
+  selector: 'edit-rating-star',
+  templateUrl: './edit-rating-star.component.html',
+  styleUrls: ['./edit-rating-star.component.scss'],
+  standalone: true,
+  imports:[IonIcon,CommonModule],
+})
+export class EditRatingStarComponent  implements OnInit {
+
+  @Input() score: number = 0; // 默认分值为0
+  @Input() scoreMax: number = 5; // 最大分值
+  starList: boolean[] = []; // 星星状态数组
+
+  @Output() onScoreChange: EventEmitter<number> = new EventEmitter<number>();
+
+  constructor() {
+    this.updateStarList();
+  }
+
+  ngOnInit() {
+    this.updateStarList();
+  }
+
+  ngOnChanges() {
+    this.updateStarList();
+  }
+
+  // 更新星星数组
+  private updateStarList() {
+    this.starList = Array(this.scoreMax).fill(false);
+    for (let i = 0; i < this.score; i++) {
+      this.starList[i] = true;
+    }
+  }
+
+  // 打分方法
+  rate(index: number) {
+    if (this.score === index + 1) {
+      // 如果点击的是当前分值,清零
+      this.score = 0;
+    } else {
+      // 否则更新分值
+      this.score = index + 1;
+    }
+    this.updateStarList();
+    this.onScoreChange.emit(this.score); // 触发分值变化事件
+  }
+
+}

+ 3 - 0
exercise-app/src/app/edit-tag/REDAME.md

@@ -0,0 +1,3 @@
+# 标签编辑组件
+ - 上面:输入框和确认按钮
+ - 下面:字符串数组,每个字符串是一个标签

+ 91 - 0
exercise-app/src/app/edit-tag/edit-tag.component.html

@@ -0,0 +1,91 @@
+<ion-card>
+  <ion-card-header>
+      <ion-card-title>用户信息输入</ion-card-title>
+  </ion-card-header>
+  <ion-card-content>
+      <ion-grid>
+          <ion-row>
+              <ion-col>
+                  <ion-item>
+                      <ion-label position="floating">性别</ion-label>
+                      <ion-input [(ngModel)]="gender" type="text" placeholder="性别"></ion-input>
+                  </ion-item>
+              </ion-col>
+          </ion-row>
+          <ion-row>
+              <ion-col>
+                  <ion-item>
+                      <ion-label position="floating">年龄</ion-label>
+                      <ion-input [(ngModel)]="age" type="number" placeholder="年龄"></ion-input>
+                  </ion-item>
+              </ion-col>
+          </ion-row>
+          <ion-row>
+              <ion-col>
+                  <ion-item>
+                      <ion-label position="floating">身高 (cm)</ion-label>
+                      <ion-input [(ngModel)]="height" type="number" placeholder="身高"></ion-input>
+                  </ion-item>
+              </ion-col>
+          </ion-row>
+          <ion-row>
+              <ion-col>
+                  <ion-item>
+                      <ion-label position="floating">体重 (kg)</ion-label>
+                      <ion-input [(ngModel)]="weight" type="number" placeholder="体重"></ion-input>
+                  </ion-item>
+              </ion-col>
+          </ion-row>
+          <ion-row>
+              <ion-col>
+                  <ion-item>
+                      <ion-label>健身目标</ion-label>
+                      <ion-select [(ngModel)]="fitnessGoal" placeholder="选择健身目标">
+                          <ion-select-option value="增肌">增肌</ion-select-option>
+                          <ion-select-option value="减脂">减脂</ion-select-option>
+                          <ion-select-option value="塑形">塑形</ion-select-option>
+                          <ion-select-option value="提高体能">提高体能</ion-select-option>
+                      </ion-select>
+                  </ion-item>
+              </ion-col>
+          </ion-row>
+          <ion-row>
+              <ion-col>
+                  <ion-item>
+                      <ion-label>健身经验</ion-label>
+                      <ion-select [(ngModel)]="fitnessExperience" placeholder="选择健身经验">
+                          <ion-select-option value="新手">新手</ion-select-option>
+                          <ion-select-option value="老手">老手</ion-select-option>
+                      </ion-select>
+                  </ion-item>
+              </ion-col>
+          </ion-row>
+          <ion-row>
+              <ion-col>
+                  <ion-item>
+                      <ion-label position="floating">疾病史</ion-label>
+                      <ion-textarea [(ngModel)]="medicalHistory" placeholder="请描述您的疾病史"></ion-textarea>
+                  </ion-item>
+              </ion-col>
+          </ion-row>
+          <ion-row>
+              <ion-col>
+                  <ion-item>
+                      <ion-label position="floating">伤痛情况</ion-label>
+                      <ion-textarea [(ngModel)]="injuryHistory" placeholder="请描述您的伤痛情况"></ion-textarea>
+                  </ion-item>
+              </ion-col>
+          </ion-row>
+      </ion-grid>
+
+      <ion-button expand="full" (click)="addTags()">添加您的标签</ion-button>
+
+      <ion-list>
+          <ion-item *ngFor="let tag of tags">
+              <ion-chip>{{tag}}
+                  <ion-icon name="close" (click)="deleteTag(tag)"></ion-icon>
+              </ion-chip>
+          </ion-item>
+      </ion-list>
+  </ion-card-content>
+</ion-card>

+ 13 - 0
exercise-app/src/app/edit-tag/edit-tag.component.scss

@@ -0,0 +1,13 @@
+ion-label {
+  margin-bottom: 20px; /* 增加底部间距 */
+}
+
+ion-input {
+  margin-top: 20px; /* 增加顶部间距 */
+}
+h5{
+  margin: 20px;
+}
+ion-textarea {
+  margin-top: 20px;
+}

+ 22 - 0
exercise-app/src/app/edit-tag/edit-tag.component.spec.ts

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

+ 69 - 0
exercise-app/src/app/edit-tag/edit-tag.component.ts

@@ -0,0 +1,69 @@
+import { Component, Output, EventEmitter, OnInit } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonChip, IonButton, IonGrid, IonIcon, IonInput, IonItem, IonLabel, IonList, IonSelect, IonSelectOption, IonTextarea, IonRow, IonCol } from '@ionic/angular/standalone';
+import { CommonModule } from '@angular/common'; // 导入 CommonModule
+
+@Component({
+  selector: 'app-edit-tag',
+  templateUrl: './edit-tag.component.html',
+  styleUrls: ['./edit-tag.component.scss'],
+  standalone: true,
+  imports: [
+    FormsModule,
+    IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonChip, IonButton, 
+    IonGrid, IonIcon, IonInput, IonItem, IonLabel, IonList, IonSelect, IonSelectOption, IonTextarea
+    ,IonRow,IonCol,CommonModule
+  ]
+})
+export class EditTagComponent implements OnInit {
+  
+   // 用户输入
+   gender: string = "";
+   age: number | null = null;
+   height: number | null = null;
+   weight: number | null = null;
+   fitnessGoal: string = "";
+   fitnessExperience: string = "";
+   medicalHistory: string = "";
+   injuryHistory: string = "";
+ 
+   // 标签列表
+   tags: Array<string> = [];
+ 
+   @Output() onTagChange: EventEmitter<Array<string>> = new EventEmitter<Array<string>>();
+
+
+   addTags() {
+     const tag = `性别: ${this.gender}, 年龄: ${this.age}, 身高: ${this.height}, 体重: ${this.weight}, 健身目标: ${this.fitnessGoal}, 健身经验: ${this.fitnessExperience}`;
+     this.tags.push(tag);
+     this.emitTags();
+     this.clearInputs();
+   }
+ 
+   deleteTag(tag: string) {
+     const idx = this.tags.findIndex(item => item === tag);
+     if (idx > -1) {
+       this.tags.splice(idx, 1);
+       this.emitTags();
+     }
+   }
+ 
+   emitTags() {
+     this.onTagChange.emit(this.tags);
+   }
+ 
+   clearInputs() {
+     this.gender = "";
+     this.age = null;
+     this.height = null;
+     this.weight = null;
+     this.fitnessGoal = "";
+     this.fitnessExperience = "";
+     this.medicalHistory = "";
+     this.injuryHistory = "";
+   }
+ 
+   constructor() { }
+ 
+   ngOnInit() {}
+ }

+ 33 - 0
exercise-app/src/app/exercise-picture/exercise-picture.component.html

@@ -0,0 +1,33 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>
+      示例:健身目标图片生成{{imagineWork?.progress || 0}}
+    </ion-title> 
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <!-- 生成提示词 -->
+  <ion-textarea [value]="userPrompt" (ionInput)="promptInput($event)" placeholder="健身目标的描述" autoGrow="true"></ion-textarea>
+  <ion-button (click)="createImage()" expand="block">生成健身目标图片</ion-button>
+  <!-- 意境画面提示词 -->
+  <div>
+    {{PictureDescResult}}
+  </div>
+  <!-- 生成结果 -->
+  @if(images.length) {
+    @for(imageUrl of images;track imageUrl){
+      <img [src]="imageUrl" alt="" srcset="">
+    }
+  }
+  <!-- 生成状态 -->
+  @if(!images.length){
+    @if(imagineWork){
+      <h1>生成中</h1>
+    }
+    @if(!imagineWork){
+      <h1>未开始</h1>
+    }
+  }
+
+</ion-content>

+ 0 - 0
aa.txt → exercise-app/src/app/exercise-picture/exercise-picture.component.scss


+ 22 - 0
exercise-app/src/app/exercise-picture/exercise-picture.component.spec.ts

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

+ 69 - 0
exercise-app/src/app/exercise-picture/exercise-picture.component.ts

@@ -0,0 +1,69 @@
+import { Component, OnInit } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { IonTextarea, IonButton,IonInput } from "@ionic/angular/standalone";
+import { DalleOptions, ImagineWork, FmodeChatCompletion } from 'fmode-ng';
+
+@Component({
+  selector: 'app-exercise-picture',
+  templateUrl: './exercise-picture.component.html',
+  styleUrls: ['./exercise-picture.component.scss'],
+  standalone: true,
+  imports: [
+    IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonButton,
+    IonInput,
+    IonTextarea
+  ],
+})
+export class ExercisePictureComponent  implements OnInit {
+
+  userPrompt:string = "主要是想增肌,有八块腹肌,胸肌,肌肉线条完美"
+  promptInput(ev:any){
+    this.userPrompt = ev.detail.value;
+  }
+  imagineWork:ImagineWork|undefined
+  images:Array<string> = []
+  constructor(
+  ){
+    // 示例任务,自己生成图片后请存储新的ID
+    this.imagineWork = new ImagineWork("4mPR0IW1i5");
+    this.imagineWork.fetchTask().then(work=>{
+      this.images = this.imagineWork?.images || [];
+    })
+  }
+
+  PictureDescResult:string = `` // 画面描述结果
+  async createImage(){
+    this.imagineWork = new ImagineWork();
+    // 文本生成
+    let PromptTemplate = `您是一名专业的健身教练,请您根据用户描述的内容,将其描述的身材,体型,性别,以细腻的肌肉感,线条感描述出来,具体描述如下:
+    ${this.userPrompt}
+    `
+    let completion = new FmodeChatCompletion([
+      {role:"system",content:""},
+      {role:"user",content:PromptTemplate}
+    ])
+    completion.sendCompletion().subscribe((message:any)=>{
+      // 打印消息体
+      console.log(message.content)
+      // 赋值消息内容给组件内属性
+      this.PictureDescResult = message.content
+      if(message?.complete){ // 判断message为完成状态,则设置isComplete为完成
+        // 图片生成
+        let PicturePrompt = `${this.PictureDescResult}\n风格:暗色调。画面不带任何文字。突出线条感。`
+        let options:DalleOptions = {prompt:PicturePrompt}
+        this.imagineWork?.draw(options).subscribe(work=>{
+            console.log("imagineWork",work?.toJSON())
+            console.log("images",work?.get("images"))
+            if(work?.get("images")?.length){
+              this.images = work?.get("images");
+            }
+        })
+      }
+    })
+
+    
+  }
+  ngOnInit() {}
+
+}

+ 98 - 0
exercise-app/src/app/page-exercise-analysis/class-chat-completion.ts

@@ -0,0 +1,98 @@
+export interface TestChatMessage{
+  role:string
+  content:string
+}
+export class TestChatCompletion{
+messageList:Array<TestChatMessage>
+constructor(messageList:Array<TestChatMessage>){
+  this.messageList = messageList
+}
+async createCompletionByStream() {
+
+let token = localStorage.getItem("token");
+let bodyJson = {
+"token": `Bearer ${token}`,
+"messages": this.messageList,
+"model": "fmode-4.5-128k",
+"temperature": 0.5,
+"presence_penalty": 0,
+"frequency_penalty": 0,
+"top_p": 1,
+"stream":true
+};
+
+let response = await fetch("https://test.fmode.cn/api/apig/aigc/gpt/v1/chat/completions", {
+"headers": {
+  "accept": "text/event-stream",
+  "sec-fetch-dest": "empty",
+  "sec-fetch-mode": "cors",
+  "sec-fetch-site": "same-site"
+},
+"referrer": "https://ai.fmode.cn/",
+"body": JSON.stringify(bodyJson),
+"method": "POST",
+"mode": "cors",
+"credentials": "omit"
+});
+
+let messageAiReply = ""
+let messageIndex = this.messageList.length
+let reader = response.body?.getReader();
+if (!reader) {
+throw new Error("Failed to get the response reader.");
+}
+
+let decoder = new TextDecoder();
+let buffer = "";
+
+while (true) {
+let { done, value } = await reader.read();
+if (done) {
+  break;
+}
+
+buffer += decoder.decode(value);
+
+// Split the buffer by newlines to get individual messages
+let messages = buffer.split("\n");
+
+// Process each message
+for (let i = 0; i < messages.length - 1; i++) {
+  let message = messages[i];
+
+  // Process the message as needed
+  /**
+   * data: {"id":"chatcmpl-y2PLKqPDnwAFJIj2L5aqdH5TWK9Yv","object":"chat.completion.chunk","created":1696770162,"model":"fmode-4.5-128k","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
+   * data: {"id":"chatcmpl-y2PLKqPDnwAFJIj2L5aqdH5TWK9Yv","object":"chat.completion.chunk","created":1696770162,"model":"fmode-4.5-128k","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
+   * data: [DONE]
+   */
+  let dataText = message.replace("data:\ ","")
+  if(dataText.startsWith("{")){
+    try{
+      let dataJson = JSON.parse(dataText)
+      console.log(dataJson)
+      messageAiReply += dataJson?.choices?.[0]?.delta?.content || ""
+      this.messageList[messageIndex] = {
+        role:"assistant",
+        content:messageAiReply
+      }
+    }catch(err){}
+  }
+  if(dataText.startsWith("[")){
+    console.log(message)
+    console.log("完成")
+    this.messageList[messageIndex] = {
+      role:"assistant",
+      content:messageAiReply
+    }
+    messageAiReply = ""
+  }
+  // Parse the message as JSON
+  // let data = JSON.parse(message);
+
+  // Clear the processed message from the buffer
+  buffer = buffer.slice(message.length + 1);
+}
+}
+}
+}

+ 22 - 0
exercise-app/src/app/page-exercise-analysis/page-exercise-analysis.component.html

@@ -0,0 +1,22 @@
+
+
+<ion-list>
+  <ion-item>
+    运动标签
+  </ion-item>
+  <ion-item>
+    <app-edit-tag (onTagChange)="setTagsValue($event)">
+    </app-edit-tag>
+      
+  </ion-item>
+  <ion-item>
+    <ion-textarea [value]="exerciseDesc" (ionInput)="userInput($event)" label="运动描述" placeholder="具体描述你的感受" [autoGrow]="true"></ion-textarea>
+  </ion-item>
+<ion-item>
+  <ion-button expand="block" (click)="analysisExercise()">个性化训练计划推荐</ion-button>
+</ion-item>
+<ion-item>
+  {{exerciseResult}}
+</ion-item>
+</ion-list>
+

+ 0 - 0
test.md → exercise-app/src/app/page-exercise-analysis/page-exercise-analysis.component.scss


+ 22 - 0
exercise-app/src/app/page-exercise-analysis/page-exercise-analysis.component.spec.ts

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

+ 132 - 0
exercise-app/src/app/page-exercise-analysis/page-exercise-analysis.component.ts

@@ -0,0 +1,132 @@
+import { Component, OnInit } from '@angular/core';
+import { EditTagComponent } from '../edit-tag/edit-tag.component';
+import {IonButton, IonItem, IonList, IonTextarea } from '@ionic/angular/standalone';
+import { TestChatCompletion } from './class-chat-completion';
+// import {FmodeChatCompletion}from "fmode-ng"
+
+@Component({
+  selector: 'app-page-exercise-analysis',
+  templateUrl: './page-exercise-analysis.component.html',
+  styleUrls: ['./page-exercise-analysis.component.scss'],
+  standalone: true,
+  imports:[EditTagComponent,IonList,IonItem,IonTextarea,IonButton]
+})
+export class PageExerciseAnalysisComponent  implements OnInit {
+
+// 运动关键词
+exerciseTags:Array<string>=[]
+setTagsValue(ev:any){
+  this.exerciseTags=ev;
+}
+
+
+// 运动描述
+exerciseDesc:string=""
+
+userInput(ev:any){
+  this.exerciseDesc=ev.detail.value
+}
+
+// 运动结果
+exerciseResult:string=""
+
+// 个性化训练计划推荐
+async analysisExercise(){
+  console.log(this.exerciseTags);
+  console.log(this.exerciseDesc);
+  console.log("开始");
+  let prompt=`您是一名专业的个性化运动推荐专家,请帮我生成一个个性化运动计划,关键词:${this.exerciseTags.join(",")},运动描述:${this.exerciseDesc}`
+  let TOKEN='r:E4KpGvTEto-188798522991732011467'
+  localStorage.setItem("token",TOKEN)
+  let messageList:any=[
+  {
+    role:"system" , content:`${new Date().toLocaleDateString()}`
+  },
+  {
+    role:"user",content:prompt
+  }
+]
+
+//创建并发起一条新的消息补全
+let completion=new TestChatCompletion(messageList)
+//持续更新事件推送的消息体内容绑定至消息变量
+completion.createCompletionByStream()
+
+setInterval(()=>{
+  console.log(messageList)
+  this.exerciseDesc=messageList[messageList.length-1]?.content
+  
+},1000)
+//   let bodyJson = {
+//   "token": `Bearer ${TOKEN}`,
+//   "messages": messageList,
+//   "model": "fmode-4.5-128k",
+//   "temperature": 0.5,
+//   "presence_penalty": 0,
+//   "frequency_penalty": 0,
+//   "top_p": 1,
+//   "stream":true
+// };
+
+// let response = await fetch("https://test.fmode.cn/api/apig/aigc/gpt/v1/chat/completions", {
+//   "headers": {
+//     "accept": "text/event-stream",
+//   },
+//   "body": JSON.stringify(bodyJson),
+//   "method": "POST",
+//   "mode": "cors",
+//   "credentials": "omit"
+// });
+
+// if(response && response.body){
+// let reader = response.body.getReader();
+// if (!reader) {
+//   throw new Error("Failed to get the response reader.");
+// }
+
+// let decoder = new TextDecoder();
+// let buffer = "";
+
+// while (true) {
+//   let { done, value } = await reader.read();
+//   if (done) {
+//     break;
+//   }
+
+//   let data= decoder.decode(value);
+
+//   /**
+//    * data: {"id":"chatcmpl-AXpzLoeWsP9iG5sfnbV5dRtc34sgS","object":"chat.completion.chunk","created":1732628031,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_3de1288069","choices":[{"index":0,"delta":{"content":"。"},"logprobs":null,"finish_reason":null}],"usage":null}
+
+//     data: {"id":"chatcmpl-AXpzLoeWsP9iG5sfnbV5dRtc34sgS","object":"chat.completion.chunk","created":1732628031,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_3de1288069","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":null}
+
+//     data: {"id":"chatcmpl-AXpzLoeWsP9iG5sfnbV5dRtc34sgS","object":"chat.completion.chunk","created":1732628031,"model":"gpt-4o-mini-2024-07-18","system_fingerprint":"fp_3de1288069","choices":[],"usage":{"prompt_tokens":116,"completion_tokens":665,"total_tokens":781,"prompt_tokens_details":{"cached_tokens":0,"audio_tokens":0},"completion_tokens_details":{"reasoning_tokens":0,"audio_tokens":0,"accepted_prediction_tokens":0,"rejected_prediction_tokens":0}}}
+
+//     data: [DONE]
+//    */
+
+//     let messages=data.split("\n");
+//     messages.forEach(message=>{
+//       console.log(message);
+//       let dataStr:string =message.split("data:")[1]
+//       if(dataStr&&dataStr.startsWith("{")){
+//         try{
+//           let json=JSON.parse(dataStr);
+//           let content=json.choices[0].delta.content
+//           console.log(content);
+//           this.exerciseResult=this.exerciseResult+content
+          
+//         }catch(err){}
+//       }
+      
+//     })
+//   console.log(data);
+  
+// }
+// }
+}
+  constructor() { }
+
+  ngOnInit() {}
+
+}

+ 3 - 0
exercise-app/src/app/page-rxjs/page-rxjs.component.html

@@ -0,0 +1,3 @@
+<p>
+  page-rxjs works!
+</p>

+ 0 - 0
exercise-app/src/app/page-rxjs/page-rxjs.component.scss


+ 22 - 0
exercise-app/src/app/page-rxjs/page-rxjs.component.spec.ts

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

+ 19 - 0
exercise-app/src/app/page-rxjs/page-rxjs.component.ts

@@ -0,0 +1,19 @@
+import { Component, OnInit } from '@angular/core';
+import { from } from 'rxjs';
+
+@Component({
+  selector: 'app-page-rxjs',
+  templateUrl: './page-rxjs.component.html',
+  styleUrls: ['./page-rxjs.component.scss'],
+  standalone: true,
+})
+export class PageRxjsComponent  implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {}
+
+  textRxjs(){
+    from
+  }
+}

+ 25 - 0
exercise-app/src/app/page-test/page-test.component.html

@@ -0,0 +1,25 @@
+<h1>星星打分:分值:{{currentScore}}</h1>
+<edit-rating-star 
+  [score]="currentScore" 
+  [scoreMax]="5" 
+  (onScoreChange)="handleScoreChange($event)">
+</edit-rating-star>
+
+
+<!-- <h1>编辑标签</h1>
+<app-edit-tag (onTagChange)="setTagsValue($event)"></app-edit-tag>
+
+  <h1>父级页面,从子组件输出事件获取属性</h1>
+  <ul>
+    @for (tag of editTags; track tag;) {
+      <li>{{tag}}</li>
+    }
+  </ul> -->
+
+  <h1>编辑标签</h1>
+  <app-edit-tag (onTagChange)="setTagsValue($event)"></app-edit-tag>
+  
+  <h1>父级页面,从子组件输出事件获取属性</h1>
+  <ul>
+    <li *ngFor="let tag of editTags">{{tag}}</li>
+  </ul>

+ 0 - 0
exercise-app/src/app/page-test/page-test.component.scss


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

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

+ 40 - 0
exercise-app/src/app/page-test/page-test.component.ts

@@ -0,0 +1,40 @@
+import { Component, OnInit } from '@angular/core';
+import { EditTagComponent } from '../edit-tag/edit-tag.component';
+import { EditRatingStarComponent } from '../edit-rating-star/edit-rating-star.component';
+import { CommonModule } from '@angular/common';
+
+
+@Component({
+  selector: 'app-page-test',
+  templateUrl: './page-test.component.html',
+  styleUrls: ['./page-test.component.scss'],
+  standalone: true,
+  imports: [EditTagComponent,EditRatingStarComponent,
+    CommonModule
+  ],
+})
+export class PageTestComponent  implements OnInit {
+toRecordSport() {
+throw new Error('Method not implemented.');
+}
+
+  constructor() { }
+
+  ngOnInit() {}
+
+   // 星星打分
+   currentScore: number = 0; // 初始分值
+
+   handleScoreChange(newScore: number) {
+     this.currentScore = newScore;
+     console.log('新分值:', newScore); // 处理分值变化
+   }
+ 
+ 
+   editTags: Array<string> = [];
+
+   setTagsValue(ev: Array<string>) {
+     console.log("setTagsValue", ev);
+     this.editTags = ev;
+   }
+}

+ 22 - 0
exercise-app/src/app/page-train-recom/page-train-recom.component.html

@@ -0,0 +1,22 @@
+<ion-content>
+
+  <!-- <h1>健身类型</h1> -->
+  <ion-input [value]="leixing" (ionInput)="leixingInput($event)" ></ion-input >
+
+  <!-- 文本域:生成提示词 -->
+   <!-- <h1>健身状态的描述</h1> -->
+  <ion-textarea [value]="userPrompt" (ionInput)="promptInput($event)" placeholder="文本提示词" autoGrow="true"></ion-textarea>
+
+  <!-- 按钮:执行消息生成函数 -->
+  <ion-button (click)="sendMessage()" expand="block">初步的健身建议</ion-button>
+    
+   <!-- 展示:返回消息内容 -->
+  <!-- 消息传输过程中,实时预览 -->
+  @if(!isComplete){
+    <div>{{responseMsg}}</div>
+  }
+  <!-- 消息传输完成后,实时预览Markdown格式 -->
+  @if(isComplete){
+    <fm-markdown-preview class="content-style" [content]="responseMsg"></fm-markdown-preview>
+  }
+</ion-content>

+ 0 - 0
exercise-app/src/app/page-train-recom/page-train-recom.component.scss


+ 22 - 0
exercise-app/src/app/page-train-recom/page-train-recom.component.spec.ts

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

+ 60 - 0
exercise-app/src/app/page-train-recom/page-train-recom.component.ts

@@ -0,0 +1,60 @@
+import { Component, OnInit,Input, Output, EventEmitter } from '@angular/core';
+import { IonButton, IonContent, IonHeader, IonInput, IonTextarea, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+/** 引用:从fmode-ng库引用FmodeChatCompletion类 */
+import { FmodeChatCompletion ,MarkdownPreviewModule} from 'fmode-ng';
+
+
+@Component({
+  selector: 'app-page-train-recom',
+  templateUrl: './page-train-recom.component.html',
+  styleUrls: ['./page-train-recom.component.scss'],
+  standalone: true,
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton,IonTextarea,IonInput
+    ,MarkdownPreviewModule
+   ],
+})
+export class PageTrainRecomComponent  implements OnInit {
+  @Input() tags: string[] = []; // 接收标签信息
+  @Output() recommendationGenerated = new EventEmitter<string>(); // 输出生成的建议
+ 
+ 
+  constructor() {}
+  ngOnInit(){}
+  // 用户输入提示词
+  leixing:string = "健身类型:"
+  leixingInput(ev:any){
+    this.leixing = ev.detail.value;
+  }
+  // 用户输入提示词
+  userPrompt:string = "请描述您所想要达到的健身状态:"
+  promptInput(ev:any){
+    this.userPrompt = ev.detail.value;
+  }
+  // 属性:组件内用于展示消息内容的变量
+  responseMsg:string = ""
+  // 方法:实例化completion对象,传入消息数组,并订阅生成的可观察对象。
+  isComplete:boolean = false; // 定义完成状态属性,用来标记是否补全完成
+  sendMessage(){
+    console.log("create")
+
+    let PromptTemplate = `
+    您作为一名专业的${this.leixing}健身教练,请您根据用户描述的健身类型和标签,给出一些建议。
+    以下是用户的口述:${this.userPrompt}
+    用户的标签:${this.tags.join(', ')}
+    `;
+
+    let completion = new FmodeChatCompletion([
+      {role:"system",content:""},
+      {role:"user",content:PromptTemplate}
+    ])
+    completion.sendCompletion().subscribe((message:any)=>{
+      console.log("AI返回的内容:", message.content); // 打印完整内容
+      this.responseMsg = message.content;
+      this.recommendationGenerated.emit(this.responseMsg); // 发出生成的建议
+      if (message?.complete) {
+          this.isComplete = true;
+      }
+  });
+  }
+
+}

+ 90 - 90
exercise-app/src/app/tab1/tab1.page.html

@@ -1,117 +1,117 @@
 <ion-header>
-  <ion-toolbar>
-    <ion-buttons slot="start">
-      <ion-logo src="assets/logo.png"></ion-logo>
-    </ion-buttons>
-    <ion-title>健身APP</ion-title>
+  <ion-toolbar color="tertiary">
+    <ion-title>首页</ion-title>
     <ion-buttons slot="end">
-      <ion-searchbar placeholder="搜索"></ion-searchbar>
+      <ion-avatar>
+        <img src="../../assets/image.png" alt="用户头像">
+      </ion-avatar>
     </ion-buttons>
   </ion-toolbar>
 </ion-header>
-<ion-toolbar>
-  <ion-segment value="home">
-    <ion-segment-button value="home">
-      <ion-icon name="home"></ion-icon>
-      首页
-    </ion-segment-button>
-    <ion-segment-button value="training">
-      <ion-icon name="fitness"></ion-icon>
-      训练计划
-    </ion-segment-button>
-    <ion-segment-button value="nutrition">
-      <ion-icon name="nutrition"></ion-icon>
-      营养管理
-    </ion-segment-button>
-    <ion-segment-button value="community">
-      <ion-icon name="people"></ion-icon>
-      社区
-    </ion-segment-button>
-    <ion-segment-button value="profile">
-      <ion-icon name="person"></ion-icon>
-      个人中心
-    </ion-segment-button>
-  </ion-segment>
-</ion-toolbar>
 
 <ion-content>
-  <ion-slides pager="true">
-    <ion-slide>
-      <img src="assets/banner1.jpg" />
-      <h2>开启你的健身之旅!</h2>
-    </ion-slide>
-    <ion-slide>
-      <img src="assets/banner2.jpg" />
-      <h2>最新活动:挑战赛进行中!</h2>
-    </ion-slide>
-    <ion-slide>
-      <img src="assets/banner3.jpg" />
-      <h2>用户成功案例分享</h2>
-    </ion-slide>
-  </ion-slides>
+  <!-- 可填入的标签 -->
+  <ion-card color="secondary">
+    <ion-card-header>
+      <ion-card-title>填写您的健身信息</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+
+      <app-edit-tag (onTagChange)="setTagsValue($event)"></app-edit-tag>
+      <h1>您的输入如下:</h1>
+      <ul>
+        <!-- @for (tag of editTags; track tag;) {
+          <li>{{tag}}</li>
+        } -->
+        <li *ngFor="let tag of editTags">{{tag}}</li>
+      </ul>
 
-  <ion-card>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 个性化健身训练推荐 -->
+  <ion-card color="light">
     <ion-card-header>
-      <ion-card-title>个性化推荐</ion-card-title>
+      <ion-card-title>个性化健身训练推荐</ion-card-title>
     </ion-card-header>
+    <!-- <ion-card-content>
+      <page-train-recom [tags]="editTags" (recommendationGenerated)="onRecommendationGenerated($event)"></page-train-recom>
+      <ion-button (click)="sendRecommendation()" expand="block">获取个性化健身建议</ion-button>
+    </ion-card-content> -->
     <ion-card-content>
-      <ion-list>
-        <ion-item>
-          <ion-label>个性化训练计划推荐</ion-label>
-          <ion-button slot="end">查看</ion-button>
-        </ion-item>
-        <ion-item>
-          <ion-label>近期热门饮食方案推荐</ion-label>
-          <ion-button slot="end">查看</ion-button>
-        </ion-item>
-        <ion-item>
-          <ion-label>社区动态推荐</ion-label>
-          <ion-button slot="end">查看</ion-button>
-        </ion-item>
-      </ion-list>
+      <app-page-train-recom 
+        [tags]="editTags" 
+        (recommendationGenerated)="onRecommendationGenerated($event)">
+      </app-page-train-recom >
+      <ion-button (click)="sendRecommendation()" expand="block">获取个性化健身建议</ion-button>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 显示 AI 生成的建议 -->
+  <ion-card *ngIf="aiRecommendation" color="tertiary">
+    <ion-card-header>
+      <ion-card-title>AI 生成的健身建议
+        :{{ aiRecommendation }}
+      </ion-card-title> 
+    </ion-card-header>
+    <ion-card-content>
+       <!-- <fm-markdown-preview class="content-style" [content]="aiRecommendation"></fm-markdown-preview> -->
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 提交图片生成健身后效果 -->
+  <ion-card color="tertiary">
+    <ion-card-header>
+      <ion-card-title>健身后效果预览</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-item>
+        <ion-label>上传您的照片</ion-label>
+        <ion-button slot="end" color="primary">上传</ion-button>
+      </ion-item>
+      <p>AI将为您生成健身后的效果图。</p>
     </ion-card-content>
   </ion-card>
 
-  <ion-grid>
-    <ion-row>
-      <ion-col>
-        <ion-button expand="full" routerLink="/training">
-          <ion-icon name="fitness"></ion-icon>
-          开始训练
-        </ion-button>
-      </ion-col>
-      <ion-col>
-        <ion-button expand="full" routerLink="/nutrition">
-          <ion-icon name="nutrition"></ion-icon>
-          记录饮食
-        </ion-button>
-      </ion-col>
-      <ion-col>
-        <ion-button expand="full" routerLink="/community">
-          <ion-icon name="people"></ion-icon>
-          查看社区
-        </ion-button>
-      </ion-col>
-    </ion-row>
-  </ion-grid>
+  <!-- 欢迎信息 -->
+  <ion-card color="light">
+    <ion-card-header>
+      <ion-card-title>欢迎回来,张三!</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <p>今天是个锻炼的好日子!</p>
+    </ion-card-content>
+  </ion-card>
 
-  <ion-card>
+  <!-- 热门推荐 -->
+  <ion-card color="secondary">
     <ion-card-header>
-      <ion-card-title>用户反馈</ion-card-title>
+      <ion-card-title>热门推荐</ion-card-title>
     </ion-card-header>
     <ion-card-content>
       <ion-list>
         <ion-item>
-          <ion-label>用户A:FitMind AI的训练计划让我进步很快!</ion-label>
+          <ion-label>全身锻炼计划</ion-label>
+          <ion-button slot="end" color="primary">查看</ion-button>
         </ion-item>
         <ion-item>
-          <ion-label>用户B:饮食管理功能非常实用!</ion-label>
+          <ion-label>健康饮食指南</ion-label>
+          <ion-button slot="end" color="primary">查看</ion-button>
         </ion-item>
       </ion-list>
-      <ion-range min="0" max="5" value="4" color="primary">
-        <ion-label slot="start">评分</ion-label>
-        <ion-label slot="end">5</ion-label>
-      </ion-range>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 最新活动 -->
+  <ion-card color="tertiary">
+    <ion-card-header>
+      <ion-card-title>最新活动</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <p>参与我们的夏季健身挑战,赢取丰厚奖品!</p>
+      <ion-button expand="full" color="secondary">了解更多</ion-button>
     </ion-card-content>
   </ion-card>
 </ion-content>
+
+<!-- <ion-button (click)="goTestPage()">进入测试页</ion-button> -->

+ 76 - 5
exercise-app/src/app/tab1/tab1.page.ts

@@ -1,14 +1,85 @@
-import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
-import { ExploreContainerComponent } from '../explore-container/explore-container.component';
+import { Component, ViewChild } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, 
+  IonButtons,IonSearchbar,IonSegment,IonSegmentButton
+  ,IonIcon,IonCard,IonCardHeader,IonCardContent,IonList
+  ,IonItem,IonLabel,IonButton,IonGrid,IonRow,IonCol,IonCardTitle,IonRange,
+  IonAvatar,
+  IonFooter
+ 
+} from '@ionic/angular/standalone';
+import { Router } from '@angular/router';
+import { EditTagComponent } from '../edit-tag/edit-tag.component';
+
+import { FmodeChatCompletion ,MarkdownPreviewModule} from 'fmode-ng';
+import { PageTrainRecomComponent } from '../page-train-recom/page-train-recom.component';
+import { CommonModule } from '@angular/common'; // 导入 CommonModule
 
 @Component({
   selector: 'app-tab1',
   templateUrl: 'tab1.page.html',
   styleUrls: ['tab1.page.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonButtons,IonSearchbar,IonSegment,IonSegmentButton,
+    IonIcon,IonCard,IonCardHeader,IonCardContent,IonList
+    ,IonItem,IonLabel,IonButton,IonGrid,IonRow,IonCol,IonCardTitle,IonRange
+   ,IonAvatar,IonFooter,
+   EditTagComponent,PageTrainRecomComponent,
+  MarkdownPreviewModule,CommonModule
+  ,
+  ],
+
+  
 })
 export class Tab1Page {
-  constructor() {}
+  @ViewChild(PageTrainRecomComponent) pageTrainRecom!: PageTrainRecomComponent; // 使用 @ViewChild 获取子组件
+  
+
+  editTags:Array<string>=[]
+  aiRecommendation: string = ""; // 存储 AI 的建议
+  // isGenerated: boolean = false; // 标记 AI 建议是否已生成
+  toRecordSport(){
+    alert("开始运动")
+  }
+
+  constructor(private router:Router) {}
+  goTestPage(){ 
+    this.router.navigate(['/tabs/test']);
+   }
+
+   // 导航到指定页面
+ navigateTo(page: string)
+ {
+  this.router.navigate(['/tabs/${page}']);
+ }
+
+   // 编辑标签
+
+
+
+  // 更新标签列表的方法
+  setTagsValue(ev: Array<string>) {
+    console.log("setTagsValue", ev);
+    this.editTags = ev;
+  }
+
+  // 删除标签的方法
+  removeTag(tag: string) {
+    this.editTags = this.editTags.filter(t => t !== tag);
+  }
+
+  sendRecommendation() {
+    // 触发子组件的 sendMessage 方法
+    if (this.pageTrainRecom) {
+      this.pageTrainRecom.sendMessage();
+    }
+  }
+
+  // 接收 AI 的建议
+  onRecommendationGenerated(recommendation: string) {
+    console.log("AI建议:", recommendation); // 调试输出
+    
+    this.aiRecommendation = recommendation; // 更新 AI 建议
+    // this.isGenerated = true; // 设置标记为 true,表示建议已生成
+  }
 }

+ 43 - 12
exercise-app/src/app/tab2/tab2.page.html

@@ -1,17 +1,48 @@
-<ion-header [translucent]="true">
+<ion-header>
   <ion-toolbar>
-    <ion-title>
-      Tab 2
-    </ion-title>
+    <ion-title>今日</ion-title>
   </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>
+<ion-content>
+  <!-- 今日目标 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>今日目标</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <p>完成30分钟的有氧运动</p>
+      <p>饮水2000ml</p>
+    </ion-card-content>
+  </ion-card>
 
-  <app-explore-container name="Tab 2 page"></app-explore-container>
-</ion-content>
+  <!-- 推荐课程 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>推荐课程</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list>
+        <ion-item>
+          <ion-label>瑜伽初学者课程</ion-label>
+          <ion-button slot="end">开始</ion-button>
+        </ion-item>
+        <ion-item>
+          <ion-label>HIIT高强度间歇训练</ion-label>
+          <ion-button slot="end">开始</ion-button>
+        </ion-item>
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 活动和挑战 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>活动与挑战</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <p>参与7天健身挑战,赢取奖品!</p>
+      <ion-button expand="full">查看详情</ion-button>
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 10 - 4
exercise-app/src/app/tab2/tab2.page.ts

@@ -1,16 +1,22 @@
 import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonCardHeader, IonList, IonItem, IonLabel, IonButton, IonCardTitle, IonGrid, IonRow, IonCol, IonAvatar, IonButtons, IonIcon } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
-
+import { Router } from '@angular/router';
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
   styleUrls: ['tab2.page.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent]
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,
+    IonCard,IonCardContent,IonCardHeader,IonList,IonItem,IonLabel,IonButton,IonCardTitle,
+    IonGrid,IonRow,IonCol,IonButtons,IonAvatar,IonIcon
+  ]
 })
 export class Tab2Page {
 
-  constructor() {}
+  constructor(private router:Router) {}
 
+  goToPage2(){
+    this.router.navigate(['/tabs/train-recom'])
+  }
 }

+ 32 - 12
exercise-app/src/app/tab3/tab3.page.html

@@ -1,17 +1,37 @@
-<ion-header [translucent]="true">
+<ion-header>
   <ion-toolbar>
-    <ion-title>
-      Tab 3
-    </ion-title>
+    <ion-title>运动</ion-title>
   </ion-toolbar>
 </ion-header>
 
-<ion-content [fullscreen]="true">
-  <ion-header collapse="condense">
-    <ion-toolbar>
-      <ion-title size="large">Tab 3</ion-title>
-    </ion-toolbar>
-  </ion-header>
+<ion-content>
+  <!-- 训练计划 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>训练计划</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list>
+        <ion-item>
+          <ion-label>全身锻炼</ion-label>
+          <ion-button slot="end">查看详情</ion-button>
+        </ion-item>
+        <ion-item>
+          <ion-label>核心力量训练</ion-label>
+          <ion-button slot="end">查看详情</ion-button>
+        </ion-item>
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
 
-  <app-explore-container name="Tab 3 page"></app-explore-container>
-</ion-content>
+  <!-- 运动记录 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>运动记录</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <p>今日步数:8000步</p>
+      <p>卡路里消耗:500卡</p>
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 4 - 2
exercise-app/src/app/tab3/tab3.page.ts

@@ -1,5 +1,5 @@
 import { Component } from '@angular/core';
-import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonCardHeader, IonList, IonItem, IonLabel, IonButton, IonCardTitle } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
 
 @Component({
@@ -7,7 +7,9 @@ import { ExploreContainerComponent } from '../explore-container/explore-containe
   templateUrl: 'tab3.page.html',
   styleUrls: ['tab3.page.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent],
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,
+    IonCard,IonCardContent,IonCardHeader,IonList,IonItem,IonLabel,IonButton,IonCardTitle
+  ]
 })
 export class Tab3Page {
   constructor() {}

+ 37 - 0
exercise-app/src/app/tab4/tab4.page.html

@@ -0,0 +1,37 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-title>商城</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <!-- 热门商品 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>热门商品</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list>
+        <ion-item>
+          <ion-label>瑜伽垫</ion-label>
+          <ion-button slot="end">购买</ion-button>
+        </ion-item>
+        <ion-item>
+          <ion-label>健身手环</ion-label>
+          <ion-button slot="end">购买</ion-button>
+        </ion-item>
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 优惠活动 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>优惠活动</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <p>满200减50,限时优惠!</p>
+      <ion-button expand="full">查看详情</ion-button>
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 0 - 0
exercise-app/src/app/tab4/tab4.page.scss


+ 17 - 0
exercise-app/src/app/tab4/tab4.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Tab4Page } from './tab4.page';
+
+describe('Tab4Page', () => {
+  let component: Tab4Page;
+  let fixture: ComponentFixture<Tab4Page>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(Tab4Page);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 23 - 0
exercise-app/src/app/tab4/tab4.page.ts

@@ -0,0 +1,23 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { IonButton, IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+
+@Component({
+  selector: 'app-tab4',
+  templateUrl: './tab4.page.html',
+  styleUrls: ['./tab4.page.scss'],
+  standalone: true,
+  imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule,
+    IonCard,IonCardContent,IonCardHeader,IonList,IonItem,IonLabel,IonButton,IonCardTitle
+  ]
+
+})
+export class Tab4Page implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}

+ 30 - 0
exercise-app/src/app/tab5/tab5.page.html

@@ -0,0 +1,30 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-title>我的</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <!-- 用户信息 -->
+  <ion-card color="light">
+    <ion-card-header>
+      <ion-card-title>用户信息</ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <p>用户名:张三</p>
+      <p>联系方式19987546595</p>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 设置 -->
+  <ion-list>
+    <ion-item>
+      <ion-label>账户设置</ion-label>
+      <ion-button slot="end" color="secondary">编辑</ion-button>
+    </ion-item>
+    <ion-item>
+      <ion-label>隐私设置</ion-label>
+      <ion-button slot="end" color="secondary">编辑</ion-button>
+    </ion-item>
+  </ion-list>
+</ion-content>

+ 0 - 0
exercise-app/src/app/tab5/tab5.page.scss


+ 17 - 0
exercise-app/src/app/tab5/tab5.page.spec.ts

@@ -0,0 +1,17 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { Tab5Page } from './tab5.page';
+
+describe('Tab5Page', () => {
+  let component: Tab5Page;
+  let fixture: ComponentFixture<Tab5Page>;
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(Tab5Page);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 24 - 0
exercise-app/src/app/tab5/tab5.page.ts

@@ -0,0 +1,24 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { IonButton, IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+
+
+@Component({
+  selector: 'app-tab5',
+  templateUrl: './tab5.page.html',
+  styleUrls: ['./tab5.page.scss'],
+  standalone: true,
+  imports: [IonContent, IonHeader, IonTitle, IonToolbar, CommonModule, FormsModule,
+    IonCard,IonCardContent,IonCardHeader,IonList,IonItem,IonLabel,IonButton,IonCardTitle
+  ]
+
+})
+export class Tab5Page implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}

+ 11 - 1
exercise-app/src/app/tabs/tabs.page.html

@@ -6,11 +6,21 @@
     </ion-tab-button>
 
     <ion-tab-button tab="tab2" href="/tabs/tab2">
+      <ion-icon aria-hidden="true" name="today"></ion-icon>
+      <ion-label>今日</ion-label>
+    </ion-tab-button>
+
+    <ion-tab-button tab="tab3" href="/tabs/tab3">
       <ion-icon aria-hidden="true" name="heart-circle"></ion-icon>
       <ion-label>运动</ion-label>
     </ion-tab-button>
 
-    <ion-tab-button tab="tab3" href="/tabs/tab3">
+    <ion-tab-button tab="tab4" href="/tabs/tab4">
+      <ion-icon aria-hidden="true" name="storefront"></ion-icon>
+      <ion-label>商城</ion-label>
+    </ion-tab-button>
+
+    <ion-tab-button tab="tab5" href="/tabs/tab5">
       <ion-icon aria-hidden="true" name="person"></ion-icon>
       <ion-label>我的</ion-label>
     </ion-tab-button>

+ 9 - 8
exercise-app/src/app/tabs/tabs.page.ts

@@ -1,23 +1,24 @@
 import { Component, EnvironmentInjector, inject } from '@angular/core';
-import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, IonButton, IonButtons, IonSearchbar, IonSegment, IonSegmentButton, IonicSlides, IonCard, IonCardContent, IonCardTitle, IonList, IonItem } from '@ionic/angular/standalone';
 import { addIcons } from 'ionicons';
-import { home,heartCircle,person,  } from 'ionicons/icons';
+import { IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel, IonCard, IonCardContent, IonCardHeader, IonList, IonItem, IonButton, IonCardTitle} from '@ionic/angular/standalone';
 
+import { home,person,heartCircle,storefront,today  } from 'ionicons/icons';
+
+// import { star } from 'ionicons/icons';
+// import { starOutline } from 'ionicons/icons';
+// addIcons({star,starOutline})
 @Component({
   selector: 'app-tabs',
   templateUrl: 'tabs.page.html',
   styleUrls: ['tabs.page.scss'],
   standalone: true,
-  imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel
-    ,IonButtons,IonSearchbar,IonSegment,IonSegmentButton,
-IonCard,IonCardContent,IonCardTitle,IonCardContent,
-IonList,IonItem,IonButton
-  ],
+  imports: [IonTabs, IonTabBar, IonTabButton, IonIcon, IonLabel,
+  IonCard,IonCardContent,IonCardHeader,IonList,IonItem,IonButton,IonCardTitle],
 })
 export class TabsPage {
   public environmentInjector = inject(EnvironmentInjector);
 
   constructor() {
-    addIcons({ home,heartCircle,person });
+    addIcons({ home,person,heartCircle,storefront,today });
   }
 }

+ 31 - 0
exercise-app/src/app/tabs/tabs.routes.ts

@@ -21,11 +21,42 @@ export const routes: Routes = [
         loadComponent: () =>
           import('../tab3/tab3.page').then((m) => m.Tab3Page),
       },
+      {
+        path: 'tab4',
+        loadComponent: () =>
+          import('../tab4/tab4.page').then((m) => m.Tab4Page),
+      },
+      {
+        path: 'tab5',
+        loadComponent: () =>
+          import('../tab5/tab5.page').then((m) => m.Tab5Page),
+      },
+      {
+        path: 'test',
+        loadComponent: () =>
+          import('../page-test/page-test.component').then((m) => m.PageTestComponent),
+      },
+      {
+        path: 'exercise/analysis',
+        loadComponent: () =>
+          import('../page-exercise-analysis/page-exercise-analysis.component').then((m) => m.PageExerciseAnalysisComponent),
+      },
+
+      {
+        path: 'picture',
+        loadComponent: () =>
+          import('../exercise-picture/exercise-picture.component').then((m) => m.ExercisePictureComponent),
+      },
       {
         path: '',
         redirectTo: '/tabs/tab1',
         pathMatch: 'full',
       },
+      {
+        path: 'train-recom',
+        loadComponent: () =>
+          import('../page-train-recom/page-train-recom.component').then((m) => m.PageTrainRecomComponent),
+      },
     ],
   },
   {

BIN=BIN
exercise-app/src/assets/image.png


+ 19 - 0
exercise-app/src/main.ts

@@ -5,10 +5,29 @@ import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalo
 import { routes } from './app/app.routes';
 import { AppComponent } from './app/app.component';
 
+
+// 引用HttpClient方法
+import { provideHttpClient } from '@angular/common/http';
+// 引用移动端授权检测供应器
+import { Diagnostic } from '@awesome-cordova-plugins/diagnostic/ngx';
+// 设置Parse服务属性
+import Parse from "parse";
+Parse.initialize("ncloudmaster");
+Parse.serverURL = "https://server.fmode.cn/parse";
+localStorage.setItem("NOVA_APIG_SERVER", 'aHR0cHMlM0ElMkYlMkZzZXJ2ZXIuZm1vZGUuY24lMkZhcGklMkZhcGlnJTJG')
+
+// 注意:替换Token 根据Token设置Parse服务帐套权限
+Parse.User.become('r:E4KpGvTEto-188798522991732011467')
+
+
 bootstrapApplication(AppComponent, {
   providers: [
     { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
     provideIonicAngular(),
     provideRouter(routes, withPreloading(PreloadAllModules)),
+    // 添加HttpClient供应器
+    provideHttpClient(),
+    // 添加Diagnostic
+    Diagnostic,
   ],
 });

+ 1 - 0
exercise-app/tsconfig.json

@@ -2,6 +2,7 @@
 {
   "compileOnSave": false,
   "compilerOptions": {
+    "allowSyntheticDefaultImports":true,
     "baseUrl": "./",
     "outDir": "./dist/out-tsc",
     "forceConsistentCasingInFileNames": true,

+ 5 - 0
ionic.config.json

@@ -0,0 +1,5 @@
+{
+  "name": "exercise-app",
+  "integrations": {},
+  "type": "angular-standalone"
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio