FelixTDT 4 kuukautta sitten
vanhempi
commit
e876170a5b
100 muutettua tiedostoa jossa 2607 lisäystä ja 14 poistoa
  1. 0 0
      Assets/images/jiangping.jpg
  2. BIN
      Assets/images/kobe.jpg
  3. 89 3
      package-lock.json
  4. 2 0
      package.json
  5. 16 0
      src/app/app-routing.module.ts
  6. 4 1
      src/app/app.component.ts
  7. 2 2
      src/app/home/home.page.html
  8. 10 3
      src/app/home/home.page.ts
  9. 2 4
      src/app/tab3/tab3.page.html
  10. 10 1
      src/app/tab3/tab3.page.ts
  11. 2 0
      src/app/tabs/tabs-routing.module.ts
  12. 0 0
      src/assets/images/avatar.png
  13. BIN
      src/assets/images/jiangping.jpg
  14. BIN
      src/assets/images/kobe.jpg
  15. 12 0
      src/modules/aigc/aigc-routing.module.ts
  16. 14 0
      src/modules/aigc/aigc.module.ts
  17. 17 0
      src/modules/aigc/chat/chat-routing.module.ts
  18. 20 0
      src/modules/aigc/chat/chat.module.ts
  19. 24 0
      src/modules/aigc/chat/chat.page.html
  20. 0 0
      src/modules/aigc/chat/chat.page.scss
  21. 17 0
      src/modules/aigc/chat/chat.page.spec.ts
  22. 31 0
      src/modules/aigc/chat/chat.page.ts
  23. 99 0
      src/modules/aigc/chat/class-chat-completion.ts
  24. 30 0
      src/modules/babylon/README.md
  25. 12 0
      src/modules/babylon/pages/case-babylon/README.md
  26. 17 0
      src/modules/babylon/pages/case-babylon/case-babylon-routing.module.ts
  27. 20 0
      src/modules/babylon/pages/case-babylon/case-babylon.module.ts
  28. 6 0
      src/modules/babylon/pages/case-babylon/case-babylon.page.html
  29. 43 0
      src/modules/babylon/pages/case-babylon/case-babylon.page.scss
  30. 17 0
      src/modules/babylon/pages/case-babylon/case-babylon.page.spec.ts
  31. 171 0
      src/modules/babylon/pages/case-babylon/case-babylon.page.ts
  32. 17 0
      src/modules/contact/contact-detail/contact-detail-routing.module.ts
  33. 20 0
      src/modules/contact/contact-detail/contact-detail.module.ts
  34. 13 0
      src/modules/contact/contact-detail/contact-detail.page.html
  35. 0 0
      src/modules/contact/contact-detail/contact-detail.page.scss
  36. 17 0
      src/modules/contact/contact-detail/contact-detail.page.spec.ts
  37. 15 0
      src/modules/contact/contact-detail/contact-detail.page.ts
  38. 17 0
      src/modules/contact/contact-list/contact-list-routing.module.ts
  39. 20 0
      src/modules/contact/contact-list/contact-list.module.ts
  40. 34 0
      src/modules/contact/contact-list/contact-list.page.html
  41. 0 0
      src/modules/contact/contact-list/contact-list.page.scss
  42. 17 0
      src/modules/contact/contact-list/contact-list.page.spec.ts
  43. 42 0
      src/modules/contact/contact-list/contact-list.page.ts
  44. 12 0
      src/modules/contact/contact-routing.module.ts
  45. 14 0
      src/modules/contact/contact.module.ts
  46. 17 0
      src/modules/post-detail/post-detail-routing.module.ts
  47. 20 0
      src/modules/post-detail/post-detail.module.ts
  48. 75 0
      src/modules/post-detail/post-detail.page.html
  49. 14 0
      src/modules/post-detail/post-detail.page.scss
  50. 17 0
      src/modules/post-detail/post-detail.page.spec.ts
  51. 45 0
      src/modules/post-detail/post-detail.page.ts
  52. 32 0
      src/modules/study/case-angular/comps/edit-ratio-star/README.md
  53. 3 0
      src/modules/study/case-angular/comps/edit-ratio-star/edit-ratio-star.component.html
  54. 0 0
      src/modules/study/case-angular/comps/edit-ratio-star/edit-ratio-star.component.scss
  55. 22 0
      src/modules/study/case-angular/comps/edit-ratio-star/edit-ratio-star.component.spec.ts
  56. 61 0
      src/modules/study/case-angular/comps/edit-ratio-star/edit-ratio-star.component.ts
  57. 17 0
      src/modules/study/case-angular/pages/case-edit/case-edit-routing.module.ts
  58. 22 0
      src/modules/study/case-angular/pages/case-edit/case-edit.module.ts
  59. 59 0
      src/modules/study/case-angular/pages/case-edit/case-edit.page.html
  60. 0 0
      src/modules/study/case-angular/pages/case-edit/case-edit.page.scss
  61. 17 0
      src/modules/study/case-angular/pages/case-edit/case-edit.page.spec.ts
  62. 27 0
      src/modules/study/case-angular/pages/case-edit/case-edit.page.ts
  63. 17 0
      src/modules/study/case-angular/pages/tsdatatype/tsdatatype-routing.module.ts
  64. 20 0
      src/modules/study/case-angular/pages/tsdatatype/tsdatatype.module.ts
  65. 83 0
      src/modules/study/case-angular/pages/tsdatatype/tsdatatype.page.html
  66. 0 0
      src/modules/study/case-angular/pages/tsdatatype/tsdatatype.page.scss
  67. 17 0
      src/modules/study/case-angular/pages/tsdatatype/tsdatatype.page.spec.ts
  68. 53 0
      src/modules/study/case-angular/pages/tsdatatype/tsdatatype.page.ts
  69. 17 0
      src/modules/study/case-echarts/case-echarts-routing.module.ts
  70. 20 0
      src/modules/study/case-echarts/case-echarts.module.ts
  71. 22 0
      src/modules/study/case-echarts/case-echarts.page.html
  72. 0 0
      src/modules/study/case-echarts/case-echarts.page.scss
  73. 17 0
      src/modules/study/case-echarts/case-echarts.page.spec.ts
  74. 260 0
      src/modules/study/case-echarts/case-echarts.page.ts
  75. 22 0
      src/modules/study/case-js-module/case-edit/case-edit.module.ts
  76. 163 0
      src/modules/study/case-js-module/case-parse-petmarket/README.md
  77. 17 0
      src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket-routing.module.ts
  78. 20 0
      src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.module.ts
  79. 31 0
      src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.page.html
  80. 0 0
      src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.page.scss
  81. 17 0
      src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.page.spec.ts
  82. 36 0
      src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.page.ts
  83. 27 0
      src/modules/study/case-js-module/case-parse-petmarket/pet.service.ts
  84. 49 0
      src/modules/study/case-js-module/case-parse/README.md
  85. 17 0
      src/modules/study/case-js-module/case-parse/case-parse-routing.module.ts
  86. 20 0
      src/modules/study/case-js-module/case-parse/case-parse.module.ts
  87. 17 0
      src/modules/study/case-js-module/case-parse/case-parse.page.html
  88. 0 0
      src/modules/study/case-js-module/case-parse/case-parse.page.scss
  89. 17 0
      src/modules/study/case-js-module/case-parse/case-parse.page.spec.ts
  90. 28 0
      src/modules/study/case-js-module/case-parse/case-parse.page.ts
  91. 71 0
      src/modules/study/case-js-module/case-pet-detail/README.md
  92. 17 0
      src/modules/study/case-js-module/case-pet-detail/case-pet-detail-routing.module.ts
  93. 20 0
      src/modules/study/case-js-module/case-pet-detail/case-pet-detail.module.ts
  94. 13 0
      src/modules/study/case-js-module/case-pet-detail/case-pet-detail.page.html
  95. 0 0
      src/modules/study/case-js-module/case-pet-detail/case-pet-detail.page.scss
  96. 17 0
      src/modules/study/case-js-module/case-pet-detail/case-pet-detail.page.spec.ts
  97. 29 0
      src/modules/study/case-js-module/case-pet-detail/case-pet-detail.page.ts
  98. 17 0
      src/modules/study/case-js-module/case-pet-edit/case-pet-edit-routing.module.ts
  99. 20 0
      src/modules/study/case-js-module/case-pet-edit/case-pet-edit.module.ts
  100. 23 0
      src/modules/study/case-js-module/case-pet-edit/case-pet-edit.page.html

+ 0 - 0
src/assets/kobe.jpg → Assets/images/jiangping.jpg


BIN
Assets/images/kobe.jpg


+ 89 - 3
package-lock.json

@@ -23,6 +23,7 @@
         "@capacitor/status-bar": "6.0.0",
         "@ionic/angular": "^8.0.0",
         "ionicons": "^7.0.0",
+        "parse": "^5.3.0",
         "rxjs": "~7.8.0",
         "tslib": "^2.3.0",
         "zone.js": "~0.14.2"
@@ -40,6 +41,7 @@
         "@capacitor/cli": "6.1.0",
         "@ionic/angular-toolkit": "^11.0.1",
         "@types/jasmine": "~5.1.0",
+        "@types/parse": "^3.0.9",
         "@typescript-eslint/eslint-plugin": "^6.0.0",
         "@typescript-eslint/parser": "^6.0.0",
         "eslint": "^8.57.0",
@@ -2522,6 +2524,18 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@babel/runtime-corejs3": {
+      "version": "7.24.7",
+      "resolved": "https://registry.npmmirror.com/@babel/runtime-corejs3/-/runtime-corejs3-7.24.7.tgz",
+      "integrity": "sha512-eytSX6JLBY6PVAeQa2bFlDx/7Mmln/gaEpsit5a3WEvjGfiIytEsgAwuIXCPM0xvw0v0cJn3ilq0/TvXrW0kgA==",
+      "dependencies": {
+        "core-js-pure": "^3.30.2",
+        "regenerator-runtime": "^0.14.0"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
     "node_modules/@babel/template": {
       "version": "7.24.7",
       "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.24.7.tgz",
@@ -5287,6 +5301,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/parse": {
+      "version": "3.0.9",
+      "resolved": "https://registry.npmmirror.com/@types/parse/-/parse-3.0.9.tgz",
+      "integrity": "sha512-DGTHygc7krgmNAK8h42giwmAofCd9uv2++RD+zw6OmWI7AEnlTYZwEuWsx22SA2CSMQrZW8P2INHLpQbnQFUng==",
+      "dev": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/qs": {
       "version": "6.9.15",
       "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.15.tgz",
@@ -7369,6 +7392,16 @@
         "url": "https://opencollective.com/core-js"
       }
     },
+    "node_modules/core-js-pure": {
+      "version": "3.37.1",
+      "resolved": "https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.37.1.tgz",
+      "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==",
+      "hasInstallScript": true,
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
     "node_modules/core-util-is": {
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -7513,6 +7546,12 @@
         "node": ">= 8"
       }
     },
