3d2y1 vor 3 Tagen
Ursprung
Commit
75d72a5484
28 geänderte Dateien mit 2051 neuen und 28 gelöschten Zeilen
  1. 137 17
      app/myapp/package-lock.json
  2. 1 1
      app/myapp/package.json
  3. 1 8
      app/myapp/src/app/tab1/AIwenbenbuquan/page-training-plan/page-training-plan.component.html
  4. 2 2
      app/myapp/src/app/tab1/tab1.page.scss
  5. 19 0
      app/myapp/src/app/tabs/tabs-routing.module.ts
  6. 6 0
      app/myapp/src/app/tabs/tabs.page.html
  7. 324 0
      app/myapp/src/demo/sport/import-sport-data.ts
  8. 208 0
      app/myapp/src/demo/sport/page-sport-display/page-sport-display.component.html
  9. 63 0
      app/myapp/src/demo/sport/page-sport-display/page-sport-display.component.scss
  10. 22 0
      app/myapp/src/demo/sport/page-sport-display/page-sport-display.component.spec.ts
  11. 72 0
      app/myapp/src/demo/sport/page-sport-display/page-sport-display.component.ts
  12. 115 0
      app/myapp/src/demo/sport/page-sport-list/page-sport-list.component.html
  13. 55 0
      app/myapp/src/demo/sport/page-sport-list/page-sport-list.component.scss
  14. 22 0
      app/myapp/src/demo/sport/page-sport-list/page-sport-list.component.spec.ts
  15. 48 0
      app/myapp/src/demo/sport/page-sport-list/page-sport-list.component.ts
  16. 431 0
      app/myapp/src/lib/ncloud.ts
  17. 66 0
      app/myapp/src/lib/user/page-mine/modal-user-edit/modal-user-edit.component.html
  18. 27 0
      app/myapp/src/lib/user/page-mine/modal-user-edit/modal-user-edit.component.scss
  19. 22 0
      app/myapp/src/lib/user/page-mine/modal-user-edit/modal-user-edit.component.spec.ts
  20. 65 0
      app/myapp/src/lib/user/page-mine/modal-user-edit/modal-user-edit.component.ts
  21. 36 0
      app/myapp/src/lib/user/page-mine/modal-user-login/modal-user-login.component.html
  22. 0 0
      app/myapp/src/lib/user/page-mine/modal-user-login/modal-user-login.component.scss
  23. 22 0
      app/myapp/src/lib/user/page-mine/modal-user-login/modal-user-login.component.spec.ts
  24. 92 0
      app/myapp/src/lib/user/page-mine/modal-user-login/modal-user-login.component.ts
  25. 62 0
      app/myapp/src/lib/user/page-mine/page-mine.component.html
  26. 51 0
      app/myapp/src/lib/user/page-mine/page-mine.component.scss
  27. 22 0
      app/myapp/src/lib/user/page-mine/page-mine.component.spec.ts
  28. 60 0
      app/myapp/src/lib/user/page-mine/page-mine.component.ts

+ 137 - 17
app/myapp/package-lock.json

@@ -34,7 +34,7 @@
         "@angular-eslint/eslint-plugin-template": "^19.0.0",
         "@angular-eslint/schematics": "^19.0.0",
         "@angular-eslint/template-parser": "^19.0.0",
-        "@angular/cli": "^19.0.0",
+        "@angular/cli": "19.2.13",
         "@angular/compiler-cli": "^19.0.0",
         "@angular/language-service": "^19.0.0",
         "@capacitor/cli": "7.2.0",
@@ -301,13 +301,13 @@
       }
     },
     "node_modules/@angular-devkit/schematics": {
-      "version": "19.2.8",
-      "resolved": "https://registry.npmmirror.com/@angular-devkit/schematics/-/schematics-19.2.8.tgz",
-      "integrity": "sha512-QsmFuYdAyeCyg9WF/AJBhFXDUfCwmDFTEbsv5t5KPSP6slhk0GoLNZApniiFytU2siRlSxVNpve2uATyYuAYkQ==",
+      "version": "19.2.13",
+      "resolved": "https://registry.npmmirror.com/@angular-devkit/schematics/-/schematics-19.2.13.tgz",
+      "integrity": "sha512-NhSPz3lI9njEo8eMUlZVGtlXl12UcNZv5lWTBZY/FGWUu6P5ciD/9iJINbc1jiaDH5E/DLEicUNuai0Q91X4Nw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@angular-devkit/core": "19.2.8",
+        "@angular-devkit/core": "19.2.13",
         "jsonc-parser": "3.3.1",
         "magic-string": "0.30.17",
         "ora": "5.4.1",
@@ -319,6 +319,34 @@
         "yarn": ">= 1.13.0"
       }
     },
+    "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": {
+      "version": "19.2.13",
+      "resolved": "https://registry.npmmirror.com/@angular-devkit/core/-/core-19.2.13.tgz",
+      "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ajv": "8.17.1",
+        "ajv-formats": "3.0.1",
+        "jsonc-parser": "3.3.1",
+        "picomatch": "4.0.2",
+        "rxjs": "7.8.1",
+        "source-map": "0.7.4"
+      },
+      "engines": {
+        "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      },
+      "peerDependencies": {
+        "chokidar": "^4.0.0"
+      },
+      "peerDependenciesMeta": {
+        "chokidar": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@angular-devkit/schematics/node_modules/rxjs": {
       "version": "7.8.1",
       "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.1.tgz",
@@ -636,18 +664,18 @@
       }
     },
     "node_modules/@angular/cli": {
-      "version": "19.2.8",
-      "resolved": "https://registry.npmmirror.com/@angular/cli/-/cli-19.2.8.tgz",
-      "integrity": "sha512-8/6HBgmqjE8fODFeIIohHVbmCjYlYQj3anvZneEUAGlRbr2IvLUxj7k1/O+9pawEEsOsyjXh5bIvFmEzL19fBw==",
+      "version": "19.2.13",
+      "resolved": "https://registry.npmmirror.com/@angular/cli/-/cli-19.2.13.tgz",
+      "integrity": "sha512-dDRCS73/lrItWx9j4SmwHR56GiZsW8ObNi2q9l/1ny813CG9K43STYFG/wJvGS7ZF3y5hvjIiJOwBx2YIouOIw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@angular-devkit/architect": "0.1902.8",
-        "@angular-devkit/core": "19.2.8",
-        "@angular-devkit/schematics": "19.2.8",
+        "@angular-devkit/architect": "0.1902.13",
+        "@angular-devkit/core": "19.2.13",
+        "@angular-devkit/schematics": "19.2.13",
         "@inquirer/prompts": "7.3.2",
         "@listr2/prompt-adapter-inquirer": "2.0.18",
-        "@schematics/angular": "19.2.8",
+        "@schematics/angular": "19.2.13",
         "@yarnpkg/lockfile": "1.1.0",
         "ini": "5.0.0",
         "jsonc-parser": "3.3.1",
@@ -669,6 +697,60 @@
         "yarn": ">= 1.13.0"
       }
     },
