Browse Source

完成生成选项的UI设计、简易的推荐算法,优化AI提示词

s202226701053 6 months ago
parent
commit
10b0cf8808
51 changed files with 901 additions and 86 deletions
  1. 9 1
      E-Cover-app/package-lock.json
  2. 2 0
      E-Cover-app/package.json
  3. 11 3
      E-Cover-app/src/agent/tasks/generate/generate-picture.ts
  4. 14 11
      E-Cover-app/src/agent/tasks/generate/generate-prompt.ts
  5. 1 1
      E-Cover-app/src/app/app.routes.ts
  6. 69 7
      E-Cover-app/src/app/generate-option/generate-option.component.html
  7. 66 7
      E-Cover-app/src/app/generate-option/generate-option.component.scss
  8. 84 28
      E-Cover-app/src/app/generate-option/generate-option.component.ts
  9. 32 4
      E-Cover-app/src/app/tab1/tab1.page.html
  10. 14 1
      E-Cover-app/src/app/tab1/tab1.page.scss
  11. 92 6
      E-Cover-app/src/app/tab1/tab1.page.ts
  12. 0 2
      E-Cover-app/src/app/tab2/tab2.page.html
  13. 6 3
      E-Cover-app/src/app/tab2/tab2.page.ts
  14. 1 1
      E-Cover-app/src/app/tab3/tab3.page.html
  15. 4 1
      E-Cover-app/src/app/tab3/tab3.page.ts
  16. BIN
      E-Cover-app/src/assets/fonts/title-font.TTF
  17. BIN
      E-Cover-app/src/assets/generate-option-style/areaStyle1.jpg
  18. BIN
      E-Cover-app/src/assets/generate-option-style/areaStyle2.jpg
  19. BIN
      E-Cover-app/src/assets/generate-option-style/areaStyle3.jpg
  20. BIN
      E-Cover-app/src/assets/generate-option-style/areaStyle4.jpg
  21. BIN
      E-Cover-app/src/assets/generate-option-style/areaStyle5.jpg
  22. BIN
      E-Cover-app/src/assets/generate-option-style/areaStyle6.jpg
  23. BIN
      E-Cover-app/src/assets/generate-option-style/areaStyle7.jpg
  24. BIN
      E-Cover-app/src/assets/generate-option-style/artStyle1.jpg
  25. BIN
      E-Cover-app/src/assets/generate-option-style/artStyle2.jpg
  26. BIN
      E-Cover-app/src/assets/generate-option-style/artStyle3.jpg
  27. BIN
      E-Cover-app/src/assets/generate-option-style/artStyle4.jpg
  28. BIN
      E-Cover-app/src/assets/generate-option-style/artStyle5.jpg
  29. BIN
      E-Cover-app/src/assets/generate-option-style/artStyle6.jpg
  30. BIN
      E-Cover-app/src/assets/generate-option-style/checked.png
  31. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea1.jpg
  32. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea2.jpg
  33. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea3.jpg
  34. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea4.jpg
  35. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea5.jpg
  36. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea6.jpg
  37. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea7.jpg
  38. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea8.jpg
  39. BIN
      E-Cover-app/src/assets/generate-option-style/designIdea9.jpg
  40. BIN
      E-Cover-app/src/assets/generate-option-style/function1.jpg
  41. BIN
      E-Cover-app/src/assets/generate-option-style/function2.jpg
  42. BIN
      E-Cover-app/src/assets/generate-option-style/function3.jpg
  43. BIN
      E-Cover-app/src/assets/generate-option-style/function4.jpg
  44. BIN
      E-Cover-app/src/assets/generate-option-style/function5.jpg
  45. BIN
      E-Cover-app/src/assets/generate-option-style/function6.jpg
  46. 67 0
      E-Cover-app/src/lib/component/post-detail/post-detail.component.html
  47. 231 0
      E-Cover-app/src/lib/component/post-detail/post-detail.component.scss
  48. 54 5
      E-Cover-app/src/lib/component/post-detail/post-detail.component.ts
  49. 2 2
      E-Cover-app/src/lib/component/post-list/post-list.component.html
  50. 2 3
      E-Cover-app/src/lib/component/post-list/post-list.component.ts
  51. 140 0
      E-Cover-app/src/lib/service/recommend.ts

+ 9 - 1
E-Cover-app/package-lock.json

@@ -24,6 +24,7 @@
         "@ionic/angular": "^8.0.0",
         "fmode-ng": "^0.0.63",
         "ionicons": "^7.2.1",
+        "lodash": "^4.17.21",
         "rxjs": "~7.8.0",
         "swiper": "^11.1.15",
         "tslib": "^2.3.0",
@@ -42,6 +43,7 @@
         "@capacitor/cli": "6.2.0",
         "@ionic/angular-toolkit": "^11.0.1",
         "@types/jasmine": "~5.1.0",
+        "@types/lodash": "^4.17.13",
         "@typescript-eslint/eslint-plugin": "^6.0.0",
         "@typescript-eslint/parser": "^6.0.0",
         "eslint": "^8.57.0",