+    "node_modules/crypto-js": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
+      "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+      "optional": true
+    },
     "node_modules/css-loader": {
       "version": "7.1.1",
       "resolved": "https://registry.npmmirror.com/css-loader/-/css-loader-7.1.1.tgz",
@@ -10091,6 +10130,11 @@
         "postcss": "^8.1.0"
       }
     },
+    "node_modules/idb-keyval": {
+      "version": "6.2.1",
+      "resolved": "https://registry.npmmirror.com/idb-keyval/-/idb-keyval-6.2.1.tgz",
+      "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg=="
+    },
     "node_modules/ieee754": {
       "version": "1.2.1",
       "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz",
@@ -13382,6 +13426,25 @@
         "node": ">=6"
       }
     },
+    "node_modules/parse": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmmirror.com/parse/-/parse-5.3.0.tgz",
+      "integrity": "sha512-mWBnE6hHJhdvlx5KPQcYgCGRdgqKhPw+5fSC0j7vOfse3Lkh3xtDwOfmDpvv2LXZVBj72G/mgVKMRmbAICRzkQ==",
+      "dependencies": {
+        "@babel/runtime-corejs3": "7.24.7",
+        "idb-keyval": "6.2.1",
+        "react-native-crypto-js": "1.0.0",
+        "uuid": "10.0.0",
+        "ws": "8.17.1",
+        "xmlhttprequest": "1.8.0"
+      },
+      "engines": {
+        "node": "18 || 19 || 20 || 22"
+      },
+      "optionalDependencies": {
+        "crypto-js": "4.2.0"
+      }
+    },
     "node_modules/parse-imports": {
       "version": "2.1.1",
       "resolved": "https://registry.npmmirror.com/parse-imports/-/parse-imports-2.1.1.tgz",
@@ -13434,6 +13497,18 @@
         "node": ">= 0.10"
       }
     },
+    "node_modules/parse/node_modules/uuid": {
+      "version": "10.0.0",
+      "resolved": "https://registry.npmmirror.com/uuid/-/uuid-10.0.0.tgz",
+      "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/parse5": {
       "version": "7.1.2",
       "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.1.2.tgz",
@@ -14075,6 +14150,11 @@
       "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
       "dev": true
     },
+    "node_modules/react-native-crypto-js": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/react-native-crypto-js/-/react-native-crypto-js-1.0.0.tgz",
+      "integrity": "sha512-FNbLuG/HAdapQoybeZSoes1PWdOj0w242gb+e1R0hicf3Gyj/Mf8M9NaED2AnXVOX01b2FXomwUiw1xP1K+8sA=="
+    },
     "node_modules/readable-stream": {
       "version": "3.6.2",
       "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -14140,8 +14220,7 @@
     "node_modules/regenerator-runtime": {
       "version": "0.14.1",
       "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
-      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
-      "dev": true
+      "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
     },
     "node_modules/regenerator-transform": {
       "version": "0.15.2",
@@ -17326,7 +17405,6 @@
       "version": "8.17.1",
       "resolved": "https://registry.npmmirror.com/ws/-/ws-8.17.1.tgz",
       "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
-      "dev": true,
       "engines": {
         "node": ">=10.0.0"
       },
@@ -17374,6 +17452,14 @@
         "node": ">=8.0"
       }
     },
+    "node_modules/xmlhttprequest": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmmirror.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
+      "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",

+ 2 - 0
package.json

@@ -28,6 +28,7 @@
     "@capacitor/status-bar": "6.0.0",
     "@ionic/angular": "^8.0.0",
     "ionicons": "^7.0.0",
+    "parse": "^5.3.0",
     "rxjs": "~7.8.0",
     "tslib": "^2.3.0",
     "zone.js": "~0.14.2"
@@ -45,6 +46,7 @@
     "@capacitor/cli": "6.1.0",
     "@ionic/angular-toolkit": "^11.0.1",
     "@types/jasmine": "~5.1.0",
+    "@types/parse": "^3.0.9",
     "@typescript-eslint/eslint-plugin": "^6.0.0",
     "@typescript-eslint/parser": "^6.0.0",
     "eslint": "^8.57.0",

+ 16 - 0
src/app/app-routing.module.ts

@@ -6,10 +6,26 @@ const routes: Routes = [
     path: '',
     loadChildren: () => import('./tabs/tabs.module').then(m => m.TabsPageModule)
   },
+  {
+    path: 'user',
+    loadChildren: () => import('../modules/user/user.module').then(m => m.UserModule)
+  },
   // {
   //   path: 'home',
   //   loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
   // }
+  {
+    path: 'login',
+    loadChildren: () => import('../modules/user/login/login.module').then(m => m.LoginPageModule)
+  },
+  {
+    path: 'edit-info',
+    loadChildren: () => import('../modules/user/edit-info/edit-info-routing.module').then(m => m.EditInfoPageRoutingModule)
+  },
+  {
+    path: 'post-detail/:id',
+    loadChildren: () => import('../modules/post-detail/post-detail.module').then( m => m.PostDetailPageModule)
+  },
 ];
 @NgModule({
   imports: [

+ 4 - 1
src/app/app.component.ts

@@ -1,5 +1,8 @@
 import { Component } from '@angular/core';
-
+// 引用Parse JS SDK
+import Parse from "parse";
+Parse.initialize("dev"); // 设置applicationId
+Parse.serverURL = "http://web2023.fmode.cn:9999/parse"; // 设置serverURL
 @Component({
   selector: 'app-root',
   templateUrl: 'app.component.html',

+ 2 - 2
src/app/home/home.page.html

@@ -22,10 +22,10 @@
   <ion-searchbar placeholder="搜索"></ion-searchbar>
 
   <!-- 内容卡片 -->
-  <ion-card *ngFor="let item of items">
+  <ion-card *ngFor="let item of items" (click)="goToPostDetail(item.id)">
     <ion-card-header>
       <ion-card-title>{{ item.title }}</ion-card-title>
-      <ion-card-subtitle>{{ item.subtitle }}</ion-card-subtitle>
+      <ion-card-subtitle>{{ item.author }}</ion-card-subtitle>
     </ion-card-header>
     <ion-card-content>
       <ion-grid>

+ 10 - 3
src/app/home/home.page.ts

@@ -1,4 +1,5 @@
 import { Component } from '@angular/core';
+import { Router } from '@angular/router';
 
 @Component({
   selector: 'app-home',
@@ -9,21 +10,27 @@ export class HomePage {
 
   items = [
     {
+      id: 1,
       title: "是什么让“牢大”科比,成了中文互联网最热门的流量密码?",
-      subtitle: "BB姬",
+      author: "BB姬",
       image: "assets/images/kobe.jpg",
       description: "本文首发于微信公众号“BB姬”(bibiji300)真新镇小茂|文 2015年,网络游戏主播李赣组织孙笑川等人,成...",
       footer: "4076 赞同 · 1696 收藏"
     },
     {
+      id: 2,
       title: "汪汪:姜萍事件很快就会有结果,做点分析预测",
-      subtitle: "科学声音",
+      author: "科学声音",
       image: "assets/images/jiangping.jpg",
       description: "阿里达摩院在6.21发布了决赛通知,确认姜萍参赛。今天是6.24,姜萍考试结束,我觉得我可以写点东西了。前...",
       footer: "1049 赞同 · 469 评论"
     }
   ];
+  constructor(private router: Router) {} // 确保 Router 被正确引入
 
-  constructor() {}
+  // constructor() {}
 
+  goToPostDetail(id: number) {
+    this.router.navigate(['/post-detail', id]);
+  }
 }

+ 2 - 4
src/app/tab3/tab3.page.html

@@ -8,16 +8,14 @@
   <!-- 顶部个人信息 -->
   <ion-card>
     <ion-item>
-      <ion-avatar slot="start">
+      <ion-avatar slot="start" (click)="navigateToLogin()">
         <img [src]="userInfo.avatarUrl" />
       </ion-avatar>
       <ion-label>
         <h2>{{ userInfo.username }}</h2>
         <p>{{ userInfo.nickname }}</p>
       </ion-label>
-    </ion-item>
-    <ion-item lines="none">
-      <ion-button fill="outline" slot="end">添加认证信息</ion-button>
+      <ion-button fill="outline" slot="end" (click)="navigateToEditInfo()">编辑资料</ion-button>
     </ion-item>
     <ion-item lines="none">
       <ion-label>

+ 10 - 1
src/app/tab3/tab3.page.ts

@@ -10,7 +10,7 @@ export class Tab3Page {
   userInfo: any = {
     username: 'Faraday',
     nickname: '点击查看个人主页',
-    avatarUrl: '/assets/avatar.png',
+    avatarUrl: '/assets/images/avatar.png',
     followers: 10086,
     following: 129,
     favorites: 7,
@@ -18,6 +18,15 @@ export class Tab3Page {
   };
 
   constructor(private navCtrl: NavController) {}
+  //编辑资料
+  navigateToEditInfo() {
+    this.navCtrl.navigateForward('/edit-info');
+  }
+
+  //新增,登录
+  navigateToLogin() {
+    this.navCtrl.navigateForward('/login');
+  }
 
   goToBookshelf() {
     this.navCtrl.navigateForward('/bookshelf');

+ 2 - 0
src/app/tabs/tabs-routing.module.ts

@@ -7,6 +7,7 @@ const routes: Routes = [
     path: 'tabs',
     component: TabsPage,
     children: [
+
       {
         path: 'home',  // 新增的路由配置
         loadChildren: () => import('../home/home.module').then(m => m.HomePageModule)
@@ -28,6 +29,7 @@ const routes: Routes = [
         redirectTo: '/tabs/tab1',
         pathMatch: 'full'
       }
+
     ]
   },
   {

+ 0 - 0
src/assets/avatar.png → src/assets/images/avatar.png


BIN
src/assets/images/jiangping.jpg


BIN
src/assets/images/kobe.jpg


+ 12 - 0
src/modules/aigc/aigc-routing.module.ts

@@ -0,0 +1,12 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+const routes: Routes = [
+  {path: 'chat', loadChildren: () => import('./chat/chat.module').then(mod => mod.ChatPageModule)},
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class AigcRoutingModule { }

+ 14 - 0
src/modules/aigc/aigc.module.ts

@@ -0,0 +1,14 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { AigcRoutingModule } from './aigc-routing.module';
+
+
+@NgModule({
+  declarations: [],
+  imports: [
+    CommonModule,
+    AigcRoutingModule
+  ]
+})
+export class AigcModule { }

+ 17 - 0
src/modules/aigc/chat/chat-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { ChatPage } from './chat.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: ChatPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class ChatPageRoutingModule {}

+ 20 - 0
src/modules/aigc/chat/chat.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { ChatPageRoutingModule } from './chat-routing.module';
+
+import { ChatPage } from './chat.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    ChatPageRoutingModule
+  ],
+  declarations: [ChatPage]
+})
+export class ChatPageModule {}

+ 24 - 0
src/modules/aigc/chat/chat.page.html

@@ -0,0 +1,24 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-title>
+      AIGC 消息发送
+    </ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <ion-item>
+    <ion-input placeholder="输入消息" [(ngModel)]="userInput"></ion-input>
+  </ion-item>
+
+  <ion-button expand="block" (click)="sendMessage()">发送</ion-button>
+
+  <ion-card *ngFor="let message of messageList">
+    <ion-card-header>
+      {{message?.role}}
+    </ion-card-header>
+    <ion-card-content>
+      {{ message?.content }}
+    </ion-card-content>
+  </ion-card>
+</ion-content>

+ 0 - 0
src/modules/aigc/chat/chat.page.scss


+ 17 - 0
src/modules/aigc/chat/chat.page.spec.ts

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

+ 31 - 0
src/modules/aigc/chat/chat.page.ts

@@ -0,0 +1,31 @@
+import { Component, OnInit } from '@angular/core';
+// 引用FmodeChatCompletion类
+import { TestChatCompletion, TestChatMessage } from './class-chat-completion';
+
+@Component({
+  selector: 'app-chat',
+  templateUrl: './chat.page.html',
+  styleUrls: ['./chat.page.scss'],
+})
+export class ChatPage implements OnInit {
+  messageList:Array<TestChatMessage> = []
+  userInput:string = ""
+
+  completion:TestChatCompletion
+  constructor() { 
+    this.completion = new TestChatCompletion(this.messageList)
+  }
+
+  ngOnInit() {
+  }
+  sendMessage(){
+    this.messageList.push({
+      role:"user",
+      content: this.userInput
+    })
+    this.userInput = ""
+    this.completion.createCompletionByStream()
+
+  }
+
+}

+ 99 - 0
src/modules/aigc/chat/class-chat-completion.ts

@@ -0,0 +1,99 @@
+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": "gpt-3.5-turbo",
+  "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/",
+  "referrerPolicy": "strict-origin-when-cross-origin",
+  "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":"gpt-3.5-turbo-0613","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
+     * data: {"id":"chatcmpl-y2PLKqPDnwAFJIj2L5aqdH5TWK9Yv","object":"chat.completion.chunk","created":1696770162,"model":"gpt-3.5-turbo-0613","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);
+  }
+}
+}
+}

+ 30 - 0
src/modules/babylon/README.md

@@ -0,0 +1,30 @@
+# WebGPU Babylon教学案例库
+
+
+
+# 模型素材
+- 中国城市lowpoly
+    - DIR https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/chinese-city-lowpoly/gltf/
+    - FILE scene.gltf
+- 中国象棋
+    - DIR https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/chinese-chess/gltf/
+    - FILE scene.gltf
+- 古风少女
+    - DIR https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/ancient-chinese-woman/gltf/
+    - FILE scene.gltf
+- 古风少年
+    - https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/ancient-chinese-man/README.md
+- 卡通异瞳少女
+    - DIR https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/vtuber-selen/gltf/
+    - FILE scene.gltf
+- 中国老杯子
+    - DIR https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/chinese-iron-cup/
+    - FILE scene.gltf
+
+## 上传格式
+- OBS对象KEY
+    - /storage/3d/model/<model_name>/gltf/scene.gltf
+- OBS命令行
+    - obsutil sync ../model/ obs://nova-cloud/storage/3d/model/
+- 本地临时存储
+    - ~/Downloads/model/<model_name>/gltf/scene.gltf

+ 12 - 0
src/modules/babylon/pages/case-babylon/README.md

@@ -0,0 +1,12 @@
+# Babylon.js使用示例
+
+- 官方网站 https://doc.babylonjs.com/
+
+# 依赖安装
+- npm安装 https://doc.babylonjs.com/setup/frameworkPackages/es6Support
+
+``` bash
+npm install @babylonjs/core -S
+npm install @babylonjs/materials -S
+npm install @babylonjs/loaders -S
+```

+ 17 - 0
src/modules/babylon/pages/case-babylon/case-babylon-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { CaseBabylonPage } from './case-babylon.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: CaseBabylonPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class CaseBabylonPageRoutingModule {}

+ 20 - 0
src/modules/babylon/pages/case-babylon/case-babylon.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CaseBabylonPageRoutingModule } from './case-babylon-routing.module';
+
+import { CaseBabylonPage } from './case-babylon.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CaseBabylonPageRoutingModule
+  ],
+  declarations: [CaseBabylonPage]
+})
+export class CaseBabylonPageModule {}

+ 6 - 0
src/modules/babylon/pages/case-babylon/case-babylon.page.html

@@ -0,0 +1,6 @@
+<div class="list">
+  <div (click)="showCharacter(item?.name)" class="character" *ngFor="let item of CharacterMeshList">
+    <h1>{{item?.name}}</h1>
+  </div>
+</div>
+<canvas id="renderCanvas" touch-action="none"></canvas>

+ 43 - 0
src/modules/babylon/pages/case-babylon/case-babylon.page.scss

@@ -0,0 +1,43 @@
+// 调试面板
+#scene-explorer-host {
+    position: absolute !important;
+}
+#sceneExplorer{
+    position:absolute!important;
+    height:100vh!important;
+}
+.babylonDebugLayer {
+    position: absolute;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    width: 300px; /* 调试层的宽度 */
+    background-color: rgba(255, 255, 255, 0.8); /* 背景颜色和透明度 */
+    z-index: 20; /* 确保调试层在最顶层 */
+    overflow: auto; /* 如果内容过多,添加滚动条 */
+  }
+  
+  .babylonDebugLayer .content {
+    padding: 10px;
+  }
+
+// 展示区
+#renderCanvas {
+    width: 100vw;
+    height: 100vh;
+    display: block;
+    font-size: 0;
+    z-index: 1;
+}
+
+.list{
+    position: absolute;
+    top:20px;
+    z-index: 10;
+    width: 100vw;
+    display: flex;
+    justify-content: center;
+    .character{
+        
+    }
+}