+    "node_modules/@angular/cli/node_modules/@angular-devkit/architect": {
+      "version": "0.1902.13",
+      "resolved": "https://registry.npmmirror.com/@angular-devkit/architect/-/architect-0.1902.13.tgz",
+      "integrity": "sha512-ZMj+PjK22Ph2U8usG6L7LqEfvWlbaOvmiWXSrEt9YiC9QJt6rsumCkOgUIsmHQtucm/lK+9CMtyYdwH2fYycjg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@angular-devkit/core": "19.2.13",
+        "rxjs": "7.8.1"
+      },
+      "engines": {
+        "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      }
+    },
+    "node_modules/@angular/cli/node_modules/@angular-devkit/core": {
+      "version": "19.2.13",
+      "resolved": "https://registry.npmmirror.com/@angular-devkit/core/-/core-19.2.13.tgz",
+      "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ajv": "8.17.1",
+        "ajv-formats": "3.0.1",
+        "jsonc-parser": "3.3.1",
+        "picomatch": "4.0.2",
+        "rxjs": "7.8.1",
+        "source-map": "0.7.4"
+      },
+      "engines": {
+        "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      },
+      "peerDependencies": {
+        "chokidar": "^4.0.0"
+      },
+      "peerDependenciesMeta": {
+        "chokidar": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@angular/cli/node_modules/rxjs": {
+      "version": "7.8.1",
+      "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.1.tgz",
+      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/@angular/common": {
       "version": "19.2.7",
       "resolved": "https://registry.npmmirror.com/@angular/common/-/common-19.2.7.tgz",
@@ -4524,14 +4606,14 @@
       "license": "MIT"
     },
     "node_modules/@schematics/angular": {
-      "version": "19.2.8",
-      "resolved": "https://registry.npmmirror.com/@schematics/angular/-/angular-19.2.8.tgz",
-      "integrity": "sha512-oE/RzC9a0kS6+T72zX08Qkh42tbHlPZxFx1lm3saIzU9mifxlQRT9Od4PK+yksDBvxvtr+TcM2KVOqxCujpHXg==",
+      "version": "19.2.13",
+      "resolved": "https://registry.npmmirror.com/@schematics/angular/-/angular-19.2.13.tgz",
+      "integrity": "sha512-SOpK4AwH0isXo7Y2SkgXLyGLMw4GxWPAun6sCLiprmop4KlqKGGALn4xIW0yjq0s5GS0Vx0FFjz8bBfPkgnawA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@angular-devkit/core": "19.2.8",
-        "@angular-devkit/schematics": "19.2.8",
+        "@angular-devkit/core": "19.2.13",
+        "@angular-devkit/schematics": "19.2.13",
         "jsonc-parser": "3.3.1"
       },
       "engines": {
@@ -4540,6 +4622,44 @@
         "yarn": ">= 1.13.0"
       }
     },