@@ -5987,6 +5989,13 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/@types/lodash": {
+      "version": "4.17.13",
+      "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.13.tgz",
+      "integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/mime": {
       "version": "1.3.5",
       "resolved": "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz",
@@ -13023,7 +13032,6 @@
       "version": "4.17.21",
       "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.debounce": {

+ 2 - 0
E-Cover-app/package.json

@@ -29,6 +29,7 @@
     "@ionic/angular": "^8.0.0",
     "fmode-ng": "^0.0.63",
     "ionicons": "^7.2.1",
+    "lodash": "^4.17.21",
     "rxjs": "~7.8.0",
     "swiper": "^11.1.15",
     "tslib": "^2.3.0",
@@ -47,6 +48,7 @@
     "@capacitor/cli": "6.2.0",
     "@ionic/angular-toolkit": "^11.0.1",
     "@types/jasmine": "~5.1.0",
+    "@types/lodash": "^4.17.13",
     "@typescript-eslint/eslint-plugin": "^6.0.0",
     "@typescript-eslint/parser": "^6.0.0",
     "eslint": "^8.57.0",

+ 11 - 3
E-Cover-app/src/agent/tasks/generate/generate-picture.ts

@@ -19,12 +19,20 @@ export function TaskGeneratePicture(options: {
             let imagineWork = new ImagineWork();
             // 图片生成
             const designPrompt = JSON.stringify(options.shareData.designPrompt);
-            let PicturePrompt = `您是一名专业的服装画师,请根据客户的身高、体重等数据,精准地画出穿搭效果图。
-                以下是用户的数据:${designPrompt}`
+            let PicturePrompt = `您是一名专业的模特摄影师,请根据用户数据,精准地拍出穿搭效果图。
+                以下是用户的数据:${designPrompt}
+                有一些注意事项是你需要注意的:
+                1.图片的规格为:高 (1024x1792)应该是垂直方向的,请尽量保持这个比例。图片内容不要超过范围。
+                2.客户模特一定要用正确的年龄段,不要给你的是20岁,你用30多岁的人当模特,这是不行的。
+                3.客户的自定义描述极度重要,请务必按照客户的描述来拍摄。
+                4.客户模特一定要用正确的体型。比如客户是80kg,你找个瘦子当模特,这是不行的,一定是体型肥胖的。
+                5.照片结构整齐一些,不要有太多凌乱的感觉。左半边只需要一张整体预览,右半边找画师画出schemeList中服装的细节图。
+                6.客户模特脸部需要遮挡,不要露出脸部。
+                7.照片和画师画的细节图,一定都要分布在正方形的画布上,不要超出范围。`
             console.log(PicturePrompt);
             let imgOptions: DalleOptions = { prompt: PicturePrompt }
             let countDownInt = setInterval(() => {
-                task3.progress += 0.01
+                task3.progress += 0.03
             }, 1000)
             imagineWork?.draw(imgOptions).subscribe((work: any) => {
                 console.log("imagineWork", work?.toJSON())

+ 14 - 11
E-Cover-app/src/agent/tasks/generate/generate-prompt.ts

@@ -19,32 +19,35 @@ export function TaskGeneratePrompt(options: {
             客户的要求如下:${userProfileJson}
             结果以JSON格式表示:(以下是JSON格式的参考方案)
             {
-                "schemeName":"方案名(例如:夏日小清新)",
-                "gender":"性别",
-                "age":"年龄",
+                "方案名":"方案名(例如:夏日小清新)",
+                "性别":"性别",
+                "年龄":"年龄",
                 ......
-                (传入的json中有什么属性就补上什么)
+                (传入的json中有什么属性就翻译一下,补上什么)
                 "schemeList":{
                     "上衣":["name":"上衣特征描述(简洁,例如:轻薄短袖衬衫)","desc":"穿搭思路描述(具体,例如:选择一款轻薄透气的短袖衬衫,颜色可以是浅蓝色或淡灰色,搭配细条纹或小格纹图案,以增加层次感。衬衫的剪裁要稍微宽松,以便于活动,适合办公室环境。)"],
                     "下装":["name":"下装特征描述","desc":"穿搭思路描述"],
                     ......
-                    (身上所有的衣服从里到外全部写出来加入schemeList中,例如冬季穿衣较多,从里到外有内衬衣、毛衣、夹克、棉袄、外套等。若部分有就将其部分写出来。
-                    还有不要写笼统的“上衣”“下装”“配饰”等词条,要写例如“裤子”“棉毛衫”“项链”“围巾”等具体的服装类型的词条)
                 }
             }
+            1.设计思路一定要有理有逻辑,并且要详细。
+            2.身上所有的衣服从里到外全部写出来加入schemeList中,例如冬季穿衣较多,从里到外有内衬衣、毛衣、夹克、棉袄、外套等。
+            这部分你能想得到的都要写出来。但是不要完全不符合逻辑。例如又穿了外套,外套里面又穿了外套,这种不符合逻辑的就不要写。
+            3.不要写笼统的“上衣”“下装”“配饰”等这种广泛的分类词条,要写例如“裤子”“棉毛衫”“项链”“围巾”等具体的服装类型的词条。
+            4.一定要注重用户的customDesc自定义描述,他们的意见极度重要,一定要体现出来。
             `;
             let completion = new FmodeChatCompletion([
                 { role: "system", content: "" },
                 { role: "user", content: PromptTemplate }
             ])
             completion.sendCompletion().subscribe((message: any) => {
-                if (task2.progress < 0.5) {
-                    task2.progress += 0.1
+                if (task2.progress < 0.3) {
+                    task2.progress += 0.003
                 }
-                if (task2.progress >= 0.5 && task2.progress <= 0.9) {
-                    task2.progress += 0.01
+                if (task2.progress >= 0.3 && task2.progress <= 0.8) {
+                    task2.progress += 0.002
                 }
-                if (task2.progress >= 0.9) {
+                if (task2.progress >= 0.8) {
                     task2.progress += 0.001
                 }
                 // 打印消息

+ 1 - 1
E-Cover-app/src/app/app.routes.ts

@@ -37,7 +37,7 @@ export const routes: Routes = [
       import('./send-post/send-post.component').then((m)=>m.SendPostComponent),
   },
   {
-    path:'postDetail',
+    path:'postDetail/:postId',
     loadComponent:()=>
       import('../lib/component/post-detail/post-detail.component').then((m)=>m.PostDetailComponent),
   },

+ 69 - 7
E-Cover-app/src/app/generate-option/generate-option.component.html

@@ -40,14 +40,76 @@
   </div>
   <!--可供用户选择的提示词,默认隐藏,通过点击风格输入框中的helper-text展开-->
   <div id="option-prompt">
-    <!--区域风格,包含四个卡片-->
-    <div id="areaStyle" class="scroll-x">
-      <ion-card *ngFor="let i of [0,1,2,3,4,5,6]" (click)="toggleActive(areaStyle,i,'areaStyle')">
-        <ion-img [src]="getImageSrc(areaStyle,i,'areaStyle' + (i+1))"></ion-img>
-      </ion-card>
-      <p class="title"> "areaStyleTitle" </p>
-      <p class="keyword"> areaStyleKeyword </p>
+    <!--区域风格,包含七个卡片-->
+    <div id="areaStyle" class="scroll-container">
+      <div class="scroll-x">
+        <ion-card *ngFor="let i of [0,1,2,3,4,5,6];" (click)="toggleActive(areaStyle,i,'areaStyle')">
+          <ion-img [src]="getImageSrc(areaStyle,i,'areaStyle' + (i+1))"></ion-img>
+          @if(areaStyle[getKeyByIndex(areaStyle,i)]) {
+          <ion-img class="checked" src="/assets/generate-option-style/checked.png"></ion-img>}
+        </ion-card>
+      </div>
+      @if(getSelectedKey(areaStyle)!="null") {
+      <p class="title"> {{getSelectedKey(areaStyle)}} </p>
+      <p class="keyword">{{description[getSelectedKey(areaStyle)]}}</p>
+      }
+      @if(getSelectedKey(areaStyle) == "null") {
+      <p class="title"> 请选择区域风格 </p>
+      }
     </div>
+    <!--功能场景,包含七个卡片-->
+    <div id="function" class="scroll-container">
+      <div class="scroll-x">
+        <ion-card *ngFor="let i of [0,1,2,3,4,5];" (click)="toggleActive(function,i,'function')">
+          <ion-img [src]="getImageSrc(function,i,'function' + (i+1))"></ion-img>
+          @if(function[getKeyByIndex(function,i)]) {
+          <ion-img class="checked" src="/assets/generate-option-style/checked.png"></ion-img>}
+        </ion-card>
+      </div>
+      @if(getSelectedKey(function)!="null") {
+      <p class="title"> {{getSelectedKey(function)}} </p>
+      <p class="keyword">{{description[getSelectedKey(function)]}}</p>
+      }
+      @if(getSelectedKey(function) == "null") {
+      <p class="title"> 请选择功能场景 </p>
+      }
+    </div>
+    <!--设计理念,包含九个卡片-->
+    <div id="designIdea" class="scroll-container">
+      <div class="scroll-x">
+        <ion-card *ngFor="let i of [0,1,2,3,4,5,6,7,8];" (click)="toggleActive(designIdea,i,'designIdea')">
+          <ion-img [src]="getImageSrc(designIdea,i,'designIdea' + (i+1))"></ion-img>
+          @if(designIdea[getKeyByIndex(designIdea,i)]) {
+          <ion-img class="checked" src="/assets/generate-option-style/checked.png"></ion-img>}
+        </ion-card>
+      </div>
+      @if(getSelectedKey(designIdea)!="null") {
+      <p class="title"> {{getSelectedKey(designIdea)}} </p>
+      <p class="keyword">{{description[getSelectedKey(designIdea)]}}</p>
+      }
+      @if(getSelectedKey(designIdea) == "null") {
+      <p class="title"> 请选择设计理念 </p>
+      }
+    </div>
+    <!--艺术风格,包含六个卡片-->
+    <div id="artStyle" class="scroll-container">
+      <div class="scroll-x">
+        <ion-card *ngFor="let i of [0,1,2,3,4,5];" (click)="toggleActive(artStyle,i,'artStyle')">
+          <ion-img [src]="getImageSrc(artStyle,i,'artStyle' + (i+1))"></ion-img>
+          @if(artStyle[getKeyByIndex(artStyle,i)]) {
+          <ion-img class="checked" src="/assets/generate-option-style/checked.png"></ion-img>}
+        </ion-card>
+      </div>
+      @if(getSelectedKey(artStyle)!="null") {
+      <p class="title"> {{getSelectedKey(artStyle)}} </p>
+      <p class="keyword">{{description[getSelectedKey(artStyle)]}}</p>
+      }
+      @if(getSelectedKey(artStyle) == "null") {
+      <p class="title"> 请选择艺术风格 </p>
+      }
+    </div>
+
+
   </div>
 
   <ion-button (click)="sendMsgAndGoGenerateResult()">生成</ion-button>

+ 66 - 7
E-Cover-app/src/app/generate-option/generate-option.component.scss

@@ -146,23 +146,55 @@ ion-content {
     color: #999;
   }
 
-  //隐藏其他可选选项的样式
+  //以下为其他可选选项的样式
   #option-prompt {
     display: none;
   }
 
+  .scroll-container {
+    width: 95%;
+    margin: 15px auto;
+    padding: 10px;
+    border-radius: 15px;
+  }
+
   .scroll-x {
-    display: flex;
-    /* 使用 Flexbox 来让子元素横向排列 */
     overflow-x: auto;
-    /* 启用横向滚动 */
+    scrollbar-width: none;
     white-space: nowrap;
-    /* 防止换行 */
-    padding-bottom: 10px;
-    /* 可选:调整滚动区域底部的间距 */
+  }
+
+  .scroll-x ion-card {
+    height: 150px;
+    width: 150px;
+    border-radius: 20px;
+    display: inline-block;
+
+    ion-img {
+      width: 100%;
+      height: 100%;
+    }
   }
 
   //以下为区域风格选择框的样式
+  #areaStyle {
+    background-color: #affffc;
+  }
+
+  //以下为场景功能选择框的样式
+  #function {
+    background-color: #ffb8fd;
+  }
+
+  //以下为设计理念选择框的样式
+  #designIdea {
+    background-color: #f3ffb8;
+  }
+
+  //以下为艺术风格选择框的样式
+  #artStyle {
+    background-color: #ffdab8;
+  }
 }
 
 #container {
@@ -184,4 +216,31 @@ ion-content {
   place-items: center;
   color: black;
   border-radius: 15px;
+}
+
+.checked {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  z-index: 20;
+  top: 0;
+}
+
+.title {
+  @font-face {
+    font-family: 'title-font';
+    src: url(/assets/fonts/title-font.TTF);
+  }
+
+  font-family: 'title-font';
+  font-size: larger;
+  margin:0;
+  text-align: center;
+}
+
+.keyword {
+  font-family: 'handWritten';
+  font-size: larger;
+  text-align: center;
+  margin: 5px;
 }

+ 84 - 28
E-Cover-app/src/app/generate-option/generate-option.component.ts

@@ -18,7 +18,7 @@ addIcons({ 'arrow-back-outline': arrowBackOutline, radioButtonOffOutline, closeC
   templateUrl: './generate-option.component.html',
   styleUrls: ['./generate-option.component.scss'],
   standalone: true,
-  imports: [CustomHeaderComponent,IonicModule, CommonModule, FormsModule],
+  imports: [CustomHeaderComponent, IonicModule, CommonModule, FormsModule],
 })
 export class GenerateOptionComponent implements OnInit {
   /**
@@ -34,6 +34,39 @@ export class GenerateOptionComponent implements OnInit {
   goBack() {
     this.navCtrl.back();
   }
+  /**
+   * @常量定义
+   */
+  description: { [key: string]: string } = {
+    '中国风': "汉服/唐装/中式元素/古典美",
+    '日系': "棉麻/清新/柔和/文艺/舒适/盐系/森女系",
+    '韩系': "色彩淡雅/感性/温柔/气质/甜美",
+    '欧美风': "Athflow风/随性/简单/街头/帅气/张扬/性感/中性",
+    '英伦风': "学院派/复古猎装/绅士格子/复古/低饱和度",
+    '法式': "优雅/浪漫/知性/碎花/慵懒",
+    '波西米亚风': "层层叠叠的花边/细绳结/皮质的流苏/纷乱的珠串装饰",
+    '通勤风': "职业装/OL风/时髦知识分子风/简约通勤",
+    '休闲风': "时尚/街头/日常/慵懒休闲风",
+    '田园风': "轻松/舒适/棉麻/碎花/蕾丝",
+    '校园风': "美式校园/英伦校园/日式JK",
+    '约会装': "御姐/软妹/辣妹/仙女/淑女",
+    '度假风': "轻盈/舒适/清爽/自然/休闲",
+    '新中式': "传统中式元素融合现代设计/混搭/中式美学元素/更日常",
+    '淑女风': "知性/优雅/简洁大方/现代都市",
+    '名媛风': "高贵/典雅/精致/凸显气质",
+    '简约风': "艺术/至简纯粹/质感/高级感",
+    '极简风': "老钱风/CleanFit风/静奢风简约/大气/中性",
+    '中性风': "无性别主义/中性色调",
+    '民族风': "民族元素/手工艺/色彩鲜艳",
+    '复古风': "随性/文艺/复古/浊色调",
+    '嘻哈风': "街头文化/运动/奢华时尚",
+    '哥特风格': "暗黑/锐角三角形/金属件",
+    '浪漫主义': "柔美/荷叶边/透明",
+    '洛可可风格': "C型漩涡花纹/胸衣/木耳边/花边",
+    '洛丽塔风格': "可爱/甜美/蕾丝/褶边",
+    '维多利亚风': "蕾丝/细砂/荷叶边/缎带蝴蝶结/多层次/褶皱/抽褶/羊腿袖",
+    '未来主义': "金属色/现代几何/科技感",
+  };
   /**
    * @选项卡定义变量
   */
@@ -45,11 +78,10 @@ export class GenerateOptionComponent implements OnInit {
     weight: '不限制',
     season: '不限制',
     customDesc: '',
-    regStyle: '不限制',
-    sceFunction: '不限制',
-    dsgPhilosophy: '不限制',
+    areaStyle: '不限制',
+    function: '不限制',
+    designIdea: '不限制',
     artStyle: '不限制',
-    color: '不限制',
   };
   //性别选项卡定义变量
   gender: { [key: string]: boolean } = {
@@ -102,7 +134,6 @@ export class GenerateOptionComponent implements OnInit {
     '休闲风': false,
     '田园风': false,
     '校园风': false,
-    'Party风': false,
     '约会装': false,
     '度假风': false
   };
@@ -115,11 +146,8 @@ export class GenerateOptionComponent implements OnInit {
     '极简风': false,
     '中性风': false,
     '民族风': false,
-    '戏剧风': false,
     '复古风': false,
-    'Y2K': false,
     '嘻哈风': false,
-    '甜酷风': false
   }
   //艺术风格选项卡定义变量
   artStyle: { [key: string]: boolean } = {
@@ -136,6 +164,24 @@ export class GenerateOptionComponent implements OnInit {
     '美拉德穿搭': false
   };
 
+  /**
+   * @根据索引找到键值
+   */
+  getKeyByIndex(option: { [key: string]: boolean }, index: number): string {
+    const keys = Object.keys(option); // 获取 option 对象中的所有键
+    return keys[index]; // 根据索引找到选中的键
+  }
+  /**
+   * @返回字典中true值对应的key
+   */
+  getSelectedKey(option: { [key: string]: boolean }): string {
+    for (const key in option) {
+      if (option[key]) {
+        return key;
+      }
+    }
+    return "null"; // 如果没有找到选中的键,则返回null
+  }
   /**
    * @切换选项卡
    * 1.option:字典对象
@@ -143,15 +189,19 @@ export class GenerateOptionComponent implements OnInit {
    * 3.name:字典变量名
    */
   toggleActive(option: { [key: string]: boolean }, index: number, name: string) {
-    for (const optkey in option) {
-      option[optkey] = false;
+    const selectedKey = this.getKeyByIndex(option, index); // 根据索引找到选中的键
+    if (!option[selectedKey]) {
+      for (const optkey in option) {
+        option[optkey] = false;
+      }
+      // 将选中的键对应的值设置为 true
+      option[selectedKey] = true;
+      // 更新 userProfile 中的对应字段
+      this.userProfile[name as keyof UserProfile] = selectedKey;
+    }else{
+      option[selectedKey] = false;
+      this.userProfile[name as keyof UserProfile] = '不限制';
     }
-    const keys = Object.keys(option); // 获取 option 对象中的所有键
-    const selectedKey = keys[index]; // 根据索引找到选中的键
-    // 将选中的键对应的值设置为 true
-    option[selectedKey] = true;
-    // 更新 userProfile 中的对应字段
-    this.userProfile[name as keyof UserProfile] = selectedKey;
     console.log(this.userProfile);
   }
   /**
@@ -163,7 +213,14 @@ export class GenerateOptionComponent implements OnInit {
   getImageSrc(option: { [key: string]: boolean }, index: number, String: string): string {
     const keys = Object.keys(option); // 获取 option 对象中的所有键
     const selectedKey = keys[index]; // 根据索引找到选中的键
+    if (String.includes('areaStyle') ||
+      String.includes('function') ||
+      String.includes('designIdea') ||
+      String.includes('artStyle')) {
+      return '/assets/generate-option-style/' + String + '.jpg';
+    }
     const isActive = option[selectedKey];
+    // return isActive ? 'https://app.fmode.cn/dev/jxnu/202226701053/assets/generate-option-style/' + String + '-isActive-true.png' : 'https://app.fmode.cn/dev/jxnu/202226701053/assets/generate-option-style/' + String + '-isActive-false.png';
     return isActive ? '/assets/generate-option-style/' + String + '-isActive-true.png' : '/assets/generate-option-style/' + String + '-isActive-false.png';
   }
 
@@ -254,18 +311,18 @@ export class GenerateOptionComponent implements OnInit {
    * 3.调用doGenerateTask方法
    */
   sendMsgAndGoGenerateResult() {
-    if(this.validate()){
-    this.showJudgePage();
-    console.log("展示当前读取到的用户信息:");
-    console.log(this.userProfile);
-    this.doGenerateTask();
+    if (this.validate()) {
+      this.showJudgePage();
+      console.log("展示当前读取到的用户信息:");
+      console.log(this.userProfile);
+      this.doGenerateTask();
     }
   }
 
-  validate(){
+  validate() {
     console.log("开始验证用户信息");
     let currentUser = new CloudUser();
-    if(!currentUser?.id){
+    if (!currentUser?.id) {
       console.log("用户未登录,请先登录");
       //openUserLoginModal(this.modalCtrl as any);
       return false;
@@ -284,9 +341,8 @@ type UserProfile = {
   weight: string;
   season: string;
   customDesc: string;
-  regStyle: string;
-  sceFunction: string;
-  dsgPhilosophy: string;
+  areaStyle: string;
+  function: string;
+  designIdea: string;
   artStyle: string;
-  color: string;
 };

+ 32 - 4
E-Cover-app/src/app/tab1/tab1.page.html

@@ -1,14 +1,17 @@
 <!--页头-->
 <ion-header collapse="fade" mode="ios">
     <ion-toolbar>
-        <ion-button slot="start">南昌
-            <ion-icon slot="end" name="caret-down-outline"></ion-icon>
+        <ion-button slot="start"><p id="location">南昌</p>
+            <ion-icon id="icon" slot="end" name="caret-down-outline"></ion-icon>
         </ion-button>
         <ion-searchbar></ion-searchbar>
     </ion-toolbar>
 </ion-header>
 <!--内容-->
-<ion-content [fullscreen]="true">
+<ion-content [fullscreen]="true" [scrollEvents]="true" (ionScroll)="handleScroll($any($event))">
+    <ion-refresher slot="fixed" [pullFactor]="0.5" [pullMin]="100" [pullMax]="200" (ionRefresh)="handleRefresh($event)">
+        <ion-refresher-content></ion-refresher-content>
+    </ion-refresher>
     <!--主页宣传轮播图-->
     <div id="banner">
         <swiper-container autoplay-delay="2000" loop="true">
@@ -72,6 +75,31 @@
                 <img *ngFor="let item of items" [src]="item?.get('image')" />
             </div>
         </ion-segment-content>
-        <ion-segment-content id="second">Second</ion-segment-content>
+        <ion-segment-content id="second">
+            <div class="segment-content">
+                <img *ngFor="let item of recommendItems" [src]="item?.get('ItemID')?.image" />
+            </div>
+        </ion-segment-content>
+    </ion-segment-view>
+    <!--第二个段设置-->
+    <div style="display: flex;justify-content: space-between;width: 90%;margin:0 5%;">
+        <ion-segment>
+            <ion-segment-button value="like" content-id="like">
+                <ion-label>猜你喜欢</ion-label>
+            </ion-segment-button>
+        </ion-segment>
+
+        <ion-button class="all" shape="round">
+            <ion-text>全部</ion-text>
+            <ion-icon slot="end" name="chevron-forward-outline"></ion-icon>
+        </ion-button>
+    </div>
+    <!--第二个段展示-->
+    <ion-segment-view>
+        <ion-segment-content id="like">
+            <div class="segment-content">
+                <img *ngFor="let item of alreadyItems" [src]="item?.get('ItemID')?.image" (click)="recommend(item)" />
+            </div>
+        </ion-segment-content>
     </ion-segment-view>
 </ion-content>

+ 14 - 1
E-Cover-app/src/app/tab1/tab1.page.scss

@@ -6,6 +6,15 @@ ion-header {
     ion-button {
         --background: none;
         --border-radius: 1000px;
+        
+        #location{
+            padding: 0;
+            margin: 0;
+            color: white;
+        }
+        #icon{
+            color: white;
+        }
     }
 
     ion-searchbar {
@@ -82,6 +91,10 @@ ion-card {
 ion-content {
     --background: #f8f8f8;
     --color: #000;
+
+    ion-segment-view{
+        height: auto;
+    }
 }
 
 ion-segment {
@@ -121,7 +134,7 @@ ion-segment {
     --box-shadow: none;
 }
 
-#first {
+#first,#like {
     width: 100vw;
 }
 

+ 92 - 6
E-Cover-app/src/app/tab1/tab1.page.ts

@@ -1,12 +1,13 @@
-import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { Component, CUSTOM_ELEMENTS_SCHEMA, NgZone } from '@angular/core';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
 import { addIcons } from 'ionicons';
 import { caretDownOutline, chevronForwardOutline } from 'ionicons/icons';
 import { Router } from '@angular/router';
 import { register } from 'swiper/element/bundle';
-import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { CloudObject, CloudQuery, CloudUser } from 'src/lib/ncloud';
 import { CommonModule } from '@angular/common';
-import { IonButton, IonCard, IonContent, IonImg, IonLabel, IonSearchbar, IonSegment, IonSegmentButton, IonSegmentContent, IonSegmentView, IonText } from '@ionic/angular/standalone';
+import { IonButton, IonCard, IonContent, IonImg, IonLabel, IonRefresher, IonSearchbar, IonSegment, IonSegmentButton, IonSegmentContent, IonSegmentView, IonText, ScrollDetail } from '@ionic/angular/standalone';
+import { RecommendationService } from 'src/lib/service/recommend';
 addIcons({ caretDownOutline, chevronForwardOutline });
 register();
 @Component({
@@ -15,17 +16,44 @@ register();
   styleUrls: ['tab1.page.scss'],
   standalone: true,
   imports: [ExploreContainerComponent, CommonModule, IonButton, IonImg, IonContent, IonCard,
-    IonSegment, IonSegmentButton, IonSegmentContent, IonLabel, IonSegmentView, IonText, IonSearchbar
+    IonSegment, IonSegmentButton, IonSegmentContent, IonLabel, IonSegmentView, IonText, IonSearchbar,
+    IonRefresher
   ],
   schemas: [CUSTOM_ELEMENTS_SCHEMA],
 })
 
 export class Tab1Page {
   constructor(private router: Router) { }
-  ngOnInit() { }
-  ionViewWillEnter() { 
+  async ngOnInit() {
+    await this.getAllItem();
+    await this.getRecommendItem();
+  }
+  ionViewWillEnter() {
     this.getHotItem();
   }
+  /**
+   * @动态元素定位修改
+   */
+  handleScroll(ev: CustomEvent<ScrollDetail>) {
+    if (ev.detail.scrollTop != 0) {
+      let element = document.getElementById("location");
+      if (element)
+        element.style.color = "black";
+
+      element = document.getElementById("icon");
+      if (element)
+        element.style.color = "black";
+    }
+    else {
+      let element = document.getElementById("location");
+      if (element)
+        element.style.color = "white";
+
+      element = document.getElementById("icon");
+      if (element)
+        element.style.color = "white";
+    }
+  }
   //转到GenerateOption页面
   goGenerateOption() {
     this.router.navigate(['generateOption']);
@@ -42,4 +70,62 @@ export class Tab1Page {
     console.log("获取热门item的top7成功:")
     console.log(this.items);
   }
+  /**
+   * @读取推荐item
+   */
+  currentUser: CloudUser = new CloudUser();
+  recommendItems: CloudObject[] | any = [];
+  alreadyItems: CloudObject[] | any = [];
+  async getAllItem() {
+    let query = new CloudQuery("UserPrefer");
+    query.include("UserID");
+    query.include("ItemID");
+    query.equalTo("UserID", this.currentUser.toPointer());
+    this.recommendItems = await query.find();
+    this.recommendItems.sort((a: { get: (arg0: string) => number; }, b: { get: (arg0: string) => number; }) => b.get('preference') - a.get('preference'));
+  }
+  async getRecommendItem() {
+    let tempItems: CloudObject[] | any = [];
+    //将recommendItems从最中间分开为list1和list2,每组随机抽取2个,放入tempItems中
+    let list1 = this.recommendItems.slice(0, Math.floor(this.recommendItems.length / 2));
+    let list2 = this.recommendItems.slice(Math.floor(this.recommendItems.length / 2));
+    for (let i = 0; i < 2; i++) {
+      if (list1.length > 0) {
+        let index = Math.floor(Math.random() * list1.length);
+        tempItems.push(list1[index]);
+        list1.splice(index, 1);
+      }
+      if (list2.length > 0) {
+        let index = Math.floor(Math.random() * list2.length);
+        tempItems.push(list2[index]);
+        list2.splice(index, 1);
+      }
+    }
+    //将alreadyItems放入list2,再将list1和list2变为recommendItems
+    for (let i = 0; i < this.alreadyItems.length; i++) {
+      list2.push(this.alreadyItems[i]);
+    }
+    this.recommendItems = list1.concat(list2);
+    //将recommendItems降序排序
+    this.recommendItems.sort((a: { get: (arg0: string) => number; }, b: { get: (arg0: string) => number; }) => b.get('preference') - a.get('preference'));
+    //让alreadyItems=tempItems,以便下次推荐时不再重复
+    this.alreadyItems = tempItems;
+    console.log("获取推荐item成功:")
+    console.log(this.alreadyItems);
+  }
+  async recommend(item: CloudObject) {
+    let newItem = new CloudQuery("ItemInfo");
+    newItem.equalTo("objectId", item?.get("ItemID")?.objectId);
+    const newitem = await newItem.find();
+    console.log(newitem);
+    let recommendService = new RecommendationService();
+    recommendService.onItemClick(this.currentUser, newitem[0]);
+  }
+
+  handleRefresh(event: any) {
+    setTimeout(() => {
+      this.getRecommendItem();
+      event.target.complete();
+    }, 2000);
+  }
 }

+ 0 - 2
E-Cover-app/src/app/tab2/tab2.page.html

@@ -58,6 +58,4 @@
             </ion-segment-content>
       </ion-segment-view>
 
-      <ion-button size="large" (click)="goSendPost()">Tab 2</ion-button>
-
 </ion-content>

+ 6 - 3
E-Cover-app/src/app/tab2/tab2.page.ts

@@ -1,4 +1,4 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton, IonSegment, IonSegmentButton, IonSegmentContent, IonSegmentView, IonIcon, IonText, IonLabel, IonCard, IonImg } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
 import { Router } from '@angular/router';
@@ -19,8 +19,11 @@ addIcons({ addOutline, chevronForwardCircle })
     IonCard, IonImg, CommonModule, PostListComponent
   ]
 })
-export class Tab2Page {
-  constructor(private router: Router) { }
+export class Tab2Page implements OnInit {
+  constructor(private router: Router) {}
+  ngOnInit() {
+    this.getTop3Tag();
+  }
   ionViewWillEnter() {
     this.getTop3Tag();
   }

+ 1 - 1
E-Cover-app/src/app/tab3/tab3.page.html

@@ -76,7 +76,7 @@
   </ion-card>
   <ion-segment-view>
     <ion-segment-content id="myPost">
-      <ion-card class="post" *ngFor="let post of posts">
+      <ion-card class="post" *ngFor="let post of posts" (click)="goPostDetail(post)">
         <ion-img [src]="post?.get('image')[0] || 'assets/icon/favicon.png'" />
         <ion-title>{{post?.get('title')}}</ion-title>
         <div id="author">

+ 4 - 1
E-Cover-app/src/app/tab3/tab3.page.ts

@@ -52,11 +52,14 @@ export class Tab3Page {
     }
   }
   /**
-   * @跳转到设置页面
+   * @跳转界面函数
    */
   goSetting() {
     this.router.navigate(["/settings"]);
   }
+  goPostDetail(post:CloudObject){
+    this.router.navigate(['postDetail',post.id]);
+  }
   /**
    * @读取用户发布的帖子
    */

BIN
E-Cover-app/src/assets/fonts/title-font.TTF


BIN
E-Cover-app/src/assets/generate-option-style/areaStyle1.jpg


BIN
E-Cover-app/src/assets/generate-option-style/areaStyle2.jpg


BIN
E-Cover-app/src/assets/generate-option-style/areaStyle3.jpg


BIN
E-Cover-app/src/assets/generate-option-style/areaStyle4.jpg


BIN
E-Cover-app/src/assets/generate-option-style/areaStyle5.jpg


BIN
E-Cover-app/src/assets/generate-option-style/areaStyle6.jpg


BIN
E-Cover-app/src/assets/generate-option-style/areaStyle7.jpg


BIN
E-Cover-app/src/assets/generate-option-style/artStyle1.jpg


BIN
E-Cover-app/src/assets/generate-option-style/artStyle2.jpg


BIN
E-Cover-app/src/assets/generate-option-style/artStyle3.jpg


BIN
E-Cover-app/src/assets/generate-option-style/artStyle4.jpg


BIN
E-Cover-app/src/assets/generate-option-style/artStyle5.jpg


BIN
E-Cover-app/src/assets/generate-option-style/artStyle6.jpg


BIN
E-Cover-app/src/assets/generate-option-style/checked.png


BIN
E-Cover-app/src/assets/generate-option-style/designIdea1.jpg


BIN
E-Cover-app/src/assets/generate-option-style/designIdea2.jpg


BIN
E-Cover-app/src/assets/generate-option-style/designIdea3.jpg


BIN
E-Cover-app/src/assets/generate-option-style/designIdea4.jpg


BIN
E-Cover-app/src/assets/generate-option-style/designIdea5.jpg


BIN
E-Cover-app/src/assets/generate-option-style/designIdea6.jpg


BIN
E-Cover-app/src/assets/generate-option-style/designIdea7.jpg


BIN
E-Cover-app/src/assets/generate-option-style/designIdea8.jpg


BIN
E-Cover-app/src/assets/generate-option-style/designIdea9.jpg


BIN
E-Cover-app/src/assets/generate-option-style/function1.jpg


BIN
E-Cover-app/src/assets/generate-option-style/function2.jpg


BIN
E-Cover-app/src/assets/generate-option-style/function3.jpg


BIN
E-Cover-app/src/assets/generate-option-style/function4.jpg


BIN
E-Cover-app/src/assets/generate-option-style/function5.jpg


BIN
E-Cover-app/src/assets/generate-option-style/function6.jpg


+ 67 - 0
E-Cover-app/src/lib/component/post-detail/post-detail.component.html

@@ -1,2 +1,69 @@
 <!--头部-->
 <app-custom-header [param]="['帖子详情','    ']"></app-custom-header>
+<!--正文内容-->
+<ion-content>
+    <!--帖子-->
+    <div class="post">
+        <div class="post-header">
+            <div class="avatar">
+                <img src="{{post?.get('UserID')?.avatar}}" alt="User Avatar" class="avatar-image">
+            </div>
+            <div class="user-info">
+                <span class="username">{{ post?.get('UserID')?.name }}</span>
+                <!--关注按钮-->
+                <div [ngClass]="isFollowed ? 'fan-button' : 'follow-button'" (click)="toggleFollow()">
+                    <div *ngIf="!isFollowed" class="distance">+</div>
+                    <span *ngIf="!isFollowed">关注</span>
+                    <span *ngIf="isFollowed">已关注</span>
+                </div>
+
+            </div>
+        </div>
+        <h2 class="post-title">{{post?.get('title')}}</h2>
+        <!--标签-->
+        <div class="tag-container">
+            <div class="tag" *ngFor="let tag of post?.data?.tag">
+                <ion-icon name="bookmark-outline" class="tagpicture"></ion-icon>{{tag}}
+            </div>
+        </div>
+
+        <p class="post-content-text">{{post?.get('content')}}</p> <!--内容-->
+        <div class="image-gallery">
+            <img src="{{image}}" alt="Post Image" class="post-image" *ngFor="let image of post?.data?.image">
+
+        </div>
+
+    </div>
+
+    <!-- 评论区域 -->
+    <div class="comment-section">
+        <h3 class="comment-title">全部评论({{comments_num}})</h3>
+
+        <div class="comment" *ngFor="let comment of comments">
+            <div class="comment-box">
+                <div class="comment-left">
+                    <div class="comment-avatar">
+                        <img src="{{comment?.get('UserID')?.avatar}}"
+                            alt="User Avatar" class="comment-avatar-image">
+                    </div>
+                    <span class="floor">1L</span>
+                </div>
+
+                <div class="comment-middle">
+
+                    <div class="comment-content">
+                        <span class="comment-username">{{comment?.get('UserID')?.name}}</span>
+                        <p class="comment-text">{{comment?.data?.content}}</p>
+                        <span class="time-ago">5小时前</span>
+                    </div>
+                </div>
+
+                <div class="comment-right">
+                    <ion-icon name="heart-outline" class="like-icon"></ion-icon>
+                    <span class="like-count">10</span>
+                </div>
+            </div>
+        </div>
+
+    </div>
+</ion-content>

+ 231 - 0
E-Cover-app/src/lib/component/post-detail/post-detail.component.scss

@@ -0,0 +1,231 @@
+.post {
+    padding: 10px; /* 设置内边距 */
+    background-color: white; /* 背景色 */
+   
+    border-bottom: grey 1px solid; /* 下边框 */
+
+  }
+  
+  .post-header {
+    display: flex; /* 使用 Flexbox 布局 */
+    align-items: center; /* 垂直居中 */
+    margin-bottom: 10px; /* 标题与用户信息之间的间距 */
+  }
+  
+  .avatar {
+    width: 45px; /* 头像宽度 */
+    height: 45px; /* 头像高度 */
+    border-radius: 50%; /* 圆形头像 */
+    overflow: hidden; /* 隐藏超出部分 */
+    margin-right: 10px; /* 头像与用户名之间的间距 */
+    border: black 1px solid; /* 边框 */
+
+  }
+  
+  .avatar-image {
+    width: 100%; /* 头像宽度100% */
+    height: 100%; /* 头像高度100% */
+    object-fit: cover; /* 保持比例填充 */
+  }
+  
+  .user-info {
+    flex: 1; /* 让用户名部分占用剩余空间 */
+    display: flex;
+    justify-content: space-between; /* 用户名和三个点之间的间距 */
+    align-items: center; /* 垂直居中 */
+  }
+  
+  .username {
+    font-weight: bold; /* 加粗用户名 */
+    font-size: 22px; /* 用户名字体大小 */
+  }
+  
+  .more-icon {
+    font-size: 25px; /* 三个点图标大小 */
+    color: gray;
+    margin-right: 10px;
+  }
+  
+  .post-title {
+    font-size: 18px; /* 标题字体大小 */
+    margin: 10px 5px; /* 标题上下间距 */
+    font-weight: bold;
+  }
+  
+  //标签
+.tag-container {
+    display: flex;
+    justify-content: flex-start; /* 标签靠左对齐 */
+    margin-bottom: 10px; /* 将标签容器推到底部 */
+  }
+  
+  .tag {
+    background-color: #f0f0f0; // 灰色背景
+    color: black; // 黑色字体
+    border-radius: 20px; // 椭圆形效果
+    padding: 5px 10px; // 标签内边距
+    margin-right: 17px; // 标签之间的间距
+    font-size: 14px; // 标签字体大小
+    display: inline-flex; // 让标签内容居中
+    align-items: center; // 垂直居中
+  }
+  
+  .tagpicture{
+    margin-left: 5px;
+    margin-right: 5px;
+  }
+  
+  .post-content-text {
+    font-size: 18px; /* 帖子内容字体大小 */
+    margin-bottom: 10px; /* 内容与图片之间的间距 */
+  }
+  
+  .image-gallery {
+    display: flex; /* 使用 Flexbox 布局 */
+    
+    flex-wrap: wrap; /* 允许换行 */
+  }
+  
+  .post-image {
+    width: 32%; /* 每张图片占用32%的宽度 */
+    height: auto; /* 高度自适应 */
+    border-radius: 5px; /* 图片圆角 */
+    margin-bottom: 5px; /* 图片与下一张图片之间的间距 */
+    margin-right: 5px; /* 图片与下一张图片之间的间距 */
+
+  }
+
+  .button-container {
+    display: flex; /* 使用 Flexbox 布局 */
+    justify-content: space-between; /* 按钮之间的间距 */
+  }
+  
+  .action-button {
+    flex: 1; /* 每个按钮占用相同的空间 */
+    font-size: 17px; /* 按钮字体大小 */
+    align-items: center; // 垂直居中
+    color: black; // 字体颜色
+  }
+  
+  .action-button ion-icon {
+    font-size: 20px; /* 图标大小 */
+    margin-right: 5px; /* 图标与文本之间的间距 */
+  }
+//关注按钮
+  .follow-button {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 80px; /* 按钮宽度 */
+    height: 30px; /* 按钮高度 */
+    border-radius: 20px; /* 椭圆形 */
+    background-color: black; /* 初始背景色 */
+    color: white; /* 初始字体颜色 */
+    cursor: pointer;
+    transition: background-color 0.3s, color 0.3s; /* 添加过渡效果 */
+  }
+
+  //已关注按钮
+  .fan-button {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 80px; /* 按钮宽度 */
+    height: 30px; /* 按钮高度 */
+    border-radius: 20px; /* 椭圆形 */
+    background-color:white; /* 初始背景色 */
+    color: black; /* 初始字体颜色 */
+  }
+
+  //+与关注的距离
+  .distance{
+    margin-right: 5px;
+  }
+
+
+  .comment-section {
+    background-color: white; /* 背景色 */
+    border-radius: 8px; /* 圆角效果 */
+  }
+  
+  .comment-title {
+    font-size: 20px; /* 标题字体大小 */
+    font-weight: bold; /* 加粗 */
+    margin-bottom: 15px; /* 标题与评论之间的间距 */
+    margin-left: 10px;
+  }
+  
+  .comment {
+    margin-bottom: 20px; /* 每条评论之间的间距 */
+  }
+  
+  .comment-box {
+    display: flex; /* 使用 Flexbox 布局 */
+
+  }
+  
+  .comment-left {
+    display: flex;
+    flex-direction: column; /* 垂直排列 */
+    align-items: center; /* 水平居中 */
+    margin-right: 10px; /* 左侧与中间盒子之间的间距 */
+    margin-left: 10px;
+  }
+  
+  .comment-middle {
+    flex: 1; /* 占用剩余空间 */
+    display: flex;
+    flex-direction: column; /* 垂直排列 */
+  }
+  
+  .comment-right {
+    display: flex;
+    margin-left: 10px; /* 右侧与中间盒子之间的间距 */
+    padding-right: 15px;
+  }
+  
+  .comment-avatar {
+    width: 45px; /* 头像宽度 */
+    height: 45px; /* 头像高度 */
+    border-radius: 50%; /* 圆形头像 */
+    overflow: hidden; /* 隐藏超出部分 */
+    margin-bottom: 5px; 
+  }
+  
+  .comment-avatar-image {
+    width: 100%; /* 头像宽度100% */
+    height: 100%; /* 头像高度100% */
+    object-fit: cover; /* 保持比例填充 */
+  }
+  
+  .floor {
+    font-size: 18px; /* 楼数字体大小 */
+    color: gray; /* 楼数颜色 */
+  }
+  
+  .comment-username {
+    font-weight: bold; /* 加粗用户名 */
+    font-size: 17px; /* 用户名字体大小 */
+  }
+  
+  .comment-text {
+    font-size: 16px; /* 评论内容字体大小 */
+    margin: 3px 0; /* 内容上下间距 */
+  }
+  
+  .time-ago {
+    font-size: 16px; /* 时间字体大小 */
+    color: gray; /* 时间颜色 */
+  }
+  
+  .like-icon {
+    font-size: 25px; /* 点赞图标大小 */
+    color: gray; /* 点赞图标颜色 */
+    margin-right: 5px; /* 图标与点赞数之间的间距 */
+  }
+  
+  .like-count {
+    font-size: 14px; /* 点赞数字体大小 */
+    color: black; /* 点赞数颜色 */
+    margin-top: 3px; /* 点赞数与评论之间的间距 */
+  }

+ 54 - 5
E-Cover-app/src/lib/component/post-detail/post-detail.component.ts

@@ -1,17 +1,66 @@
+import { CommonModule } from '@angular/common';
 import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, ParamMap } from '@angular/router';
+import { IonButton, IonContent, IonIcon } from '@ionic/angular/standalone';
+import { addIcons } from 'ionicons';
+import { bookmarkOutline, ellipsisHorizontal, heartOutline } from 'ionicons/icons';
 import { CustomHeaderComponent } from '../custom-header/custom-header.component';
+import { CloudQuery } from 'src/lib/ncloud';
+import { map, switchMap } from 'rxjs/operators';
+
+addIcons({ ellipsisHorizontal, bookmarkOutline, heartOutline, })
 
 @Component({
   selector: 'app-post-detail',
   templateUrl: './post-detail.component.html',
   styleUrls: ['./post-detail.component.scss'],
   standalone: true,
-  imports:[CustomHeaderComponent]
+  imports: [IonContent, IonIcon, IonButton, CommonModule, CustomHeaderComponent]
 })
-export class PostDetailComponent  implements OnInit {
+export class PostDetailComponent implements OnInit {
+  postId: string | any;
+  post: any;
+  comments: any;
+  comments_num: number = 0;
+  constructor(private route: ActivatedRoute) { }
+  /**
+   * @从路由参数中获取帖子ID,并初始化帖子详情页面。
+   */
+  ngOnInit() {
+    this.route.paramMap.pipe(
+      switchMap((params: ParamMap) => this.route.queryParamMap.pipe(
+        map(() => params.get('postId'))
+      ))
+    ).subscribe(postId => {
+      this.postId = postId;
+      // 现在您可以使用 this.postId 来获取帖子详情或执行其他操作
+    });
+    console.log("正在加载帖子:" + this.postId)
+    this.getPostDetail();
+  }
 
-  constructor() { }
+  isFollowed: boolean = false; // 初始状态为未关注
 
-  ngOnInit() {}
+  toggleFollow() {
+    this.isFollowed = !this.isFollowed; // 切换关注状态
+  }
+  /**
+   * @获取帖子详情数据
+   */
+  async getPostDetail() {
+    let query = new CloudQuery("post");
+    query.include("UserID");
+    query.equalTo("objectId", this.postId);
+    let result = await query.find();
+    this.post = result[0];
+    console.log(this.post);
 
-}
+    let query1=new CloudQuery("comment");
+    query1.include("UserID");
+    query1.equalTo("postID", this.post.toPointer());
+    result = await query1.find();
+    this.comments = result;
+    console.log(this.comments);
+    this.comments_num=this.comments.length;
+  }
+}

+ 2 - 2
E-Cover-app/src/lib/component/post-list/post-list.component.html

@@ -1,4 +1,4 @@
-<div class="post" *ngFor="let post of posts" (click)="goPostDetail()">
+<div class="post" *ngFor="let post of posts" (click)="goPostDetail(post)">
   <div class="post-header">
     <div class="avatar">
       <ion-img
@@ -6,7 +6,7 @@
         alt="User Avatar" class="avatar-image" />
     </div>
     <div class="user-info">
-      <span class="username">{{ post.get('user')?.username || "该用户还没有名字" }}</span>
+      <span class="username">{{ post.get('UserID')?.username || "该用户还没有名字" }}</span>
       <ion-icon name="ellipsis-horizontal" class="more-icon"></ion-icon>
     </div>
   </div>

+ 2 - 3
E-Cover-app/src/lib/component/post-list/post-list.component.ts

@@ -28,7 +28,6 @@ export class PostListComponent implements OnInit {
     console.log(this.posts);
   }
 
-
   // 用于跟踪点赞状态
   isLiked: boolean = false;
 
@@ -36,7 +35,7 @@ export class PostListComponent implements OnInit {
   toggleLike() {
     this.isLiked = !this.isLiked; // 切换状态
   }
-  goPostDetail() {
-    this.router.navigate(['postDetail']);
+  goPostDetail(post:CloudObject) {
+    this.router.navigate(['postDetail',post.id]);
   }
 }

+ 140 - 0
E-Cover-app/src/lib/service/recommend.ts

@@ -0,0 +1,140 @@
+import { Injectable } from '@angular/core';
+import * as _ from 'lodash';
+import { CloudObject, CloudQuery, CloudUser } from '../ncloud';
+
+@Injectable({
+    providedIn: 'root'
+})
+export class RecommendationService {
+    userPrefer: CloudObject | any; // 用户偏好数据
+    itemTags: CloudObject | any; // 物品标签数据
+    // relatedTags: any;// 相关物品标签数据
+
+    constructor() { }
+
+    // 用户点击物品
+    public async onItemClick(currentUser: CloudUser, item: CloudObject) {
+        console.log("用户点击物品: ");
+        console.log(item);
+
+        // 获取用户偏好
+        let query = new CloudQuery("UserPrefer");
+        query.include("ItemID");
+        query.equalTo("UserID", currentUser.toPointer());
+        query.equalTo("ItemID", item.toPointer());
+        this.userPrefer = await query.find();
+        console.log("获取用户偏好: ");
+        console.log(this.userPrefer[0]);
+
+        const clickIncrement = 1; // 点击增加的偏好值
+        const relatedIncrementFactor = 0.5; // 相关物品增加偏好值的因子
+
+        //更新点击物品的偏好值
+        if (this.userPrefer[0]?.id) {
+            console.log("获取该物品偏好值:" + this.userPrefer[0].data['preference']);
+            this.userPrefer[0].data['preference'] += clickIncrement; // 每次点击增加偏好值
+            console.log("点击后的偏好值:" + this.userPrefer[0].data['preference']);
+        } else {
+            let newUserPrefer = new CloudObject("UserPrefer");
+            newUserPrefer.set({ "UserID": currentUser.toPointer(), "ItemID": item.toPointer(), "preference": clickIncrement });
+            console.log("用户即将保存数据: ");
+            console.log(newUserPrefer);
+            await newUserPrefer.save() // 如果没有记录,添加新记录
+            this.userPrefer[0] = newUserPrefer; // 同时更新本地用户偏好对象
+        }
+
+        // 获取点击物品的标签
+        this.itemTags = this.userPrefer[0].data['ItemID'].tag;
+        console.log("获取点击物品标签: ")
+        console.log(this.itemTags);
+
+        //更新与点击物品相关的其他物品的偏好值
+        query = new CloudQuery("ItemInfo");
+        let relatedObject = await query.find();
+        for (let i = 0; i < relatedObject.length; i++) {
+            if (this.userPrefer[0].data['ItemID'].objectId != relatedObject[i].get('objectId') && relatedObject[i].get('tag').length != 0) {
+                const similarity = this.calculateTagSimilarity(this.userPrefer[0].data['ItemID'].tag, relatedObject[i].get('tag'));
+                console.log(this.userPrefer[0].data['ItemID'].tag + "与" + relatedObject[i].get('tag') + "的相似度为:" + similarity);
+                if (similarity > 0) {
+                    const relatedPreferenceIncrement = clickIncrement * relatedIncrementFactor * similarity;
+                    let relatedUserPrefer: CloudObject | any;
+                    // 查找相关物品的用户偏好数据
+                    let relatedUserQuery = new CloudQuery("UserPrefer");
+                    relatedUserQuery.include("ItemID");
+                    relatedUserQuery.equalTo("UserID", currentUser.toPointer());
+                    relatedUserQuery.equalTo("ItemID", relatedObject[i].toPointer());
+                    relatedUserPrefer = await relatedUserQuery.find();
+                    console.log("查找到相关物品的用户偏好数据: ");
+                    console.log(relatedUserPrefer[0]);
+                    if (relatedUserPrefer.length > 0) {
+                        const userPrefer = relatedUserPrefer[0]; // 获取第一个用户偏好对象
+                        console.log("查找到相关物品的用户偏好数据,准备做修改: ");
+                        console.log("修改前偏好值: " + userPrefer.get('preference'));
+                        // 直接更新偏好值
+                        const updatedPreference = parseFloat(userPrefer.get('preference')) + relatedPreferenceIncrement;
+                        userPrefer.set({"preference": updatedPreference, "ItemID": relatedObject[i].toPointer() , "UserID": currentUser.toPointer() });
+                        // 保存更新后的偏好值
+                        await userPrefer.save();
+                        console.log("修改后偏好值: " + updatedPreference);
+                    } else {
+                        console.log("查找不到相关物品的用户偏好数据,准备做添加: ");
+                        let newRelatedUserPrefer = new CloudObject("UserPrefer");
+                        newRelatedUserPrefer.set({ "UserID": currentUser.toPointer(), "ItemID": relatedObject[i].toPointer(), "preference": relatedPreferenceIncrement });
+                        await newRelatedUserPrefer.save() // 如果没有记录,添加新记录
+                        relatedUserPrefer[0] = newRelatedUserPrefer; // 同时更新本地用户偏好对象
+                        console.log("已添加,相似度:" + relatedUserPrefer[0].data['preference']);
+                    }
+
+                }
+
+            }
+        }
+    }
+
+
+    //     // 获取物品的标签
+    //     private getItemTags(itemID: number) {
+    //         const item = this.itemTags.find(it => it.ItemID === itemID);
+    //         return item ? item.Tags : [];
+    //     }
+
+    //     // 获取用户的偏好
+    //     private getUserPreferences(userID: string) {
+    //         return this.userPreferences.filter(pref => pref.UserID === userID);
+    //     }
+
+    // 计算标签相似度
+    private calculateTagSimilarity(tagsA: string[], tagsB: string[]): number {
+        const intersection = _.intersection(tagsA, tagsB).length;
+        const union = _.union(tagsA, tagsB).length;
+        return union === 0 ? 0 : intersection / union; // Jaccard 相似度
+    }
+
+    //     // 获取推荐物品
+    //     public getRecommendations(userID: string): number[] {
+    //         const userPrefs = this.getUserPreferences(userID);
+    //         const recommendedItems: { [key: number]: number } = {}; // 物品ID和推荐分数
+
+    //         userPrefs.forEach(userPref => {
+    //             const itemTags = this.getItemTags(userPref.ItemID);
+
+    //             this.itemTags.forEach(item => {
+    //                 if (item.ItemID !== userPref.ItemID) {
+    //                     const similarity = this.calculateTagSimilarity(itemTags, item.Tags);
+    //                     if (similarity > 0) {
+    //                         if (!recommendedItems[item.ItemID]) {
+    //                             recommendedItems[item.ItemID] = 0;
+    //                         }
+    //                         // 根据用户的偏好和标签相似度计算推荐分数
+    //                         recommendedItems[item.ItemID] += userPref.Preference * similarity;
+    //                     }
+    //                 }
+    //             });
+    //         });
+
+    //         // 返回推荐物品按分数排序
+    //         return Object.entries(recommendedItems)
+    //             .sort((a, b) => b[1] - a[1]) // 按照分数降序排序
+    //             .map(([itemID]) => Number(itemID)); // 返回物品ID数组
+    //     }
+}