+ 17 - 0
src/modules/babylon/pages/case-babylon/case-babylon.page.spec.ts

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

+ 171 - 0
src/modules/babylon/pages/case-babylon/case-babylon.page.ts

@@ -0,0 +1,171 @@
+import { Component, OnInit } from '@angular/core';
+import * as BABYLON from "@babylonjs/core/Legacy/legacy";
+import { Engine, Scene } from "@babylonjs/core";
+import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera";
+import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight";
+import { Vector3 } from "@babylonjs/core/Maths/math.vector";
+import { CreateGround } from "@babylonjs/core/Meshes/Builders/groundBuilder";
+import { CreateSphere } from "@babylonjs/core/Meshes/Builders/sphereBuilder";
+import { GridMaterial } from "@babylonjs/materials/grid/gridMaterial";
+
+import "@babylonjs/loaders/glTF";
+
+@Component({
+  selector: 'app-case-babylon',
+  templateUrl: './case-babylon.page.html',
+  styleUrls: ['./case-babylon.page.scss'],
+})
+export class CaseBabylonPage implements OnInit {
+  private engine: BABYLON.Engine|undefined;
+  private scene: BABYLON.Scene|undefined;
+
+  CharacterMap:any = {}
+  CharacterMeshList = [
+    {name:"古风少女",
+    dirPath:"https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/ancient-chinese-woman/gltf/",
+    filePath:"scene.gltf"},
+    {name:"中国象棋",
+    dirPath:"https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/chinese-chess/gltf/",
+    filePath:"scene.gltf"},
+    {name:"卡通异瞳少女",
+    dirPath:"https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/vtuber-selen/gltf/",
+    filePath:"scene.gltf"},
+    {name:"中国老杯子",
+    dirPath:"https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/chinese-iron-cup/",
+    filePath:"scene.gltf"},
+    {name:"中国城市lowpoly",
+    dirPath:"https://nova-cloud.obs.cn-south-1.myhuaweicloud.com/storage/3d/model/chinese-city-lowpoly/gltf/",
+    filePath:"scene.gltf"},
+
+  ]
+  constructor() { }
+
+  ngOnInit() {
+    this.initBabylon()
+  }
+
+  initBabylon(){
+    // Get the canvas element from the DOM.
+    const canvas:any = document.getElementById("renderCanvas");
+    // Set the canvas size to match the device pixel ratio
+    const devicePixelRatio = window.devicePixelRatio || 1;
+    canvas.width = window.innerWidth * devicePixelRatio;
+    canvas.height = window.innerHeight * devicePixelRatio;
+    this.engine = new BABYLON.Engine(canvas, true);
+    this.scene = new BABYLON.Scene(this.engine);
+
+    // Create a basic light, aiming 0,1,0 - meaning, to the sky.
+    const light = new BABYLON.HemisphericLight('light1', new BABYLON.Vector3(0, 1, 0), this.scene);
+
+    // 加载粒子效果覆盖地面
+    this.loadGroundParticle()
+    
+    // 设置初始相机
+    // Add an ArcRotateCamera to the scene and attach it to the canvas
+    // let initPositon = new BABYLON.Vector3(0,12,0)
+    let initPositon = new BABYLON.Vector3(0,0,0)
+    let beta = Math.PI / 2.5 // 镜头初始 纬度 Math.PI / 3 斜下45度
+    let radius = 20 // 镜头初始半径
+    const camera = new BABYLON.ArcRotateCamera('camera1', Math.PI / 2, beta, radius, initPositon, this.scene);
+    camera.attachControl(canvas, true);
+
+    // Load the FBX model
+
+    this.CharacterMeshList.forEach(character=>{
+      BABYLON.SceneLoader.ImportMesh('', character.dirPath, character.filePath, this.scene, (meshes, particleSystems, skeletons) => {
+        console.log(meshes)
+        // characterMesh.isVisible
+        meshes.forEach(mesh=>mesh.isVisible=false)
+        this.CharacterMap[character?.name] = {
+          meshes:meshes,
+          particleSystems:particleSystems,
+          skeletons:skeletons
+        }
+        if (skeletons.length > 0) {
+          this.scene?.beginAnimation(skeletons[0], 0, 100, true);
+        }
+      });
+    })
+    setTimeout(() => {
+      this.showCharacter("机器人")
+    }, 3000);
+    
+
+    // Register a render loop to repeatedly render the scene.
+    this.engine.runRenderLoop(() => {
+      this.scene?.render();
+    });
+
+    // 开启调试层,用于抓取角度
+    this.scene.debugLayer.show();
+
+    // Watch for browser/canvas resize events
+    window.addEventListener('resize', () => {
+      this.engine?.resize();
+    });
+  }
+
+  showCharacter(name?:string){
+    if(!name) return
+
+    Object.keys(this.CharacterMap).forEach(tempname=>{
+      let character = this.CharacterMap[tempname];
+      if(tempname==name){
+        if(character.meshes?.length){
+          character.meshes.forEach((mesh:any)=>mesh.isVisible=true)
+        }
+      }else{
+        if(character.meshes?.length){
+          character.meshes.forEach((mesh:any)=>mesh.isVisible=false)
+        }
+      }
+    })
+
+   
+  }
+  loadGroundParticle(){
+      if(!this.scene) return
+      // 创建粒子系统
+      const particleSystem = new BABYLON.ParticleSystem("particles", 2000, this.scene);
+
+      // 纹理
+      particleSystem.particleTexture = new BABYLON.Texture("textures/flare.png", this.scene);
+
+      // 发射器
+      particleSystem.emitter = new BABYLON.Vector3(0, 0, 0); // 发射器位置
+      particleSystem.minEmitBox = new BABYLON.Vector3(-300, 0, -300); // 最小发射范围
+      particleSystem.maxEmitBox = new BABYLON.Vector3(300, 0, 300); // 最大发射范围
+
+      // 粒子颜色
+      particleSystem.color1 = new BABYLON.Color4(1, 1, 1, 1);
+      particleSystem.color2 = new BABYLON.Color4(0.5, 0.5, 1, 1);
+      particleSystem.colorDead = new BABYLON.Color4(0, 0, 0.2, 0.5);
+
+      // 粒子大小
+      particleSystem.minSize = 0.1;
+      particleSystem.maxSize = 0.5;
+
+      // 粒子生命周期
+      particleSystem.minLifeTime = 0.3;
+      particleSystem.maxLifeTime = 1.5;
+
+      // 发射速率
+      particleSystem.emitRate = 1000;
+
+      // 粒子方向
+      particleSystem.direction1 = new BABYLON.Vector3(-1, 1, -1);
+      particleSystem.direction2 = new BABYLON.Vector3(1, 1, 1);
+
+      // 重力
+      particleSystem.gravity = new BABYLON.Vector3(0, -9.81, 0);
+
+      // 粒子速度
+      particleSystem.minEmitPower = 0.5;
+      particleSystem.maxEmitPower = 1.5;
+      particleSystem.updateSpeed = 0.01;
+
+      // 开始粒子系统
+      particleSystem.start();
+  }
+
+}