+    "node_modules/@schematics/angular/node_modules/@angular-devkit/core": {
+      "version": "19.2.13",
+      "resolved": "https://registry.npmmirror.com/@angular-devkit/core/-/core-19.2.13.tgz",
+      "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ajv": "8.17.1",
+        "ajv-formats": "3.0.1",
+        "jsonc-parser": "3.3.1",
+        "picomatch": "4.0.2",
+        "rxjs": "7.8.1",
+        "source-map": "0.7.4"
+      },
+      "engines": {
+        "node": "^18.19.1 || ^20.11.1 || >=22.0.0",
+        "npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
+        "yarn": ">= 1.13.0"
+      },
+      "peerDependencies": {
+        "chokidar": "^4.0.0"
+      },
+      "peerDependenciesMeta": {
+        "chokidar": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@schematics/angular/node_modules/rxjs": {
+      "version": "7.8.1",
+      "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.1.tgz",
+      "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/@sigstore/bundle": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/@sigstore/bundle/-/bundle-3.1.0.tgz",

+ 1 - 1
app/myapp/package.json

@@ -39,7 +39,7 @@
     "@angular-eslint/eslint-plugin-template": "^19.0.0",
     "@angular-eslint/schematics": "^19.0.0",
     "@angular-eslint/template-parser": "^19.0.0",
-    "@angular/cli": "^19.0.0",
+    "@angular/cli": "19.2.13",
     "@angular/compiler-cli": "^19.0.0",
     "@angular/language-service": "^19.0.0",
     "@capacitor/cli": "7.2.0",

+ 1 - 8
app/myapp/src/app/tab1/AIwenbenbuquan/page-training-plan/page-training-plan.component.html

@@ -65,14 +65,7 @@
       </ion-card-title>
     </ion-card-header>
     <ion-card-content>
-      <div class="ai-content">
-        @if(!planGenerated){
-          <div [innerHTML]="aiContent"></div>
-        }
-        @if(planGenerated){
-          <fm-markdown-preview class="content-style" [content]="aiContent"></fm-markdown-preview>
-        }
-      </div> <!-- 删除了 keepHtml 管道 -->
+      <div class="ai-content" [innerHTML]="aiContent"></div> <!-- 删除了 keepHtml 管道 -->
     </ion-card-content>
   </ion-card>
 

+ 2 - 2
app/myapp/src/app/tab1/tab1.page.scss

@@ -25,7 +25,7 @@ body {
     top: 0;
     width: 100%;
     height: 50px;
-    background: rgb(255, 213, 5);
+    background: rgb(5, 13, 255);
     display: flex;
     justify-content: center;
     align-items: center;
@@ -35,7 +35,7 @@ body {
     backdrop-filter: blur(5px);
     border-bottom: 2px solid var(--gold);
     font-weight: bold;
-    text-shadow: 0 2px 4px rgba(198, 160, 160, 0.5);
+    text-shadow: 0 2px 4px rgba(160, 162, 198, 0.5);
 }
 
 .main-content {

+ 19 - 0
app/myapp/src/app/tabs/tabs-routing.module.ts

@@ -2,6 +2,10 @@ import { NgModule } from '@angular/core';
 import { RouterModule, Routes } from '@angular/router';
 import { TabsPage } from './tabs.page';
 import { PageTrainingPlanComponent } from '../tab1/AIwenbenbuquan/page-training-plan/page-training-plan.component'; // 导入组件
+import { PageSportListComponent } from 'src/demo/sport/page-sport-list/page-sport-list.component';
+import { PageSportDisplayComponent } from 'src/demo/sport/page-sport-display/page-sport-display.component';
+import { PageMineComponent } from 'src/lib/user/page-mine/page-mine.component';
+import { ModalUserEditComponent } from 'src/lib/user/page-mine/modal-user-edit/modal-user-edit.component';
 
 const routes: Routes = [
   {
@@ -20,6 +24,11 @@ const routes: Routes = [
         path: 'tab3',
         loadChildren: () => import('../tab3/tab3.module').then(m => m.Tab3PageModule)
       },
+      {
+        path: 'mine',
+       loadComponent: () => import('../../lib/user/page-mine/page-mine.component').then((m) => m.PageMineComponent)
+      },
+     
       {
         path: '',
         redirectTo: '/tabs/tab1',
@@ -28,7 +37,17 @@ const routes: Routes = [
       {
         path: 'AIwenbenbuquan/training',
         component: PageTrainingPlanComponent // 直接使用组件
+      },
+      {
+        path: 'demo/sport/list',
+        component: PageSportListComponent // 直接使用组件
+      },
+      {
+        path: 'demo/sport/display/:userId',
+        component: PageSportDisplayComponent // 直接使用组件
       }
+      
+      
     ]
   },
   {

+ 6 - 0
app/myapp/src/app/tabs/tabs.page.html

@@ -15,6 +15,12 @@
       <ion-icon aria-hidden="true" name="medal-outline"></ion-icon>
       <ion-label>我的</ion-label>
     </ion-tab-button>
+
+<ion-tab-button tab="mine" href="/tabs/mine">
+      <ion-icon aria-hidden="true" name="person-outline"></ion-icon>
+      <ion-label>我的</ion-label>
+    </ion-tab-button>
+
   </ion-tab-bar>
 
 </ion-tabs>

+ 324 - 0
app/myapp/src/demo/sport/import-sport-data.ts

@@ -0,0 +1,324 @@
+import { CloudObject, CloudUser } from "src/lib/ncloud";
+
+// 健身教练测试数据
+const generateTestUsers = () => {
+    return [
+        {
+            username: '张伟',
+            email: 'zhangwei@example.com',
+            password: 'sporty123',
+            phone: '13800138001',
+            height: 180,
+            weight: 75,
+            birthDate: new Date(1990, 5, 15),
+            gender: 'male',
+            // 教练特有字段
+            certification: 'ACE-CPT', // 认证信息
+            specialization: ['weightlifting', 'HIIT'], // 专长领域
+            yearsOfExperience: 5,
+            hourlyRate: 300, // 每小时收费(元)
+            availableSchedule: {
+                weekdays: ['Monday', 'Wednesday', 'Friday'],
+                timeSlots: ['09:00-12:00', '18:00-21:00']
+            },
+            clients: [], // 客户列表(可存放客户ID)
+            rating: 4.8, // 教练评分
+            introduction: '专业健身教练,擅长力量训练和减脂计划制定',
+            socialMedia: {
+                wechat: 'zhangwei_coach',
+                instagram: 'zhangwei_fitness'
+            },
+            isActive: true // 是否接单中
+        },
+        {
+            username: '李娜',
+            email: 'lina@example.com',
+            password: 'healthy456',
+            phone: '13900139001',
+            height: 165,
+            weight: 55,
+            birthDate: new Date(1995, 8, 22),
+            gender: 'female',
+            // 教练特有字段
+            certification: 'NASM-CPT, RYT-200', // 多个认证
+            specialization: ['yoga', 'pilates', 'rehabilitation'],
+            yearsOfExperience: 3,
+            hourlyRate: 250,
+            availableSchedule: {
+                weekdays: ['Tuesday', 'Thursday', 'Saturday'],
+                timeSlots: ['10:00-12:00', '15:00-19:00']
+            },
+            clients: [],
+            rating: 4.9,
+            introduction: '瑜伽和康复训练专家,帮助客户改善体态和缓解疼痛',
+            socialMedia: {
+                wechat: 'lina_yoga',
+                youtube: 'lina_yoga_channel'
+            },
+            isActive: true
+        },
+        {
+            username: '王刚',
+            email: 'wanggang@example.com',
+            password: 'start789',
+            phone: '13700137001',
+            height: 175,
+            weight: 85,
+            birthDate: new Date(1988, 2, 10),
+            gender: 'male',
+            // 教练特有字段
+            certification: 'NSCA-CSCS', // 体能训练专家认证
+            specialization: ['functional training', 'sports conditioning'],
+            yearsOfExperience: 7,
+            hourlyRate: 400,
+            availableSchedule: {
+                weekdays: ['Monday', 'Wednesday', 'Friday', 'Sunday'],
+                timeSlots: ['07:00-09:00', '17:00-22:00']
+            },
+            clients: [],
+            rating: 4.7,
+            introduction: '前职业运动员,专注于功能性训练和运动表现提升',
+            socialMedia: {
+                wechat: 'wanggang_pt',
+                tiktok: 'wanggang_fitness'
+            },
+            isActive: false // 暂时不接新客户
+        }
+    ];
+};
+
+// 训练计划测试数据(无需修改)
+const generateTestPlans = (userIds: string[]) => {
+    return [
+        {
+            planName: '30天减脂挑战',
+            description: '高强度间歇训练结合有氧运动',
+            difficulty: 'hard',
+            durationWeeks: 4,
+            sessionsPerWeek: 5,
+            targetMuscles: ['core', 'legs', 'arms'],
+            requiredEquipment: ['dumbbell', 'yoga mat'],
+            creator: userIds[0], // 张伟创建的
+            isPublic: true,
+            coverImage: null,
+            tags: ['减脂', 'HIIT'],
+            rating: 4.5,
+            estimatedCalories: 1200
+        },
+        {
+            planName: '初学者瑜伽入门',
+            description: '适合初学者的每日瑜伽练习',
+            difficulty: 'easy',
+            durationWeeks: 8,
+            sessionsPerWeek: 3,
+            targetMuscles: ['flexibility', 'balance'],
+            requiredEquipment: ['yoga mat'],
+            creator: userIds[1], // 李娜创建的
+            isPublic: true,
+            coverImage: null,
+            tags: ['瑜伽', '初学者'],
+            rating: 4.2,
+            estimatedCalories: 800
+        },
+        {
+            planName: '办公室放松训练',
+            description: '适合上班族的短时放松训练',
+            difficulty: 'medium',
+            durationWeeks: 2,
+            sessionsPerWeek: 7,
+            targetMuscles: ['neck', 'back', 'shoulders'],
+            requiredEquipment: [],
+            creator: userIds[2], // 王刚创建的
+            isPublic: false,
+            coverImage: null,
+            tags: ['办公室', '放松'],
+            rating: 3.8,
+            estimatedCalories: 300
+        }
+    ];
+};
+
+// 训练任务测试数据(无需修改)
+const generateTestTasks = (userIds: string[], planIds: string[]) => {
+    return [
+        // 减脂计划的任务
+        {
+            taskName: '周一高强度间歇训练',
+            description: '20分钟HIIT训练',
+            exerciseType: 'cardio',
+            durationMinutes: 30,
+            caloriesEstimate: 350,
+            instructions: '热身5分钟\n30秒高强度运动,30秒休息,重复8组\n拉伸放松5分钟',
+            restInterval: 30,
+            sets: 8,
+            reps: null,
+            targetWeight: null,
+            distance: null,
+            isCompleted: false,
+            plan: planIds[0],
+            assignedUser: userIds[0],
+            dueDate: new Date(Date.now() + 86400000) // 明天
+        },
+        {
+            taskName: '周三核心力量训练',
+            description: '腹肌和背部训练',
+            exerciseType: 'strength',
+            durationMinutes: 45,
+            caloriesEstimate: 280,
+            instructions: '平板支撑3组,每组1分钟\n仰卧起坐4组,每组15次\n背部伸展3组,每组12次',
+            restInterval: 45,
+            sets: 3,
+            reps: 15,
+            targetWeight: null,
+            distance: null,
+            isCompleted: true,
+            completionDate: new Date(Date.now() - 86400000), // 昨天
+            plan: planIds[0],
+            assignedUser: userIds[0],
+            dueDate: new Date(Date.now() - 86400000)
+        },
+        // 瑜伽计划的任务
+        {
+            taskName: '基础瑜伽练习',
+            description: '基础体式和呼吸练习',
+            exerciseType: 'flexibility',
+            durationMinutes: 60,
+            caloriesEstimate: 200,
+            instructions: '冥想5分钟\n太阳致敬式3轮\n基础体式练习30分钟\n放松术10分钟',
+            restInterval: null,
+            sets: null,
+            reps: null,
+            targetWeight: null,
+            distance: null,
+            isCompleted: false,
+            plan: planIds[1],
+            assignedUser: userIds[1],
+            dueDate: new Date(Date.now() + 2 * 86400000) // 后天
+        },
+        // 办公室放松训练的任务
+        {
+            taskName: '午间肩颈放松',
+            description: '5分钟办公室放松',
+            exerciseType: 'flexibility',
+            durationMinutes: 5,
+            caloriesEstimate: 50,
+            instructions: '颈部左右拉伸各15秒\n肩部绕环前后各10次\n手臂伸展各15秒',
+            restInterval: null,
+            sets: null,
+            reps: null,
+            targetWeight: null,
+            distance: null,
+            isCompleted: false,
+            plan: planIds[2],
+            assignedUser: userIds[2],
+            dueDate: new Date(Date.now() + 86400000) // 明天
+        }
+    ];
+};
+
+// 数据导入函数(需要修改用户对象字段)
+export async function importTestData() {
+    try {
+        // 1. 导入教练数据到VipUser表
+        const testUsers = generateTestUsers();
+        const userIds: string[] = [];
+        
+        for (const userData of testUsers) {
+            const user = new CloudObject("VipUser");
+            user.set({
+                username: userData.username,
+                email: userData.email,
+                phone: userData.phone,
+                password: userData.password,
+                height: userData.height,
+                weight: userData.weight,
+                birthDate: userData.birthDate,
+                gender: userData.gender,
+                // 教练特有字段
+                certification: userData.certification,
+                specialization: userData.specialization,
+                yearsOfExperience: userData.yearsOfExperience, // 确保这行存在
+                hourlyRate: userData.hourlyRate, // 确保这行存在
+                availableSchedule: userData.availableSchedule,
+                clients: userData.clients,
+                rating: userData.rating,
+                introduction: userData.introduction,
+                socialMedia: userData.socialMedia,
+                isActive: userData.isActive
+            });
+            
+            await user.save();
+            
+            if (user.id) {
+                userIds.push(user.id);
+                console.log(`健身教练 ${userData.username} 创建成功,ID: ${user.id}`);
+                // 调试输出
+                console.log('教练数据:', {
+                    yearsOfExperience: user.get('yearsOfExperience'),
+                    hourlyRate: user.get('hourlyRate')
+                });
+            }
+        }
+        
+        // 2. 导入训练计划(无需修改)
+        const testPlans = generateTestPlans(userIds);
+        const planIds: string[] = [];
+        
+        for (const planData of testPlans) {
+            const plan = new CloudObject('ExecisePlan');
+            plan.set({
+                ...planData,
+                creator: {
+                    __type: "Pointer",
+                    className: "VipUser",
+                    objectId: planData.creator
+                }
+            });
+            await plan.save();
+            
+            if (plan.id) {
+                planIds.push(plan.id);
+                console.log(`训练计划 ${planData.planName} 创建成功,ID: ${plan.id}`);
+            }
+        }
+        
+        // 3. 导入训练任务(无需修改)
+        const testTasks = generateTestTasks(userIds, planIds);
+        
+        for (const taskData of testTasks) {
+            const task = new CloudObject('ExeciseTask');
+            task.set({
+                ...taskData,
+                plan: {
+                    __type: "Pointer",
+                    className: "ExecisePlan",
+                    objectId: taskData.plan
+                },
+                assignedUser: {
+                    __type: "Pointer",
+                    className: "VipUser",
+                    objectId: taskData.assignedUser
+                },
+                stepByStep: taskData.instructions?.split('\n') || []
+            });
+            await task.save();
+            
+            if (task.id) {
+                console.log(`训练任务 ${taskData.taskName} 创建成功,ID: ${task.id}`);
+            }
+        }
+        
+        console.log('所有测试数据导入完成!');
+        return {
+            userIds,
+            planIds,
+            success: true
+        };
+    } catch (error) {
+        console.error('导入测试数据时出错:', error);
+        return {
+            success: false,
+            error
+        };
+    }
+}

+ 208 - 0
app/myapp/src/demo/sport/page-sport-display/page-sport-display.component.html

@@ -0,0 +1,208 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-buttons slot="start">
+      <ion-button text="返回" (click)="back()">返回</ion-button>
+    </ion-buttons>
+    <ion-title>教练详情</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding" *ngIf="user">
+  <!-- 教练基本信息卡片 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title class="ion-text-center">
+        <ion-avatar class="profile-avatar">
+          <img [src]="'https://robohash.org/' + user.get('username')" />
+        </ion-avatar>
+        <h1>{{user.get('username')}}</h1>
+      </ion-card-title>
+    </ion-card-header>
+    
+    <ion-card-content>
+      <ion-grid>
+        <ion-row>
+          <ion-col size="6">
+            <ion-item lines="none">
+              <ion-icon slot="start" name="person-circle"></ion-icon>
+              <ion-label>
+                <h3>性别</h3>
+                <p>{{user.get('gender') === 'male' ? '男' : '女'}}</p>
+              </ion-label>
+            </ion-item>
+          </ion-col>
+          <ion-col size="6">
+            <ion-item lines="none">
+              <ion-icon slot="start" name="calendar"></ion-icon>
+              <ion-label>
+                <h3>年龄</h3>
+                <p>{{calculateAge(user.get('birthDate'))}}岁</p>
+              </ion-label>
+            </ion-item>
+          </ion-col>
+        </ion-row>
+        
+        <ion-row>
+          <ion-col size="6">
+            <ion-item lines="none">
+              <ion-icon slot="start" name="body"></ion-icon>
+              <ion-label>
+                <h3>身高</h3>
+                <p>{{user.get('height')}} cm</p>
+              </ion-label>
+            </ion-item>
+          </ion-col>
+          <ion-col size="6">
+            <ion-item lines="none">
+              <ion-icon slot="start" name="speedometer"></ion-icon>
+              <ion-label>
+                <h3>体重</h3>
+                <p>{{user.get('weight')}} kg</p>
+              </ion-label>
+            </ion-item>
+          </ion-col>
+        </ion-row>
+        
+        <ion-row>
+          <ion-col size="6">
+            <ion-item lines="none">
+              <ion-icon slot="start" name="fitness"></ion-icon>
+              <ion-label>
+                <h3>认证信息</h3>
+                <p>{{user.get('certification')}}</p>
+              </ion-label>
+            </ion-item>
+          </ion-col>
+          <ion-col size="6">
+            <ion-item lines="none">
+              <ion-icon slot="start" name="star"></ion-icon>
+              <ion-label>
+                <h3>教练评分</h3>
+                <p>{{user.get('rating')}} / 5</p>
+              </ion-label>
+            </ion-item>
+          </ion-col>
+        </ion-row>
+      </ion-grid>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 联系信息卡片 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>
+        <ion-icon name="mail" slot="start"></ion-icon>
+        联系信息
+      </ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list lines="none">
+        <ion-item>
+          <ion-icon name="mail-outline" slot="start"></ion-icon>
+          <ion-label>{{user.get('email')}}</ion-label>
+        </ion-item>
+        <ion-item>
+          <ion-icon name="call-outline" slot="start"></ion-icon>
+          <ion-label>{{user.get('phone')}}</ion-label>
+        </ion-item>
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 教练专长和经验卡片 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>
+        <ion-icon name="barbell" slot="start"></ion-icon>
+        教练专长和经验
+      </ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-chip *ngFor="let specialization of user.get('specialization')" color="primary">
+        <ion-label>{{specialization}}</ion-label>
+      </ion-chip>
+      
+      <ion-item lines="none" class="ion-margin-top">
+        <ion-icon name="time" slot="start"></ion-icon>
+        <ion-label>
+          <h3>经验年数</h3>
+          <p>{{user.get('yearsOfExperience')}}年</p>
+        </ion-label>
+      </ion-item>
+      
+      <ion-item lines="none" class="ion-margin-top">
+        <ion-icon name="price-tag" slot="start"></ion-icon>
+        <ion-label>
+          <h3>每小时收费</h3>
+          <p>{{user.get('hourlyRate')}}元</p>
+        </ion-label>
+      </ion-item>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 社交媒体信息卡片 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>
+        <ion-icon name="logo-social" slot="start"></ion-icon>
+        社交媒体
+      </ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list lines="none">
+        <ion-item *ngIf="user.get('socialMedia').wechat">
+          <ion-icon name="logo-wechat" slot="start"></ion-icon>
+          <ion-label>{{user.get('socialMedia').wechat}}</ion-label>
+        </ion-item>
+        <ion-item *ngIf="user.get('socialMedia').instagram">
+          <ion-icon name="logo-instagram" slot="start"></ion-icon>
+          <ion-label>{{user.get('socialMedia').instagram}}</ion-label>
+        </ion-item>
+        <ion-item *ngIf="user.get('socialMedia').youtube">
+          <ion-icon name="logo-youtube" slot="start"></ion-icon>
+          <ion-label>{{user.get('socialMedia').youtube}}</ion-label>
+        </ion-item>
+        <ion-item *ngIf="user.get('socialMedia').tiktok">
+          <ion-icon name="logo-tiktok" slot="start"></ion-icon>
+          <ion-label>{{user.get('socialMedia').tiktok}}</ion-label>
+        </ion-item>
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 教练介绍卡片 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>
+        <ion-icon name="information-circle" slot="start"></ion-icon>
+        教练介绍
+      </ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <p>{{user.get('introduction')}}</p>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 接单状态卡片 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>
+        <ion-icon name="checkmark-done-circle" slot="start"></ion-icon>
+        接单状态
+      </ion-card-title>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-item lines="none">
+        <ion-icon name="checkmark-done-circle-outline" slot="start"></ion-icon>
+        <ion-label>
+          <h3>是否接单</h3>
+          <p>
+            <ion-badge [color]="user.get('isActive') ? 'success' : 'warning'">
+              {{user.get('isActive') ? '正在接单' : '暂不接单'}}
+            </ion-badge>
+          </p>
+        </ion-label>
+      </ion-item>
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 63 - 0
app/myapp/src/demo/sport/page-sport-display/page-sport-display.component.scss

@@ -0,0 +1,63 @@
+ion-content {
+  --background: #f5f5f5;
+}
+
+ion-card {
+  margin-bottom: 20px;
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  
+  ion-card-header {
+    ion-card-title {
+      display: flex;
+      align-items: center;
+      
+      ion-icon {
+        margin-right: 8px;
+      }
+    }
+  }
+}
+
+.profile-avatar {
+  width: 80px;
+  height: 80px;
+  margin: 0 auto 10px;
+  display: block;
+}
+
+ion-item {
+  --padding-start: 0;
+  --inner-padding-end: 0;
+  
+  h3 {
+    font-weight: 500;
+    font-size: 0.9rem;
+    color: var(--ion-color-medium);
+  }
+  
+  p {
+    font-size: 1rem;
+    color: var(--ion-color-dark);
+  }
+}
+
+ion-chip {
+  margin: 4px;
+}
+
+ion-badge {
+  font-weight: normal;
+}
+
+ion-grid {
+  padding: 0;
+}
+
+ion-row {
+  padding: 0;
+}
+
+ion-col {
+  padding: 0;
+}

+ 22 - 0
app/myapp/src/demo/sport/page-sport-display/page-sport-display.component.spec.ts

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

+ 72 - 0
app/myapp/src/demo/sport/page-sport-display/page-sport-display.component.ts

@@ -0,0 +1,72 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { CommonModule } from '@angular/common';
+import { IonicModule, NavController } from '@ionic/angular';
+
+@Component({
+  selector: 'app-page-sport-display',
+  templateUrl: './page-sport-display.component.html',
+  styleUrls: ['./page-sport-display.component.scss'],
+  standalone: true,
+  imports: [CommonModule, IonicModule]
+})
+
+export class PageSportDisplayComponent implements OnInit {
+
+  back(){
+    this.navCtrl.back();
+  }
+  constructor(private route: ActivatedRoute,
+    private navCtrl: NavController
+  ) {
+    this.route.params.subscribe(params => {
+      console.log(params);
+      this.loadUser(params['userId']);
+    });
+  }
+
+  user: CloudObject | undefined | null;
+
+  async loadUser(userId: string) {
+    let query = new CloudQuery("VipUser");
+    this.user = await query.get(userId);
+  }
+
+  ngOnInit() {}
+
+  // 计算年龄
+  calculateAge(birthDate: Date): number {
+    if (!birthDate) return 0;
+    const today = new Date();
+    const birth = new Date(birthDate);
+    let age = today.getFullYear() - birth.getFullYear();
+    const monthDiff = today.getMonth() - birth.getMonth();
+    
+    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
+      age--;
+    }
+    
+    return age;
+  }
+
+  // 格式化日期
+  formatDate(date: Date): string {
+    if (!date) return '无';
+    return new Date(date).toLocaleDateString('zh-CN', {
+      year: 'numeric',
+      month: 'long',
+      day: 'numeric'
+    });
+  }
+
+  // 获取健身水平中文
+  getFitnessLevel(level: string): string {
+    const levels: {[key: string]: string} = {
+      'beginner': '初级',
+      'intermediate': '中级',
+      'advanced': '高级'
+    };
+    return levels[level] || level;
+  }
+}

+ 115 - 0
app/myapp/src/demo/sport/page-sport-list/page-sport-list.component.html

@@ -0,0 +1,115 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-title>运动数据管理</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <ion-button expand="block" color="secondary" (click)="importData()">
+    <ion-icon slot="start" name="cloud-upload"></ion-icon>
+    导入运动数据
+  </ion-button>
+
+  <!-- 教练列表 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>教练团队</ion-card-title>
+      <ion-card-subtitle>专业健身教练信息</ion-card-subtitle>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list lines="full">
+        @for(user of userList; track user){
+          <ion-item button (click)="goUser(user)" detail>
+            <ion-avatar slot="start">
+              <img [src]="'https://robohash.org/' + user.get('username')" />
+            </ion-avatar>
+            <ion-label>
+              <h2>{{user.get('username')}}</h2>
+              <p>{{user.get('certification')}}</p>
+              <p>
+                @for(spec of user.get('specialization'); track spec){
+                  <ion-chip color="primary">
+                    <ion-label>{{spec}}</ion-label>
+                  </ion-chip>
+                }
+              </p>
+              <p>
+                <ion-icon name="star" color="warning"></ion-icon>
+                {{user.get('rating')}} | 
+                {{user.get('yearsOfExperience')}}年经验 | 
+                {{user.get('hourlyRate')}}元/小时
+              </p>
+            </ion-label>
+            <ion-badge slot="end" [color]="user.get('isActive') ? 'success' : 'medium'">
+              {{user.get('isActive') ? '可预约' : '暂不可约'}}
+            </ion-badge>
+          </ion-item>
+        }
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 训练计划列表 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>训练计划</ion-card-title>
+      <ion-card-subtitle>教练制定的专业计划</ion-card-subtitle>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list lines="full">
+        @for(plan of planList; track plan){
+          <ion-item>
+            <ion-label>
+              <h2>{{plan.get('planName')}}</h2>
+              <p>{{plan.get('description')}}</p>
+              <p>
+                <ion-badge color="medium">{{plan.get('difficulty')}}</ion-badge>
+                <ion-badge color="tertiary">{{plan.get('durationWeeks')}}周</ion-badge>
+                <ion-badge color="success">{{plan.get('sessionsPerWeek')}}次/周</ion-badge>
+              </p>
+              <p>
+                @for(tag of plan.get('tags'); track tag){
+                  <ion-chip>
+                    <ion-label>{{tag}}</ion-label>
+                  </ion-chip>
+                }
+              </p>
+            </ion-label>
+            <ion-badge slot="end" color="primary">
+              {{plan.get('rating')}}★
+            </ion-badge>
+          </ion-item>
+        }
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+
+  <!-- 训练任务列表 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-card-title>训练任务</ion-card-title>
+      <ion-card-subtitle>详细训练内容</ion-card-subtitle>
+    </ion-card-header>
+    <ion-card-content>
+      <ion-list lines="full">
+        @for(task of taskList; track task){
+          <ion-item>
+            <ion-label>
+              <h2>{{task.get('taskName')}}</h2>
+              <p>{{task.get('description')}}</p>
+              <p>
+                <ion-badge [color]="task.get('isCompleted') ? 'success' : 'warning'">
+                  {{task.get('isCompleted') ? '已完成' : '待完成'}}
+                </ion-badge>
+                <ion-badge color="danger">{{task.get('durationMinutes')}}分钟</ion-badge>
+                <ion-badge color="secondary">{{task.get('caloriesEstimate')}}卡路里</ion-badge>
+                <ion-badge color="tertiary">{{task.get('exerciseType')}}</ion-badge>
+              </p>
+              <p class="instructions">{{task.get('instructions')}}</p>
+            </ion-label>
+          </ion-item>
+        }
+      </ion-list>
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 55 - 0
app/myapp/src/demo/sport/page-sport-list/page-sport-list.component.scss

@@ -0,0 +1,55 @@
+ion-content {
+  --background: #f5f5f5;
+}
+
+ion-card {
+  margin-bottom: 20px;
+  border-radius: 12px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+ion-item {
+  --padding-start: 10px;
+  --padding-end: 10px;
+  --inner-padding-end: 10px;
+  --border-radius: 8px;
+  margin-bottom: 8px;
+  
+  h2 {
+    font-weight: 600;
+    margin-bottom: 5px;
+  }
+  
+  p {
+    color: var(--ion-color-medium);
+    font-size: 0.9em;
+    margin-top: 5px;
+    margin-bottom: 5px;
+  }
+}
+
+.instructions {
+  white-space: pre-line;
+  font-size: 0.8em !important;
+  color: var(--ion-color-dark) !important;
+  background: #f9f9f9;
+  padding: 8px;
+  border-radius: 4px;
+  margin-top: 8px !important;
+}
+
+ion-badge {
+  margin-right: 5px;
+}
+
+ion-chip {
+  height: 24px;
+  font-size: 0.7em;
+  margin-right: 5px;
+  margin-top: 5px;
+}
+
+ion-avatar {
+  width: 50px;
+  height: 50px;
+}

+ 22 - 0
app/myapp/src/demo/sport/page-sport-list/page-sport-list.component.spec.ts

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

+ 48 - 0
app/myapp/src/demo/sport/page-sport-list/page-sport-list.component.ts

@@ -0,0 +1,48 @@
+import { Component, OnInit } from '@angular/core';
+import { importTestData } from '../import-sport-data';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { NavController } from '@ionic/angular/standalone';
+import { IonicModule } from '@ionic/angular';
+@Component({
+  selector: 'app-page-sport-list',
+  templateUrl: './page-sport-list.component.html',
+  styleUrls: ['./page-sport-list.component.scss'],
+  standalone: true,
+  imports: [IonicModule]
+})
+export class PageSportListComponent  implements OnInit {
+
+ constructor(private navCtrl: NavController) { 
+    
+  }
+
+  ngOnInit() {
+    this.loadUserData()
+  }
+  
+  userList: Array<CloudObject> = []
+  planList: Array<CloudObject> = []
+  taskList: Array<CloudObject> = []
+
+  async loadUserData() {
+    let query1 = new CloudQuery("VipUser")
+    this.userList = await query1.find()
+    console.log(this.userList)
+
+    let query2 = new CloudQuery("ExecisePlan")
+    this.planList = await query2.find()
+    console.log(this.planList)
+
+    let query3 = new CloudQuery("ExeciseTask")
+    this.taskList = await query3.find()
+    console.log(this.taskList)
+  }
+
+  goUser(user: CloudObject) {
+    this.navCtrl.navigateForward(['tabs','demo','sport','display',user?.id])
+  }
+
+  importData() {
+    importTestData()
+  }
+}

+ 431 - 0
app/myapp/src/lib/ncloud.ts

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

+ 66 - 0
app/myapp/src/lib/user/page-mine/modal-user-edit/modal-user-edit.component.html

@@ -0,0 +1,66 @@
+<ion-header>
+  <ion-toolbar color="primary">
+    <ion-title>编辑资料</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="cancel()">
+        <ion-icon name="close-outline" slot="icon-only"></ion-icon>
+      </ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="ion-padding">
+  <ion-list>
+    <ion-item>
+      <ion-icon name="person-outline" slot="start"></ion-icon>
+      <ion-input [value]="userData['username']" (ionChange)="userDataChange('username',$event)" label="用户名"
+        label-placement="floating" placeholder="请输入用户名">
+      </ion-input>
+    </ion-item>
+
+    <ion-item>
+      <ion-icon name="id-card-outline" slot="start"></ion-icon>
+      <ion-input [value]="userData['realname']" (ionChange)="userDataChange('realname',$event)" label="真实姓名"
+        label-placement="floating" placeholder="请输入真实姓名">
+      </ion-input>
+    </ion-item>
+
+    <ion-item>
+      <ion-icon name="calendar-outline" slot="start"></ion-icon>
+      <ion-input type="number" [value]="userData['age']" (ionChange)="userDataChange('age',$event)" label="年龄"
+        label-placement="floating" placeholder="请输入年龄">
+      </ion-input>
+    </ion-item>
+
+    <ion-item>
+      <ion-icon name="transgender-outline" slot="start"></ion-icon>
+      <ion-label>性别</ion-label>
+      <ion-segment [value]="userData['gender']" (ionChange)="userDataChange('gender', $event)">
+        <ion-segment-button value="男">
+          <ion-label>男</ion-label>
+        </ion-segment-button>
+        <ion-segment-button value="女">
+          <ion-label>女</ion-label>
+        </ion-segment-button>
+      </ion-segment>
+    </ion-item>
+
+    <ion-item>
+      <ion-icon name="image-outline" slot="start"></ion-icon>
+      <ion-input [value]="userData['avatar']" (ionChange)="userDataChange('avatar',$event)" label="头像URL"
+        label-placement="floating" placeholder="请输入头像URL地址">
+      </ion-input>
+    </ion-item>
+  </ion-list>
+
+  <div class="action-buttons">
+    <ion-button expand="block" (click)="save()" color="primary" shape="round">
+      <ion-icon name="save-outline" slot="start"></ion-icon>
+      保存
+    </ion-button>
+    <ion-button expand="block" (click)="cancel()" color="medium" fill="outline" shape="round">
+      <ion-icon name="close-outline" slot="start"></ion-icon>
+      取消
+    </ion-button>
+  </div>
+</ion-content>

+ 27 - 0
app/myapp/src/lib/user/page-mine/modal-user-edit/modal-user-edit.component.scss

@@ -0,0 +1,27 @@
+/* modal-user-edit.component.scss */
+ion-content {
+    --padding-bottom: 80px;
+}
+
+.action-buttons {
+    margin-top: 32px;
+
+    ion-button {
+        margin-bottom: 16px;
+    }
+}
+
+ion-segment {
+    width: 100%;
+    max-width: 200px;
+    margin-left: auto;
+}
+
+ion-item {
+    --padding-start: 0;
+
+    ion-icon {
+        margin-right: 16px;
+        color: var(--ion-color-medium);
+    }
+}

+ 22 - 0
app/myapp/src/lib/user/page-mine/modal-user-edit/modal-user-edit.component.spec.ts

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

+ 65 - 0
app/myapp/src/lib/user/page-mine/modal-user-edit/modal-user-edit.component.ts

@@ -0,0 +1,65 @@
+import { Component, OnInit } from '@angular/core';
+import {
+  IonHeader, IonToolbar, IonTitle, IonContent,
+  IonButton, IonInput, IonItem, IonList, IonIcon,
+  IonSegment, IonSegmentButton, IonLabel, IonButtons
+} from '@ionic/angular/standalone';
+import { ModalController } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+import { addIcons } from 'ionicons';
+import {
+  closeOutline, saveOutline, personOutline,
+  idCardOutline, calendarOutline, transgenderOutline,
+  imageOutline
+} from 'ionicons/icons';
+
+@Component({
+  selector: 'app-modal-user-edit',
+  templateUrl: './modal-user-edit.component.html',
+  styleUrls: ['./modal-user-edit.component.scss'],
+  standalone: true,
+  imports: [
+    IonHeader, IonToolbar, IonTitle, IonContent,
+    IonButton, IonInput, IonItem, IonList, IonIcon,
+    IonSegment, IonSegmentButton, IonLabel, IonButtons
+  ],
+})
+export class ModalUserEditComponent implements OnInit {
+  currentUser: CloudUser | undefined;
+  userData: any = {};
+
+  constructor(private modalCtrl: ModalController) {
+    this.currentUser = new CloudUser();
+    this.userData = { ...this.currentUser.data };
+    addIcons({
+      closeOutline, saveOutline, personOutline,
+      idCardOutline, calendarOutline, transgenderOutline,
+      imageOutline
+    });
+  }
+
+  userDataChange(key: string, ev: any) {
+    let value = ev?.detail?.value;
+    if (value) {
+      this.userData[key] = value;
+    }
+  }
+
+  ngOnInit() { }
+
+  async save() {
+    Object.keys(this.userData).forEach(key => {
+      if (key == "age") {
+        this.userData[key] = Number(this.userData[key]);
+      }
+    });
+
+    this.currentUser?.set(this.userData);
+    await this.currentUser?.save();
+    this.modalCtrl.dismiss(this.currentUser, "confirm");
+  }
+
+  cancel() {
+    this.modalCtrl.dismiss(null, "cancel");
+  }
+}

+ 36 - 0
app/myapp/src/lib/user/page-mine/modal-user-login/modal-user-login.component.html

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

+ 0 - 0
app/myapp/src/lib/user/page-mine/modal-user-login/modal-user-login.component.scss


+ 22 - 0
app/myapp/src/lib/user/page-mine/modal-user-login/modal-user-login.component.spec.ts

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

+ 92 - 0
app/myapp/src/lib/user/page-mine/modal-user-login/modal-user-login.component.ts

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

+ 62 - 0
app/myapp/src/lib/user/page-mine/page-mine.component.html

@@ -0,0 +1,62 @@
+<ion-content [fullscreen]="true" class="ion-padding">
+  <ion-header [translucent]="true">
+    <ion-toolbar color="primary">
+      <ion-title>我的资料</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+  <div class="profile-section">
+    @if(currentUser?.id){
+    <!-- 已登录状态 -->
+    <ion-card>
+      <ion-card-header>
+        <ion-avatar class="profile-avatar">
+          <img [src]="currentUser?.get('avatar') || 'assets/icon/avatar-default.png'" alt="用户头像" />
+        </ion-avatar>
+        <ion-card-title class="ion-text-center">
+          {{currentUser?.get("username")}}
+        </ion-card-title>
+        <ion-card-subtitle class="ion-text-center">
+          {{currentUser?.get('realname') || '未设置姓名'}}
+        </ion-card-subtitle>
+      </ion-card-header>
+
+      <ion-card-content>
+        <ion-list lines="none">
+          <ion-item>
+            <ion-icon name="person-outline" slot="start"></ion-icon>
+            <ion-label>性别</ion-label>
+            <ion-note slot="end">{{currentUser?.get('gender') || '未设置'}}</ion-note>
+          </ion-item>
+          <ion-item>
+            <ion-icon name="calendar-outline" slot="start"></ion-icon>
+            <ion-label>年龄</ion-label>
+            <ion-note slot="end">{{currentUser?.get('age') || '未设置'}}</ion-note>
+          </ion-item>
+        </ion-list>
+
+        <ion-button expand="block" (click)="edit()" color="primary">
+          <ion-icon name="create-outline" slot="start"></ion-icon>
+          编辑资料
+        </ion-button>
+        <ion-button expand="block" (click)="logout()" color="danger" fill="outline">
+          <ion-icon name="log-out-outline" slot="start"></ion-icon>
+          登出
+        </ion-button>
+      </ion-card-content>
+    </ion-card>
+    }
+    @if(!currentUser?.id){
+    <!-- 未登录状态 -->
+    <div class="login-prompt">
+      <ion-icon name="person-circle-outline" class="login-icon"></ion-icon>
+      <h2>您还未登录</h2>
+      <p>登录后可以保存您的个人资料</p>
+      <ion-button expand="block" (click)="login()" color="primary">
+        <ion-icon name="log-in-outline" slot="start"></ion-icon>
+        登录用户 cccac 123
+      </ion-button>
+    </div>
+    }
+  </div>
+</ion-content>

+ 51 - 0
app/myapp/src/lib/user/page-mine/page-mine.component.scss

@@ -0,0 +1,51 @@
+.user-profile {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20px;
+  
+  .profile-avatar {
+    width: 100px;
+    height: 100px;
+    margin-bottom: 16px;
+  }
+  
+  h1 {
+    font-weight: bold;
+    margin: 0;
+  }
+  
+  ion-list {
+    width: 100%;
+    background: transparent;
+    
+    ion-item {
+      --background: transparent;
+      --padding-start: 0;
+    }
+  }
+}
+
+.login-prompt {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+  padding: 40px 20px;
+  
+  .prompt-icon {
+    font-size: 80px;
+    color: var(--ion-color-medium);
+    margin-bottom: 20px;
+  }
+  
+  h2 {
+    font-weight: bold;
+    margin-bottom: 8px;
+  }
+  
+  p {
+    color: var(--ion-color-medium);
+    margin-top: 0;
+  }
+}

+ 22 - 0
app/myapp/src/lib/user/page-mine/page-mine.component.spec.ts

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

+ 60 - 0
app/myapp/src/lib/user/page-mine/page-mine.component.ts

@@ -0,0 +1,60 @@
+import { Component, inject } from '@angular/core';
+
+import { ModalController } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+import { ModalUserEditComponent } from './modal-user-edit/modal-user-edit.component';
+import { addIcons } from 'ionicons';
+import {
+  IonContent, IonHeader, IonTitle, IonToolbar, IonCard,
+  IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent,
+  IonButton, IonAvatar, IonList, IonItem, IonLabel, IonNote, IonIcon
+} from '@ionic/angular/standalone';
+import {
+  personOutline, calendarOutline, createOutline,
+  logOutOutline, logInOutline, personCircleOutline
+} from 'ionicons/icons';
+
+@Component({
+  selector: 'app-page-mine',
+  templateUrl: './page-mine.component.html',
+  styleUrls: ['./page-mine.component.scss'],
+  standalone: true,
+  providers: [ModalController],
+  imports: [
+    IonContent, IonHeader, IonTitle, IonToolbar, IonCard,
+    IonCardHeader, IonCardTitle, IonCardSubtitle, IonCardContent,
+    IonButton, IonAvatar, IonList, IonItem, IonLabel, IonNote, IonIcon
+  ]
+})
+export class PageMineComponent {
+   currentUser: CloudUser | undefined;
+
+  constructor(private modalCtrl: ModalController) {
+    this.currentUser = new CloudUser();
+    addIcons({
+      personOutline, calendarOutline, createOutline,
+      logOutOutline, logInOutline, personCircleOutline
+    });
+  }
+
+
+  async edit() {
+    const modal = await this.modalCtrl.create({
+      component: ModalUserEditComponent,
+    });
+    await modal.present();
+  }
+
+  async login() {
+    const user = new CloudUser();
+    const loggedInUser = await user.login("cccac", "123");
+    if (loggedInUser?.id) {
+      this.currentUser = loggedInUser;
+    }
+  }
+
+  logout() {
+    this.currentUser?.logout();
+    this.currentUser = undefined;
+  }
+}