+ 17 - 0
src/modules/contact/contact-detail/contact-detail-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { ContactDetailPage } from './contact-detail.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: ContactDetailPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class ContactDetailPageRoutingModule {}

+ 20 - 0
src/modules/contact/contact-detail/contact-detail.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { ContactDetailPageRoutingModule } from './contact-detail-routing.module';
+
+import { ContactDetailPage } from './contact-detail.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    ContactDetailPageRoutingModule
+  ],
+  declarations: [ContactDetailPage]
+})
+export class ContactDetailPageModule {}

+ 13 - 0
src/modules/contact/contact-detail/contact-detail.page.html

@@ -0,0 +1,13 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>contact-detail</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">contact-detail</ion-title>
+    </ion-toolbar>
+  </ion-header>
+</ion-content>

+ 0 - 0
src/modules/contact/contact-detail/contact-detail.page.scss


+ 17 - 0
src/modules/contact/contact-detail/contact-detail.page.spec.ts

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

+ 15 - 0
src/modules/contact/contact-detail/contact-detail.page.ts

@@ -0,0 +1,15 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-contact-detail',
+  templateUrl: './contact-detail.page.html',
+  styleUrls: ['./contact-detail.page.scss'],
+})
+export class ContactDetailPage implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+}

+ 17 - 0
src/modules/contact/contact-list/contact-list-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { ContactListPage } from './contact-list.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: ContactListPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class ContactListPageRoutingModule {}

+ 20 - 0
src/modules/contact/contact-list/contact-list.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { ContactListPageRoutingModule } from './contact-list-routing.module';
+
+import { ContactListPage } from './contact-list.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    ContactListPageRoutingModule
+  ],
+  declarations: [ContactListPage]
+})
+export class ContactListPageModule {}

+ 34 - 0
src/modules/contact/contact-list/contact-list.page.html

@@ -0,0 +1,34 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>通讯录</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">通讯录</ion-title>
+    </ion-toolbar>
+  </ion-header>
+  
+  <ion-searchbar [(ngModel)]="searchName" (ionInput)="search()"></ion-searchbar>
+
+  <ion-list>
+    <ng-container *ngFor="let contact of contactList;let index = index;">
+      <!-- 分组:根据下标首个出现的元素显示分组分隔符 -->
+      <ion-item-divider *ngIf="charGroupIndex[contact.get('firstChar')] == index">
+        <ion-label>{{contact.get('firstChar')}}</ion-label>
+      </ion-item-divider>
+      <ion-item lines="none">
+        <ion-avatar slot="start">
+          <img [src]="contact.get('avatarUrl') || 'https://ionicframework.com/docs/img/demos/avatar.svg'">
+        </ion-avatar>
+        <ion-label>
+          <h2>{{ contact.get('name') }}</h2>
+          <p>性别: {{ contact.get('gender') }}</p>
+          <p>手机: {{ contact.get('mobile') }}</p>
+        </ion-label>
+      </ion-item>
+    </ng-container>
+  </ion-list>
+</ion-content>

+ 0 - 0
src/modules/contact/contact-list/contact-list.page.scss


+ 17 - 0
src/modules/contact/contact-list/contact-list.page.spec.ts

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

+ 42 - 0
src/modules/contact/contact-list/contact-list.page.ts

@@ -0,0 +1,42 @@
+import { Component, OnInit } from '@angular/core';
+import * as Parse from "parse";
+@Component({
+  selector: 'app-contact-list',
+  templateUrl: './contact-list.page.html',
+  styleUrls: ['./contact-list.page.scss'],
+})
+export class ContactListPage implements OnInit {
+  searchName: string = '';
+  contactList: Array<Parse.Object> = [];
+
+  ngOnInit() {
+    this.loadContact();
+  }
+
+  charGroupIndex:any = {}
+  loadContact() {
+    const Contact = Parse.Object.extend('Contact');
+    const query = new Parse.Query(Contact);
+    query.ascending('firstChar');
+
+    if (this.searchName) {
+      query.contains('name', this.searchName);
+    }
+
+    query.find().then((results) => {
+      this.contactList = results;
+      this.contactList.forEach((contact,index)=>{
+        if(this.charGroupIndex[contact.get("firstChar")] == undefined){
+          this.charGroupIndex[contact.get("firstChar")] = index
+        }
+      })
+    }, (error) => {
+      console.error('Error while fetching contacts', error);
+    });
+  }
+
+  search() {
+    this.loadContact();
+  }
+
+}

+ 12 - 0
src/modules/contact/contact-routing.module.ts

@@ -0,0 +1,12 @@
+import { NgModule } from '@angular/core';
+import { RouterModule, Routes } from '@angular/router';
+
+const routes: Routes = [
+  {path: 'list', loadChildren: () => import('./contact-list/contact-list.module').then(mod => mod.ContactListPageModule)},
+  {path: 'detail/:id', loadChildren: () => import('./contact-detail/contact-detail.module').then(mod => mod.ContactDetailPageModule)},
+];
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+export class ContactRoutingModule { }

+ 14 - 0
src/modules/contact/contact.module.ts

@@ -0,0 +1,14 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { ContactRoutingModule } from './contact-routing.module';
+
+
+@NgModule({
+  declarations: [],
+  imports: [
+    CommonModule,
+    ContactRoutingModule
+  ]
+})
+export class ContactModule { }

+ 17 - 0
src/modules/post-detail/post-detail-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { PostDetailPage } from './post-detail.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: PostDetailPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class PostDetailPageRoutingModule {}

+ 20 - 0
src/modules/post-detail/post-detail.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { PostDetailPageRoutingModule } from './post-detail-routing.module';
+
+import { PostDetailPage } from './post-detail.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    PostDetailPageRoutingModule
+  ],
+  declarations: [PostDetailPage]
+})
+export class PostDetailPageModule {}

+ 75 - 0
src/modules/post-detail/post-detail.page.html

@@ -0,0 +1,75 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-back-button defaultHref="/home"></ion-back-button>
+    </ion-buttons>
+    <ion-title>帖子详情</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <!-- 内容区 -->
+  <ion-card>
+    <ion-card-header>
+      <ion-item lines="none">
+        <ion-avatar slot="start">
+          <img [src]="post.authorAvatar" />
+        </ion-avatar>
+        <ion-label>
+          <h2>{{ post.author }}</h2>
+          <p>{{ post.time }}</p>
+        </ion-label>
+      </ion-item>
+    </ion-card-header>
+    <ion-card-content>
+      <h2>{{ post.title }}</h2>
+      <img [src]="post.image" *ngIf="post.image" />
+      <p>{{ post.content }}</p>
+    </ion-card-content>
+    <ion-item lines="none">
+      <ion-button fill="clear" slot="start">
+        <ion-icon name="thumbs-up"></ion-icon>
+        <ion-label>点赞</ion-label>
+      </ion-button>
+      <ion-button fill="clear" slot="end">
+        <ion-icon name="share-social"></ion-icon>
+        <ion-label>分享</ion-label>
+      </ion-button>
+    </ion-item>
+  </ion-card>
+
+  <!-- 评论区 -->
+  <ion-segment value="all">
+    <ion-segment-button value="all">
+      <ion-label>全部回复</ion-label>
+    </ion-segment-button>
+    <ion-segment-button value="author">
+      <ion-label>只看楼主</ion-label>
+    </ion-segment-button>
+  </ion-segment>
+
+  <ion-segment value="popular" (ionChange)="sortComments($event)">
+    <ion-segment-button value="popular">
+      <ion-label>热门</ion-label>
+    </ion-segment-button>
+    <ion-segment-button value="asc">
+      <ion-label>时间正序</ion-label>
+    </ion-segment-button>
+    <ion-segment-button value="desc">
+      <ion-label>时间倒序</ion-label>
+    </ion-segment-button>
+  </ion-segment>
+
+  <ion-list>
+    <ion-item *ngFor="let comment of comments">
+      <ion-avatar slot="start">
+        <img [src]="comment.avatar" />
+      </ion-avatar>
+      <ion-label>
+        <h2>{{ comment.author }}</h2>
+        <p>{{ comment.content }}</p>
+        <p>{{ comment.time }}</p>
+      </ion-label>
+    </ion-item>
+  </ion-list>
+</ion-content>

+ 14 - 0
src/modules/post-detail/post-detail.page.scss

@@ -0,0 +1,14 @@
+ion-avatar img {
+    width: 100%;
+    height: auto;
+  }
+  
+  ion-card h2 {
+    margin: 0;
+    font-size: 1.2em;
+  }
+  
+  ion-card p {
+    margin: 0.5em 0;
+  }
+  

+ 17 - 0
src/modules/post-detail/post-detail.page.spec.ts

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

+ 45 - 0
src/modules/post-detail/post-detail.page.ts

@@ -0,0 +1,45 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+
+@Component({
+  selector: 'app-post-detail',
+  templateUrl: './post-detail.page.html',
+  styleUrls: ['./post-detail.page.scss'],
+})
+export class PostDetailPage implements OnInit {
+  post: any;
+  comments: any[]= [];
+
+  constructor(private route: ActivatedRoute) {}
+
+  ngOnInit() {
+    const postId = this.route.snapshot.paramMap.get('id');
+    // 模拟获取数据,根据实际情况从服务器或本地获取数据
+    this.post = {
+      id: postId,
+      author: '发帖人',
+      authorAvatar: '发帖人头像URL',
+      time: '发帖时间',
+      title: `标题${postId}`,
+      content: `内容${postId}`,
+      image: `图片URL${postId}`
+    };
+
+    this.comments = [
+      { id: 1, author: '用户1', avatar: '用户1头像URL', content: '评论内容1', time: '评论时间1', likes: 10 },
+      { id: 2, author: '用户2', avatar: '用户2头像URL', content: '评论内容2', time: '评论时间2', likes: 5 },
+      // 添加更多评论数据
+    ];
+  }
+
+  sortComments(event: any) {
+    const value = event.detail.value;
+    if (value === 'popular') {
+      this.comments.sort((a, b) => b.likes - a.likes); // 按点赞数排序
+    } else if (value === 'asc') {
+      this.comments.sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime()); // 时间正序
+    } else if (value === 'desc') {
+      this.comments.sort((a, b) => new Date(b.time).getTime() - new Date(a.time).getTime()); // 时间倒序
+    }
+  }
+}

+ 32 - 0
src/modules/study/case-angular/comps/edit-ratio-star/README.md

@@ -0,0 +1,32 @@
+# 组件:星星打分组件
+
+# 组件结构描述
+- 基本功能:用户可以引入该组件,绑定在number类型的属性上,实现用户选择星星,就能返回对应星级的数字.
+- 页面模板
+    - 横向的多颗行星列表
+        - 根据starCheckList进行循环,显示满星和空星
+    - 星星用ion-icon字体图标表示
+- 常量属性
+    - starCheckList:Array<boolean> 星星选择的数组
+        - [true,true,false,false,falce] 表示两颗满星 三颗空星
+- 输入属性
+    - value:number 默认 0
+    - maxValue:number default 5 默认5颗星
+- 输出事件
+    - onValueChange 事件 返回number类型 组件内选择的星星分数
+- 逻辑函数
+    - 初始化函数
+        - 根据value和maxValue的值,分别计算空星星数量和满星星数量,生成starCheckList
+    - 星星选择函数
+        - 根据星星所在位置,给value分数赋值,并且重新生成星星数组.
+        - 将value的值通过onValueChange事件进行发送
+
+# 组件开发:人工实现
+``` bash
+ionic g component edit-ratio-star --standalone
+```
+
+# 思考题目
+- 问题1:如何实现当满星被再次点击时候,分值设置为0?
+- 问题2:如何实现不用星星做标识,换成笑脸?
+- 问题3:如何实现图标颜色的用户自定义?

+ 3 - 0
src/modules/study/case-angular/comps/edit-ratio-star/edit-ratio-star.component.html

@@ -0,0 +1,3 @@
+<div class="list">
+  <ion-icon [style.color]="color" (click)="onStarClick(index)" [name]="checked?fillIcon:emptyIcon" *ngFor="let checked of starCheckList;let index=index;"></ion-icon>
+</div>

+ 0 - 0
src/modules/study/case-angular/comps/edit-ratio-star/edit-ratio-star.component.scss


+ 22 - 0
src/modules/study/case-angular/comps/edit-ratio-star/edit-ratio-star.component.spec.ts

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

+ 61 - 0
src/modules/study/case-angular/comps/edit-ratio-star/edit-ratio-star.component.ts

@@ -0,0 +1,61 @@
+import { NgForOf } from '@angular/common';
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { IonIcon } from '@ionic/angular/standalone';
+
+@Component({
+  selector: 'app-edit-ratio-star',
+  templateUrl: './edit-ratio-star.component.html',
+  styleUrls: ['./edit-ratio-star.component.scss'],
+  standalone: true,
+  imports:[
+    IonIcon,NgForOf
+  ]
+})
+export class EditRatioStarComponent  implements OnInit {
+
+  @Input()
+  fillIcon:string = "star"
+  @Input()
+  emptyIcon:string = "star-outline"
+  @Input()
+  color:string = "#FFFF00"
+
+  @Input()
+  value:number = 2;
+  @Output()
+  onValueChange:EventEmitter<number> = new EventEmitter<number>
+
+  maxValue:number = 10;
+  starCheckList:Array<boolean> = []
+  constructor() {
+   }
+
+  ngOnInit() {
+    this.makeStarList()
+  }
+
+  onStarClick(index:number){
+    let score = index + 1
+
+    if(score==this.value){ // 点原星星,设置0分
+      this.value = 0
+    }else{ // 根据星星位置,设置分数
+      this.value = score;
+    }
+
+    this.makeStarList();
+    this.onValueChange.emit(this.value)
+  }
+  makeStarList(){
+    let starList = []
+    for (let index = 0; index < this.value; index++) {
+      starList.push(true)
+    }
+    let leftCount = this.maxValue - this.value
+    for (let index = 0; index < leftCount; index++) {
+      starList.push(false)
+    }
+    this.starCheckList = starList
+  }
+
+}

+ 17 - 0
src/modules/study/case-angular/pages/case-edit/case-edit-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { CaseEditPage } from './case-edit.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: CaseEditPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class CaseEditPageRoutingModule {}

+ 22 - 0
src/modules/study/case-angular/pages/case-edit/case-edit.module.ts

@@ -0,0 +1,22 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CaseEditPageRoutingModule } from './case-edit-routing.module';
+
+import { CaseEditPage } from './case-edit.page';
+import { EditRatioStarComponent } from '../../comps/edit-ratio-star/edit-ratio-star.component';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CaseEditPageRoutingModule,
+    EditRatioStarComponent
+  ],
+  declarations: [CaseEditPage]
+})
+export class CaseEditPageModule {}

+ 59 - 0
src/modules/study/case-angular/pages/case-edit/case-edit.page.html

@@ -0,0 +1,59 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>case-edit</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">case-edit</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+  <ion-card>
+    <ion-item>
+      星星打分:<app-edit-ratio-star [value]="score" (onValueChange)="score = $event"></app-edit-ratio-star>
+    </ion-item>
+    <ion-item>
+      图标打分:<app-edit-ratio-star color="blue" fillIcon="home" emptyIcon="home-outline" [value]="score" (onValueChange)="score = $event"></app-edit-ratio-star>
+    </ion-item>
+    <ion-item>
+      <ion-input type="number" [(ngModel)]="score"></ion-input>
+    </ion-item>
+  </ion-card>
+
+  <ion-card>
+    <ion-item>
+      已输入用户名:{{username}}
+    </ion-item>
+    <ion-item>
+      <ion-label>Input事件</ion-label>
+      <ion-input type="text" (input)="onUserNameInput($event)"></ion-input>
+    </ion-item>
+    <ion-item>
+      <ion-label>ngModel双向绑定</ion-label>
+      <ion-input type="text" [value]="username" (input)="onUserNameInput($event)"></ion-input>
+      <ion-input type="text" [(ngModel)]="username"></ion-input>
+    </ion-item>
+  </ion-card>
+
+  <ion-card>
+    <ion-item>
+      用户资料:各种输入类型
+    </ion-item>
+    <ion-item>
+      <ion-label>姓名</ion-label>
+      <ion-input type="text" [(ngModel)]="userInfo.name"></ion-input>
+    </ion-item>
+    <ion-item>
+      <ion-label>年龄</ion-label>
+      <ion-input type="number" [(ngModel)]="userInfo.age"></ion-input>
+    </ion-item>
+    <ion-item>
+      <ion-label>生日</ion-label>
+      <ion-datetime [(ngModel)]="userInfo.birthday"></ion-datetime>
+    </ion-item>
+  </ion-card>
+
+</ion-content>

+ 0 - 0
src/modules/study/case-angular/pages/case-edit/case-edit.page.scss


+ 17 - 0
src/modules/study/case-angular/pages/case-edit/case-edit.page.spec.ts

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

+ 27 - 0
src/modules/study/case-angular/pages/case-edit/case-edit.page.ts

@@ -0,0 +1,27 @@
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+  selector: 'app-case-edit',
+  templateUrl: './case-edit.page.html',
+  styleUrls: ['./case-edit.page.scss'],
+})
+export class CaseEditPage implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+  }
+
+  username:string = ""
+  userInfo:any = {
+    name:"姓名",
+    age:18,
+    birthday:new Date()
+  }
+  score:number = 4;
+  onUserNameInput(ev:Event|any){
+    console.log(ev)
+    this.username = ev?.target?.value;
+  }
+
+}

+ 17 - 0
src/modules/study/case-angular/pages/tsdatatype/tsdatatype-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { TsdatatypePage } from './tsdatatype.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: TsdatatypePage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class TsdatatypePageRoutingModule {}

+ 20 - 0
src/modules/study/case-angular/pages/tsdatatype/tsdatatype.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { TsdatatypePageRoutingModule } from './tsdatatype-routing.module';
+
+import { TsdatatypePage } from './tsdatatype.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    TsdatatypePageRoutingModule
+  ],
+  declarations: [TsdatatypePage]
+})
+export class TsdatatypePageModule {}

+ 83 - 0
src/modules/study/case-angular/pages/tsdatatype/tsdatatype.page.html

@@ -0,0 +1,83 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>{{title}} - 点赞数{{likeCount}}</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">tsdatatype</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+  <ion-item>
+    常见的数据类型:
+    {{dataTypeList}}
+  </ion-item>
+
+  <ion-item>
+    日期类型:
+    {{now}}
+    日期管道(DatePipe的使用)
+    {{now | date:"yyyy-MM-dd"}}
+  </ion-item>
+
+  <ion-item>
+    Object类型:
+    {{student}}
+    名称:{{student.name}}
+    年龄:{{student.age}}
+  </ion-item>
+
+  <ion-item>
+    Boolean类型的模板(*ngIf)
+    {{isCollection}}
+    <ion-icon *ngIf="isCollection" name="star"></ion-icon>
+    <ion-icon *ngIf="!isCollection" name="star-outline"></ion-icon>
+  </ion-item>
+
+  <ion-list>
+    接口描述的Object作为数组元素的显示
+    <ion-item>
+      直接显示:{{studentList}}
+    </ion-item>
+    <ion-item>
+      下标取元素显示:
+      {{studentList[0].name}}
+      {{studentList[1].idcard}}
+      未定义变量的预取值(需要用?.表明可能不存在的父级Object)
+      {{studentList[2]?.name}}
+    </ion-item>
+    <ion-item>
+      数组Object的循环显示(*ngFor)
+      <ion-card *ngFor="let student of studentList">
+        <ion-card-title>
+          {{student?.name}}
+        </ion-card-title>
+        <ion-card-subtitle>
+          {{student?.age}}
+        </ion-card-subtitle>
+      </ion-card>
+    </ion-item>
+  </ion-list>
+
+  <ion-list>
+    管道:https://angular.cn/guide/pipes
+    <ion-item>
+      日期格式化
+      DatePipe {{now | date:"HH:mm:SS"}}
+    </ion-item>
+    <ion-item>
+      大写转换
+      UpperCasePipe {{studentList[0]?.name | uppercase}}
+    </ion-item>
+    <ion-item>
+      JSON调试
+      JsonPipe {{studentList[0] | json}}
+    </ion-item>
+    <ion-item>
+      PercentPipe {{0.125 | percent}}
+    </ion-item>
+  </ion-list>
+</ion-content>

+ 0 - 0
src/modules/study/case-angular/pages/tsdatatype/tsdatatype.page.scss


+ 17 - 0
src/modules/study/case-angular/pages/tsdatatype/tsdatatype.page.spec.ts

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

+ 53 - 0
src/modules/study/case-angular/pages/tsdatatype/tsdatatype.page.ts

@@ -0,0 +1,53 @@
+import { Component, OnInit } from '@angular/core';
+
+
+interface Student{
+  name:string
+  age:number
+  idcard?:string
+}
+
+@Component({
+  selector: 'app-tsdatatype',
+  templateUrl: './tsdatatype.page.html',
+  styleUrls: ['./tsdatatype.page.scss'],
+})
+export class TsdatatypePage implements OnInit {
+
+  title:string = "TS数据类型与NG模板指令"
+  likeCount:number = 99
+  isCollection:boolean = false
+  now:Date = new Date();
+  dataTypeList:Array<string> = ["string","number","boolean"]
+  dataTypeList2:string[] = []
+
+  // 接口:Student
+  student:Student = {
+    name:"XiaoMing",
+    age:18,
+    idcard:"322xxxxxxxxxxxxxx"
+  }
+  studentList:Array<Student> = []
+  
+
+  constructor() { 
+    // 接口数组:Array<Student>
+    let studentList:Array<Student> = []
+    // 必填属性
+    let xiaoming = {name:"XiaoMing",age:18,idcard:"322*0000",lev:3}
+    // 可选属性
+    let xiaoli = {name:"XiaoLi",age:17}
+    studentList.push(xiaoming)
+    studentList.push(xiaoli)
+    this.studentList = studentList
+    // 未定义变量的预先取值
+    setTimeout(() => {
+      this.studentList.push({name:"小王",age:19})
+    }, 2000);
+
+  }
+
+  ngOnInit() {
+  }
+
+}

+ 17 - 0
src/modules/study/case-echarts/case-echarts-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { CaseEchartsPage } from './case-echarts.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: CaseEchartsPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class CaseEchartsPageRoutingModule {}

+ 20 - 0
src/modules/study/case-echarts/case-echarts.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CaseEchartsPageRoutingModule } from './case-echarts-routing.module';
+
+import { CaseEchartsPage } from './case-echarts.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CaseEchartsPageRoutingModule
+  ],
+  declarations: [CaseEchartsPage]
+})
+export class CaseEchartsPageModule {}

+ 22 - 0
src/modules/study/case-echarts/case-echarts.page.html

@@ -0,0 +1,22 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>NG引用Echarts图表</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">NG引用Echarts图表</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+
+   <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
+   <div id="main" style="width:100vw;max-width: 600px;height:400px;"></div>
+
+
+      <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
+      <div id="radar" style="width:100vw;max-width: 600px;height:400px;"></div>
+   
+</ion-content>

+ 0 - 0
src/modules/study/case-echarts/case-echarts.page.scss


+ 17 - 0
src/modules/study/case-echarts/case-echarts.page.spec.ts

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

+ 260 - 0
src/modules/study/case-echarts/case-echarts.page.ts

@@ -0,0 +1,260 @@
+import { Component, OnInit } from '@angular/core';
+import * as echarts from 'echarts';
+
+@Component({
+  selector: 'app-case-echarts',
+  templateUrl: './case-echarts.page.html',
+  styleUrls: ['./case-echarts.page.scss'],
+})
+export class CaseEchartsPage implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+    this.initBarChart()
+    this.initRadarChart()
+  }
+
+  initRadarChart(){
+    
+    // 基于准备好的dom,初始化echarts实例
+    var myChart = echarts.init(document.getElementById('radar'));
+
+    // 指定图表的配置项和数据
+    // Schema:
+// date,AQIindex,PM2.5,PM10,CO,NO2,SO2
+const dataBJ = [
+  [55, 9, 56, 0.46, 18, 6, 1],
+  [25, 11, 21, 0.65, 34, 9, 2],
+  [56, 7, 63, 0.3, 14, 5, 3],
+  [33, 7, 29, 0.33, 16, 6, 4],
+  [42, 24, 44, 0.76, 40, 16, 5],
+  [82, 58, 90, 1.77, 68, 33, 6],
+  [74, 49, 77, 1.46, 48, 27, 7],
+  [78, 55, 80, 1.29, 59, 29, 8],
+  [267, 216, 280, 4.8, 108, 64, 9],
+  [185, 127, 216, 2.52, 61, 27, 10],
+  [39, 19, 38, 0.57, 31, 15, 11],
+  [41, 11, 40, 0.43, 21, 7, 12],
+  [64, 38, 74, 1.04, 46, 22, 13],
+  [108, 79, 120, 1.7, 75, 41, 14],
+  [108, 63, 116, 1.48, 44, 26, 15],
+  [33, 6, 29, 0.34, 13, 5, 16],
+  [94, 66, 110, 1.54, 62, 31, 17],
+  [186, 142, 192, 3.88, 93, 79, 18],
+  [57, 31, 54, 0.96, 32, 14, 19],
+  [22, 8, 17, 0.48, 23, 10, 20],
+  [39, 15, 36, 0.61, 29, 13, 21],
+  [94, 69, 114, 2.08, 73, 39, 22],
+  [99, 73, 110, 2.43, 76, 48, 23],
+  [31, 12, 30, 0.5, 32, 16, 24],
+  [42, 27, 43, 1, 53, 22, 25],
+  [154, 117, 157, 3.05, 92, 58, 26],
+  [234, 185, 230, 4.09, 123, 69, 27],
+  [160, 120, 186, 2.77, 91, 50, 28],
+  [134, 96, 165, 2.76, 83, 41, 29],
+  [52, 24, 60, 1.03, 50, 21, 30],
+  [46, 5, 49, 0.28, 10, 6, 31]
+];
+const dataGZ = [
+  [26, 37, 27, 1.163, 27, 13, 1],
+  [85, 62, 71, 1.195, 60, 8, 2],
+  [78, 38, 74, 1.363, 37, 7, 3],
+  [21, 21, 36, 0.634, 40, 9, 4],
+  [41, 42, 46, 0.915, 81, 13, 5],
+  [56, 52, 69, 1.067, 92, 16, 6],
+  [64, 30, 28, 0.924, 51, 2, 7],
+  [55, 48, 74, 1.236, 75, 26, 8],
+  [76, 85, 113, 1.237, 114, 27, 9],
+  [91, 81, 104, 1.041, 56, 40, 10],
+  [84, 39, 60, 0.964, 25, 11, 11],
+  [64, 51, 101, 0.862, 58, 23, 12],
+  [70, 69, 120, 1.198, 65, 36, 13],
+  [77, 105, 178, 2.549, 64, 16, 14],
+  [109, 68, 87, 0.996, 74, 29, 15],
+  [73, 68, 97, 0.905, 51, 34, 16],
+  [54, 27, 47, 0.592, 53, 12, 17],
+  [51, 61, 97, 0.811, 65, 19, 18],
+  [91, 71, 121, 1.374, 43, 18, 19],
+  [73, 102, 182, 2.787, 44, 19, 20],
+  [73, 50, 76, 0.717, 31, 20, 21],
+  [84, 94, 140, 2.238, 68, 18, 22],
+  [93, 77, 104, 1.165, 53, 7, 23],
+  [99, 130, 227, 3.97, 55, 15, 24],
+  [146, 84, 139, 1.094, 40, 17, 25],
+  [113, 108, 137, 1.481, 48, 15, 26],
+  [81, 48, 62, 1.619, 26, 3, 27],
+  [56, 48, 68, 1.336, 37, 9, 28],
+  [82, 92, 174, 3.29, 0, 13, 29],
+  [106, 116, 188, 3.628, 101, 16, 30],
+  [118, 50, 0, 1.383, 76, 11, 31]
+];
+const dataSH = [
+  [91, 45, 125, 0.82, 34, 23, 1],
+  [65, 27, 78, 0.86, 45, 29, 2],
+  [83, 60, 84, 1.09, 73, 27, 3],
+  [109, 81, 121, 1.28, 68, 51, 4],
+  [106, 77, 114, 1.07, 55, 51, 5],
+  [109, 81, 121, 1.28, 68, 51, 6],
+  [106, 77, 114, 1.07, 55, 51, 7],
+  [89, 65, 78, 0.86, 51, 26, 8],
+  [53, 33, 47, 0.64, 50, 17, 9],
+  [80, 55, 80, 1.01, 75, 24, 10],
+  [117, 81, 124, 1.03, 45, 24, 11],
+  [99, 71, 142, 1.1, 62, 42, 12],
+  [95, 69, 130, 1.28, 74, 50, 13],
+  [116, 87, 131, 1.47, 84, 40, 14],
+  [108, 80, 121, 1.3, 85, 37, 15],
+  [134, 83, 167, 1.16, 57, 43, 16],
+  [79, 43, 107, 1.05, 59, 37, 17],
+  [71, 46, 89, 0.86, 64, 25, 18],
+  [97, 71, 113, 1.17, 88, 31, 19],
+  [84, 57, 91, 0.85, 55, 31, 20],
+  [87, 63, 101, 0.9, 56, 41, 21],
+  [104, 77, 119, 1.09, 73, 48, 22],
+  [87, 62, 100, 1, 72, 28, 23],
+  [168, 128, 172, 1.49, 97, 56, 24],
+  [65, 45, 51, 0.74, 39, 17, 25],
+  [39, 24, 38, 0.61, 47, 17, 26],
+  [39, 24, 39, 0.59, 50, 19, 27],
+  [93, 68, 96, 1.05, 79, 29, 28],
+  [188, 143, 197, 1.66, 99, 51, 29],
+  [174, 131, 174, 1.55, 108, 50, 30],
+  [187, 143, 201, 1.39, 89, 53, 31]
+];
+const lineStyle = {
+  width: 1,
+  opacity: 0.5
+};
+let option = {
+  backgroundColor: '#161627',
+  title: {
+    text: 'AQI - Radar',
+    left: 'center',
+    textStyle: {
+      color: '#eee'
+    }
+  },
+  legend: {
+    bottom: 5,
+    data: ['Beijing', 'Shanghai', 'Guangzhou'],
+    itemGap: 20,
+    textStyle: {
+      color: '#fff',
+      fontSize: 14
+    },
+    selectedMode: 'single'
+  },
+  radar: {
+    indicator: [
+      { name: 'AQI', max: 300 },
+      { name: 'PM2.5', max: 250 },
+      { name: 'PM10', max: 300 },
+      { name: 'CO', max: 5 },
+      { name: 'NO2', max: 200 },
+      { name: 'SO2', max: 100 }
+    ],
+    shape: 'circle',
+    splitNumber: 5,
+    axisName: {
+      color: 'rgb(238, 197, 102)'
+    },
+    splitLine: {
+      lineStyle: {
+        color: [
+          'rgba(238, 197, 102, 0.1)',
+          'rgba(238, 197, 102, 0.2)',
+          'rgba(238, 197, 102, 0.4)',
+          'rgba(238, 197, 102, 0.6)',
+          'rgba(238, 197, 102, 0.8)',
+          'rgba(238, 197, 102, 1)'
+        ].reverse()
+      }
+    },
+    splitArea: {
+      show: false
+    },
+    axisLine: {
+      lineStyle: {
+        color: 'rgba(238, 197, 102, 0.5)'
+      }
+    }
+  },
+  series: [
+    {
+      name: 'Beijing',
+      type: 'radar',
+      lineStyle: lineStyle,
+      data: dataBJ,
+      symbol: 'none',
+      itemStyle: {
+        color: '#F9713C'
+      },
+      areaStyle: {
+        opacity: 0.1
+      }
+    },
+    {
+      name: 'Shanghai',
+      type: 'radar',
+      lineStyle: lineStyle,
+      data: dataSH,
+      symbol: 'none',
+      itemStyle: {
+        color: '#B3E4A1'
+      },
+      areaStyle: {
+        opacity: 0.05
+      }
+    },
+    {
+      name: 'Guangzhou',
+      type: 'radar',
+      lineStyle: lineStyle,
+      data: dataGZ,
+      symbol: 'none',
+      itemStyle: {
+        color: 'rgb(238, 197, 102)'
+      },
+      areaStyle: {
+        opacity: 0.05
+      }
+    }
+  ]
+};
+    
+    // 使用刚指定的配置项和数据显示图表。
+    myChart.setOption(option);
+ }
+  initBarChart(){
+    
+     // 基于准备好的dom,初始化echarts实例
+     var myChart = echarts.init(document.getElementById('main'));
+
+     // 指定图表的配置项和数据
+     var option = {
+       title: {
+         text: 'ECharts 入门示例'
+       },
+       tooltip: {},
+       legend: {
+         data: ['销量']
+       },
+       xAxis: {
+         data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
+       },
+       yAxis: {},
+       series: [
+         {
+           name: '销量',
+           type: 'bar',
+           data: [5, 20, 36, 10, 10, 20]
+         }
+       ]
+     };
+
+     // 使用刚指定的配置项和数据显示图表。
+     myChart.setOption(option);
+  }
+
+}

+ 22 - 0
src/modules/study/case-js-module/case-edit/case-edit.module.ts

@@ -0,0 +1,22 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CaseEditPageRoutingModule } from './case-edit-routing.module';
+
+import { CaseEditPage } from './case-edit.page';
+import { EditRatioStarComponent } from '../../case-angular/comps/edit-ratio-star/edit-ratio-star.component';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CaseEditPageRoutingModule,
+    EditRatioStarComponent
+  ],
+  declarations: [CaseEditPage]
+})
+export class CaseEditPageModule {}

+ 163 - 0
src/modules/study/case-js-module/case-parse-petmarket/README.md

@@ -0,0 +1,163 @@
+
+# 示例:AI编写列表页
+> 我在编写一个宠物市场的页面,前端框架使用ionic7的angular版本,数据请求使用Parse JS SDK。
+> 其中宠物的Schema名称为Pet,字段name:String名称、type:String类型,price:Number价格。
+> 请帮我实现该页面,通过卡片列表展示宠物,需要支持搜索条、宠物类型(Pet的type字段)标签切换、价格(Pet的price字段)点击排序等功能。
+
+好的,我可以帮你实现这个宠物市场页面。我们将使用Ionic 7和Angular来构建前端,并通过Parse JS SDK来处理数据请求。以下是实现这个页面的详细步骤:
+
+1. **初始化项目**
+   首先,确保你已经安装了Ionic CLI。如果没有,使用以下命令安装:
+   ```bash
+   npm install -g @ionic/cli
+   ```
+
+   然后,创建一个新的Ionic Angular项目:
+   ```bash
+   ionic start pet-market blank --type=angular
+   cd pet-market
+   ```
+
+2. **安装Parse JS SDK**
+   在项目目录下安装Parse JS SDK:
+   ```bash
+   npm install parse
+   ```
+
+3. **配置Parse**
+   在`src/environments/environment.ts`中添加Parse配置:
+   ```typescript
+   export const environment = {
+     production: false,
+     parseAppId: 'YOUR_PARSE_APP_ID',
+     parseServerUrl: 'YOUR_PARSE_SERVER_URL'
+   };
+   ```
+
+   在`src/main.ts`中初始化Parse:
+   ```typescript
+   import { environment } from './environments/environment';
+   import { Parse } from 'parse';
+
+   Parse.initialize(environment.parseAppId);
+   Parse.serverURL = environment.parseServerUrl;
+   ```
+
+4. **创建服务来处理Parse请求**
+   创建一个新的服务来处理与Parse的交互。在`src/app`目录下创建一个`services`文件夹,并在其中创建`pet.service.ts`:
+   ```typescript
+   import { Injectable } from '@angular/core';
+   import { Parse } from 'parse';
+
+   @Injectable({
+     providedIn: 'root'
+   })
+   export class PetService {
+     private Pet = Parse.Object.extend('Pet');
+
+     constructor() {}
+
+     async getPets(queryParams: any): Promise<Parse.Object[]> {
+       const query = new Parse.Query(this.Pet);
+       if (queryParams.name) {
+         query.contains('name', queryParams.name);
+       }
+       if (queryParams.type) {
+         query.equalTo('type', queryParams.type);
+       }
+       if (queryParams.sortByPrice) {
+         query.ascending('price');
+       } else {
+         query.descending('price');
+       }
+       return await query.find();
+     }
+   }
+   ```
+
+5. **创建页面组件**
+   生成页面组件:
+   ```bash
+   ionic generate page PetMarket
+   ```
+
+6. **实现页面布局和逻辑**
+   在`pet-market.page.html`中,创建搜索条、类型标签和卡片列表:
+   ```html
+   <ion-header>
+     <ion-toolbar>
+       <ion-title>Pet Market</ion-title>
+     </ion-toolbar>
+   </ion-header>
+
+   <ion-content>
+     <ion-searchbar [(ngModel)]="searchQuery" (ionInput)="fetchPets()"></ion-searchbar>
+     <ion-segment [(ngModel)]="selectedType" (ionChange)="fetchPets()">
+       <ion-segment-button *ngFor="let type of petTypes" [value]="type">
+         {{ type }}
+       </ion-segment-button>
+     </ion-segment>
+     <ion-button (click)="toggleSort()">Sort by Price</ion-button>
+     <ion-list>
+       <ion-card *ngFor="let pet of pets">
+         <ion-card-header>
+           <ion-card-title>{{ pet.get('name') }}</ion-card-title>
+         </ion-card-header>
+         <ion-card-content>
+           Type: {{ pet.get('type') }}<br>
+           Price: {{ pet.get('price') | currency }}
+         </ion-card-content>
+       </ion-card>
+     </ion-list>
+   </ion-content>
+   ```
+
+   在`pet-market.page.ts`中,添加逻辑来处理数据请求和状态管理:
+   ```typescript
+   import { Component, OnInit } from '@angular/core';
+   import { PetService } from '../services/pet.service';
+
+   @Component({
+     selector: 'app-pet-market',
+     templateUrl: './pet-market.page.html',
+     styleUrls: ['./pet-market.page.scss'],
+   })
+   export class PetMarketPage implements OnInit {
+     pets: any[] = [];
+     searchQuery: string = '';
+     selectedType: string = '';
+     petTypes: string[] = ['Dog', 'Cat', 'Bird', 'Other'];
+     sortByPrice: boolean = false;
+
+     constructor(private petService: PetService) {}
+
+     ngOnInit() {
+       this.fetchPets();
+     }
+
+     async fetchPets() {
+       const queryParams = {
+         name: this.searchQuery,
+         type: this.selectedType,
+         sortByPrice: this.sortByPrice
+       };
+       this.pets = await this.petService.getPets(queryParams);
+     }
+
+     toggleSort() {
+       this.sortByPrice = !this.sortByPrice;
+       this.fetchPets();
+     }
+   }
+   ```
+
+7. **添加样式**
+   根据需要添加样式以美化页面。在`pet-market.page.scss`中编写CSS样式。
+
+8. **运行项目**
+   最后,运行项目并查看效果:
+   ```bash
+   ionic serve
+   ```
+
+这样,你就可以在Ionic Angular项目中创建一个宠物市场页面,并通过Parse JS SDK来获取和展示宠物数据。页面支持搜索、类型标签切换和价格排序功能。

+ 17 - 0
src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { CaseParsePetmarketPage } from './case-parse-petmarket.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: CaseParsePetmarketPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class CaseParsePetmarketPageRoutingModule {}

+ 20 - 0
src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CaseParsePetmarketPageRoutingModule } from './case-parse-petmarket-routing.module';
+
+import { CaseParsePetmarketPage } from './case-parse-petmarket.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CaseParsePetmarketPageRoutingModule
+  ],
+  declarations: [CaseParsePetmarketPage]
+})
+export class CaseParsePetmarketPageModule {}

+ 31 - 0
src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.page.html

@@ -0,0 +1,31 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-title>Pet Market</ion-title>
+    <ion-buttons slot="end">
+      <ion-button routerLink="/study/pet/edit/new" >发布</ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content>
+  <ion-searchbar [(ngModel)]="searchQuery" (ionInput)="fetchPets()"></ion-searchbar>
+  <ion-segment [(ngModel)]="selectedType" (ionChange)="fetchPets()">
+    <ion-segment-button *ngFor="let type of petTypes" [value]="type">
+      {{ type }}
+    </ion-segment-button>
+  </ion-segment>
+  <ion-button (click)="toggleSort()">Sort by Price</ion-button>
+  <ion-list>
+    <ion-card *ngFor="let pet of pets">
+      <ion-card-header>
+        <ion-card-title>{{ pet.get('name') }}</ion-card-title>
+      </ion-card-header>
+      <ion-card-content>
+        Type: {{ pet.get('type') }}<br>
+        Price: {{ pet.get('price') | currency }}
+      </ion-card-content>
+      <ion-button fill="clear" routerLink="/study/pet/{{pet.id}}">详情</ion-button>
+      <ion-button *ngIf="pet?.get('user')?.id == user?.id" fill="clear" routerLink="/study/pet/edit/{{pet.id}}">编辑</ion-button>
+    </ion-card>
+  </ion-list>
+</ion-content>

+ 0 - 0
src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.page.scss


+ 17 - 0
src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.page.spec.ts

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

+ 36 - 0
src/modules/study/case-js-module/case-parse-petmarket/case-parse-petmarket.page.ts

@@ -0,0 +1,36 @@
+import { Component, OnInit } from '@angular/core';
+import { PetService } from './pet.service';
+import Parse from "parse";
+@Component({
+  selector: 'app-case-parse-petmarket',
+  templateUrl: './case-parse-petmarket.page.html',
+  styleUrls: ['./case-parse-petmarket.page.scss'],
+})
+export class CaseParsePetmarketPage implements OnInit {
+  pets: any[] = [];
+  searchQuery: string = '';
+  selectedType: string = '';
+  petTypes: string[] = ['狗', '猫', '蟑螂', '羊驼'];
+  sortByPrice: boolean = false;
+
+  constructor(private petService: PetService) {}
+  user:Parse.User|undefined
+  ngOnInit() {
+    this.user = Parse.User.current();
+    this.fetchPets();
+  }
+
+  async fetchPets() {
+    const queryParams = {
+      name: this.searchQuery,
+      type: this.selectedType,
+      sortByPrice: this.sortByPrice
+    };
+    this.pets = await this.petService.getPets(queryParams);
+  }
+
+  toggleSort() {
+    this.sortByPrice = !this.sortByPrice;
+    this.fetchPets();
+  }
+}

+ 27 - 0
src/modules/study/case-js-module/case-parse-petmarket/pet.service.ts

@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import Parse from 'parse';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class PetService {
+  private Pet = Parse.Object.extend('Pet');
+
+  constructor() {}
+
+  async getPets(queryParams: any): Promise<Parse.Object[]> {
+    const query = new Parse.Query(this.Pet);
+    if (queryParams.name) {
+      query.contains('name', queryParams.name);
+    }
+    if (queryParams.type) {
+      query.equalTo('type', queryParams.type);
+    }
+    if (queryParams.sortByPrice) {
+      query.ascending('price');
+    } else {
+      query.descending('price');
+    }
+    return await query.find();
+  }
+}

+ 49 - 0
src/modules/study/case-js-module/case-parse/README.md

@@ -0,0 +1,49 @@
+# Parse JS SDK 使用示例
+
+- 使用文档:https://docs.parseplatform.org/js/guide/
+- API手册:https://parseplatform.org/Parse-SDK-JS/api/5.2.0/
+
+# 安装依赖
+``` bash
+npm i -S parse
+npm i -D @types/parse
+```
+配置 tsconfig.json
+``` json
+  "compilerOptions": {
+    "allowSyntheticDefaultImports": true,
+  }
+```
+
+# 设置微服务地址+参数
+
+``` ts
+// 引用Parse JS SDK
+import Parse from "parse";
+Parse.initialize("dev"); // 设置applicationId
+Parse.serverURL = "http://web2023.fmode.cn:9999/parse"; // 设置serverURL
+
+```
+
+# 示例:最简单的列表加载
+
+- pet.page.html
+``` html
+<ion-card *ngFor="let pet of petList">
+    {{pet.get("name")}}
+</ion-card>
+```
+
+- pet.page.ts
+``` ts
+ngOnInit() {
+    this.loadPets()
+  }
+  
+  petList:Array<Parse.Object> = []
+  async loadPets(){
+    let query = new Parse.Query("Pet");
+    let list = await query.find();
+    this.petList = list
+  }
+```

+ 17 - 0
src/modules/study/case-js-module/case-parse/case-parse-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { CaseParsePage } from './case-parse.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: CaseParsePage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class CaseParsePageRoutingModule {}

+ 20 - 0
src/modules/study/case-js-module/case-parse/case-parse.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CaseParsePageRoutingModule } from './case-parse-routing.module';
+
+import { CaseParsePage } from './case-parse.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CaseParsePageRoutingModule
+  ],
+  declarations: [CaseParsePage]
+})
+export class CaseParsePageModule {}

+ 17 - 0
src/modules/study/case-js-module/case-parse/case-parse.page.html

@@ -0,0 +1,17 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>Parse JS SDK功能演示</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-header collapse="condense">
+    <ion-toolbar>
+      <ion-title size="large">Parse JS SDK功能演示</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+  <ion-card *ngFor="let pet of petList" routerLink="/study/pet/{{pet.id}}">
+    {{pet.get("name")}}
+  </ion-card>
+</ion-content>

+ 0 - 0
src/modules/study/case-js-module/case-parse/case-parse.page.scss


+ 17 - 0
src/modules/study/case-js-module/case-parse/case-parse.page.spec.ts

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

+ 28 - 0
src/modules/study/case-js-module/case-parse/case-parse.page.ts

@@ -0,0 +1,28 @@
+import { Component, OnInit } from '@angular/core';
+
+// 引用Parse JS SDK
+import Parse from "parse";
+Parse.initialize("dev"); // 设置applicationId
+Parse.serverURL = "http://web2023.fmode.cn:9999/parse"; // 设置serverURL
+
+@Component({
+  selector: 'app-case-parse',
+  templateUrl: './case-parse.page.html',
+  styleUrls: ['./case-parse.page.scss'],
+})
+export class CaseParsePage implements OnInit {
+
+  constructor() { }
+
+  ngOnInit() {
+    this.loadPets()
+  }
+  
+  petList:Array<Parse.Object> = []
+  async loadPets(){
+    let query = new Parse.Query("Pet");
+    let list = await query.find();
+    this.petList = list
+  }
+
+}

+ 71 - 0
src/modules/study/case-js-module/case-pet-detail/README.md

@@ -0,0 +1,71 @@
+
+# 详情页制作 + 路由跳转 + 路由传参 + 根据ID加载
+
+# 页面创建和路由配置
+
+``` bash
+ionic g page case-pet-detail
+```
+
+- 修改app-routing.module.ts
+``` ts
+  {
+        path: 'pet/:id',
+        loadChildren: () => import('../modules/study/case-js-module/case-pet-detail/case-pet-detail.module').then( m => m.CasePetDetailPageModule)
+      },
+```
+
+# 路由参数id的获取
+
+- case-pet-detail.page.ts
+``` ts
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import Parse from "parse";
+
+@Component({
+  selector: 'app-case-pet-detail',
+  templateUrl: './case-pet-detail.page.html',
+  styleUrls: ['./case-pet-detail.page.scss'],
+})
+export class CasePetDetailPage implements OnInit {
+
+  constructor(private route:ActivatedRoute) { }
+
+  ngOnInit() {
+    this.loadPetById()
+  }
+
+  pet:Parse.Object|undefined
+  async loadPetById(){
+    // let id = location.pathname.split("/").pop();
+    let id = this.route.snapshot.params["id"]
+
+    if(id){
+      let query = new Parse.Query("Pet");
+      this.pet = await query.get(id);
+    }
+  }
+
+}
+```
+
+# 根据ID获取数据的渲染
+> 注意:Parse.Object类型的数据,需要用.get获取内部属性,?.get避免空对象的情况
+- case-pet-detail.page.html
+``` html
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>{{pet?.get("name")}}</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+
+  <ion-card>
+    {{pet?.get("type")}}
+    {{pet?.get("price")}}
+  </ion-card>
+</ion-content>
+
+```

+ 17 - 0
src/modules/study/case-js-module/case-pet-detail/case-pet-detail-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { CasePetDetailPage } from './case-pet-detail.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: CasePetDetailPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class CasePetDetailPageRoutingModule {}

+ 20 - 0
src/modules/study/case-js-module/case-pet-detail/case-pet-detail.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CasePetDetailPageRoutingModule } from './case-pet-detail-routing.module';
+
+import { CasePetDetailPage } from './case-pet-detail.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CasePetDetailPageRoutingModule
+  ],
+  declarations: [CasePetDetailPage]
+})
+export class CasePetDetailPageModule {}

+ 13 - 0
src/modules/study/case-js-module/case-pet-detail/case-pet-detail.page.html

@@ -0,0 +1,13 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>{{pet?.get("name")}}</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+
+  <ion-card>
+    {{pet?.get("type")}}
+    {{pet?.get("price")}}
+  </ion-card>
+</ion-content>

+ 0 - 0
src/modules/study/case-js-module/case-pet-detail/case-pet-detail.page.scss


+ 17 - 0
src/modules/study/case-js-module/case-pet-detail/case-pet-detail.page.spec.ts

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

+ 29 - 0
src/modules/study/case-js-module/case-pet-detail/case-pet-detail.page.ts

@@ -0,0 +1,29 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import Parse from "parse";
+
+@Component({
+  selector: 'app-case-pet-detail',
+  templateUrl: './case-pet-detail.page.html',
+  styleUrls: ['./case-pet-detail.page.scss'],
+})
+export class CasePetDetailPage implements OnInit {
+
+  constructor(private route:ActivatedRoute) { }
+
+  ngOnInit() {
+    this.loadPetById()
+  }
+
+  pet:Parse.Object|undefined
+  async loadPetById(){
+    // let id = location.pathname.split("/").pop();
+    let id = this.route.snapshot.params["id"]
+
+    if(id){
+      let query = new Parse.Query("Pet");
+      this.pet = await query.get(id);
+    }
+  }
+
+}

+ 17 - 0
src/modules/study/case-js-module/case-pet-edit/case-pet-edit-routing.module.ts

@@ -0,0 +1,17 @@
+import { NgModule } from '@angular/core';
+import { Routes, RouterModule } from '@angular/router';
+
+import { CasePetEditPage } from './case-pet-edit.page';
+
+const routes: Routes = [
+  {
+    path: '',
+    component: CasePetEditPage
+  }
+];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+})
+export class CasePetEditPageRoutingModule {}

+ 20 - 0
src/modules/study/case-js-module/case-pet-edit/case-pet-edit.module.ts

@@ -0,0 +1,20 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+
+import { IonicModule } from '@ionic/angular';
+
+import { CasePetEditPageRoutingModule } from './case-pet-edit-routing.module';
+
+import { CasePetEditPage } from './case-pet-edit.page';
+
+@NgModule({
+  imports: [
+    CommonModule,
+    FormsModule,
+    IonicModule,
+    CasePetEditPageRoutingModule
+  ],
+  declarations: [CasePetEditPage]
+})
+export class CasePetEditPageModule {}

+ 23 - 0
src/modules/study/case-js-module/case-pet-edit/case-pet-edit.page.html

@@ -0,0 +1,23 @@
+<ion-header [translucent]="true">
+  <ion-toolbar>
+    <ion-title>case-pet-edit</ion-title>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content [fullscreen]="true">
+  <ion-list>
+    <ion-item>
+      <ion-input label="昵称" type="text" [(ngModel)]="petInfo.name"></ion-input>
+    </ion-item>
+    <ion-item>
+      <!-- <ion-label>种类</ion-label> -->
+      <ion-input label="种类" type="text" [(ngModel)]="petInfo.type"></ion-input>
+    </ion-item>
+    <ion-item>
+      <ion-input label="价格" type="number" [(ngModel)]="petInfo.price"></ion-input>
+    </ion-item>
+  </ion-list>
+
+  <ion-button expand="block" (click)="save()">保存</ion-button>
+  <ion-button expand="block" color="light" (click)="cancel()">取消</ion-button>
+</ion-content>

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä