Bläddra i källkod

feat: new CloudObject and CloudQuery

s202226701053 6 månader sedan
förälder
incheckning
2e3bbd4b2d

+ 2 - 1
E-Cover-app/.vscode/settings.json

@@ -1,3 +1,4 @@
 {
-  "typescript.preferences.autoImportFileExcludePatterns": ["@ionic/angular/common", "@ionic/angular"]
+  "typescript.preferences.autoImportFileExcludePatterns": ["@ionic/angular/common", "@ionic/angular"],
+  "plantuml.server": "http://www.plantuml.com/plantuml"
 }

+ 1 - 28
E-Cover-app/src/app/chat-panel/chat-panel.component.html

@@ -3,31 +3,4 @@
     <ion-title>Chat</ion-title>
   </ion-toolbar>
 </ion-header>
-<!-- 聊天消息区域 -->
-<ion-content #content>
-  <div class="chat-container">
-    <div *ngFor="let message of messages"
-      [ngClass]="{'my-message': message.isMyMessage, 'other-message': !message.isMyMessage}">
-      <div class="message-content">
-        <ion-avatar slot="start" *ngIf="!message.isMyMessage">
-          <img src="https://via.placeholder.com/40" />
-        </ion-avatar>
-        <div class="message">{{ message.text }}</div> <!-- 消息内容 -->
-        <ion-avatar slot="end" *ngIf="message.isMyMessage">
-          <img src="https://via.placeholder.com/40" />
-        </ion-avatar>
-      </div>
-    </div>
-  </div>
-</ion-content>
-<!-- 输入框和发送按钮 -->
-<ion-footer>
-  <ion-toolbar>
-    <ion-buttons slot="end">
-      <ion-input [(ngModel)]="inputText" placeholder="Type a message..." (input)="onInputChange()"></ion-input>
-    </ion-buttons>
-    <ion-buttons slot="primary">
-      <ion-button (click)="sendMessage()" [disabled]="!inputText.trim()">Send</ion-button>
-    </ion-buttons>
-  </ion-toolbar>
-</ion-footer>
+<!-- fmode聊天组件 -->

+ 17 - 50
E-Cover-app/src/app/chat-panel/chat-panel.component.ts

@@ -3,6 +3,8 @@ import { IonicModule } from '@ionic/angular';
 import { FormsModule } from '@angular/forms';
 import { CommonModule } from '@angular/common';
 import { ActivatedRoute, Router } from '@angular/router';
+import { ChatPanelOptions, FmodeChat, openChatPanelModal } from 'fmode-ng';
+import { ModalController } from '@ionic/angular/standalone';
 
 
 @Component({
@@ -13,56 +15,21 @@ import { ActivatedRoute, Router } from '@angular/router';
   imports: [IonicModule, FormsModule, CommonModule],
 })
 export class ChatPanelComponent implements OnInit {
-  constructor(private route: ActivatedRoute) { }
-  userPrompt = ""
-  ngOnInit() {
-    this.route.queryParams.subscribe(params => {
-      this.userPrompt = params['userPrompt'] || '';
-      this.initializeMessages();
-    });
-  }
-
-  initializeMessages() {
-    this.messages = [
-      { text: 'Hello, how are you?', isMyMessage: false },
-      { text: this.userPrompt + 'test', isMyMessage: true },
-    ];
-  }
-  // 模拟聊天消息数据
-  messages = [
-    { text: 'Hello, how are you?', isMyMessage: false },
-    { text: this.userPrompt + 'test', isMyMessage: true },
-  ];
-
-  inputText = ''; // 输入框的内容
-
-  // 处理输入框内容变化
-  onInputChange() {
-    if (this.inputText.trim()) {
-      // Input box resizes automatically based on content
-    }
-  }
-
-  // 发送消息
-  sendMessage() {
-    if (this.inputText.trim()) {
-      const newMessage = {
-        text: this.inputText,
-        isMyMessage: true
-      };
-      this.messages.push(newMessage); // 添加消息到聊天记录
-
-      // 清空输入框
-      this.inputText = '';
-
-      // 模拟对方回复
-      setTimeout(() => {
-        const replyMessage = {
-          text: 'Got it! I will check that.',
-          isMyMessage: false
-        };
-        this.messages.push(replyMessage);
-      }, 1000); // 模拟1秒后收到对方消息
+  constructor(private route: ActivatedRoute,private modalCtrl:ModalController) { }
+  ngOnInit() { this.openGenerate() }
+
+  /**
+   * @打开聊天窗口
+   * 打开fmode聊天窗口的页面
+   */
+  openGenerate(){
+    let options:ChatPanelOptions = {
+      roleId:"2DXJkRsjXK",
+      onChatSaved:(chat:FmodeChat)=>{
+        // chat?.chatSession?.id 本次会话的 chatId
+        console.log("onChatSaved",chat,chat?.chatSession,chat?.chatSession?.id)
+      },
     }
+    openChatPanelModal(this.modalCtrl,options)
   }
 }

+ 15 - 13
E-Cover-app/src/app/generate-option/generate-option.component.html

@@ -45,27 +45,29 @@
   </div>
   <!--可供用户选择的提示词,默认隐藏,通过点击风格输入框中的helper-text展开-->
   <div id="option-prompt">
-    <ion-card *ngFor="let card of cards">
-      <ion-card-header>
-        <ion-card-title>{{ card.id }}</ion-card-title>
-      </ion-card-header>
-      <ion-chip *ngFor="let chip of card.chips" [class.isElected]="chip.isElected"
-        (click)="toggleChip(card.id, chip.id)">
-        {{ chip.label }}
-      </ion-chip>
-    </ion-card>
+    <!--区域风格,包含四个卡片-->
+    <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>
   </div>
 
-  <ion-button (click)="sendMsgAndGoGenerateResult()">
-    生成
-  </ion-button>
-  <!-- 诗词意境绘画生成结果 -->
+  <ion-button (click)="sendMsgAndGoGenerateResult()">生成</ion-button>
+  <ion-button>导入默认风格</ion-button>
+  <ion-button (click)="goChatPanel()">测试页面</ion-button>
+
+  <!-- 图片生成结果 -->
   @if(shareData.images) {
   @for(imageUrl of shareData.images;track imageUrl){
   <img [src]="imageUrl" alt="" srcset="">
   }
   }
 </ion-content>
+
+<!--处理动画,默认隐藏-->
 <div id="container">
   <div id="popover-container">
     <img style="width: 100px;" src="/assets/generate-option-style/load_animation.gif">

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

@@ -146,25 +146,33 @@ ion-content {
     color: #999;
   }
 
+  //隐藏其他可选选项的样式
   #option-prompt {
     display: none;
   }
 
-  ion-card {
-
-    .isElected {
-      --color: 'success';
-    }
+  .scroll-x {
+    display: flex;
+    /* 使用 Flexbox 来让子元素横向排列 */
+    overflow-x: auto;
+    /* 启用横向滚动 */
+    white-space: nowrap;
+    /* 防止换行 */
+    padding-bottom: 10px;
+    /* 可选:调整滚动区域底部的间距 */
   }
+
+  //以下为区域风格选择框的样式
 }
 
 #container {
-  position:absolute;
+  position: absolute;
   width: 100%;
   height: 100%;
   background-color: rgba(0, 0, 0, 0.5);
   display: none;
 }
+
 #popover-container {
   position: absolute;
   width: 70%;
@@ -172,7 +180,7 @@ ion-content {
   top: 35%;
   left: 15%;
   background-color: white;
-  display:grid;
+  display: grid;
   place-items: center;
   color: black;
   border-radius: 15px;

+ 54 - 126
E-Cover-app/src/app/generate-option/generate-option.component.ts

@@ -10,6 +10,7 @@ import { TaskGenerateUserValiate } from 'src/agent/tasks/generate/generate-user-
 import { TaskExecutor } from 'src/agent/agent.start';
 import { TaskGeneratePrompt } from 'src/agent/tasks/generate/generate-prompt';
 import { TaskGeneratePicture } from 'src/agent/tasks/generate/generate-picture';
+import { FmodeChat, ChatPanelOptions, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
 addIcons({ 'arrow-back-outline': arrowBackOutline, radioButtonOffOutline, closeCircleOutline, checkmarkCircleOutline, reloadOutline });
 @Component({
   selector: 'app-generate-option',
@@ -84,8 +85,55 @@ export class GenerateOptionComponent implements OnInit {
     '秋季': false,
     '冬季': false
   };
-  //自定义描述输入框定义变量
-
+  //区域风格选项卡定义变量
+  areaStyle: { [key: string]: boolean } = {
+    '中国风': false,
+    '日系': false,
+    '韩系': false,
+    '欧美风': false,
+    '英伦风': false,
+    '法式': false,
+    '波西米亚风': false
+  }
+  //场景功能选项卡定义变量
+  function: { [key: string]: boolean } = {
+    '通勤风': false,
+    '休闲风': false,
+    '田园风': false,
+    '校园风': false,
+    'Party风': false,
+    '约会装': false,
+    '度假风': false
+  };
+  //设计理念选项卡定义变量
+  designIdea: { [key: string]: boolean } = {
+    '新中式': false,
+    '淑女风': false,
+    '名媛风': false,
+    '简约风': false,
+    '极简风': false,
+    '中性风': false,
+    '民族风': false,
+    '戏剧风': false,
+    '复古风': false,
+    'Y2K': false,
+    '嘻哈风': false,
+    '甜酷风': false
+  }
+  //艺术风格选项卡定义变量
+  artStyle: { [key: string]: boolean } = {
+    '哥特风格': false,
+    '浪漫主义': false,
+    '洛可可风格': false,
+    '洛丽塔风格': false,
+    '维多利亚风': false,
+    '未来主义': false,
+  }
+  //颜色选项卡定义变量
+  color: { [key: string]: boolean } = {
+    '多巴胺穿搭': false,
+    '美拉德穿搭': false
+  };
 
   /**
    * @切换选项卡
@@ -118,78 +166,6 @@ export class GenerateOptionComponent implements OnInit {
     return isActive ? '/assets/generate-option-style/' + String + '-isActive-true.png' : '/assets/generate-option-style/' + String + '-isActive-false.png';
   }
 
-  //选项卡结构配置数据
-  cards = [
-    {
-      id: '区域风格',
-      chips: [
-        { id: 1, isElected: false, label: '中国风' },
-        { id: 2, isElected: false, label: '日系' },
-        { id: 3, isElected: false, label: '韩系' },
-        { id: 4, isElected: false, label: '欧美风' },
-        { id: 5, isElected: false, label: '英伦风' },
-        { id: 6, isElected: false, label: '法式' },
-        { id: 7, isElected: false, label: '波西米亚风' },
-      ],
-    },
-    {
-      id: '场景功能',
-      chips: [
-        { id: 1, isElected: false, label: '通勤风' },
-        { id: 2, isElected: false, label: '休闲风' },
-        { id: 3, isElected: false, label: '田园风' },
-        { id: 4, isElected: false, label: '校园风' },
-        { id: 5, isElected: false, label: 'Party风' },
-        { id: 6, isElected: false, label: '约会装' },
-        { id: 7, isElected: false, label: '度假风' },
-      ],
-    },
-    {
-      id: '设计理念',
-      chips: [
-        { id: 1, isElected: false, label: '新中式' },
-        { id: 2, isElected: false, label: '淑女风' },
-        { id: 3, isElected: false, label: '名媛风' },
-        { id: 4, isElected: false, label: '瑞丽风' },
-        { id: 5, isElected: false, label: '简约风' },
-        { id: 6, isElected: false, label: '极简风' },
-        { id: 7, isElected: false, label: '中性风' },
-        { id: 8, isElected: false, label: '性冷淡风' },
-        { id: 9, isElected: false, label: '民族风' },
-        { id: 10, isElected: false, label: '戏剧风' },
-        { id: 11, isElected: false, label: '复古风' },
-        { id: 12, isElected: false, label: 'Y2K' },
-        { id: 13, isElected: false, label: '嘻哈风' },
-        { id: 14, isElected: false, label: '朋克风' },
-        { id: 15, isElected: false, label: '嬉皮风' },
-        { id: 16, isElected: false, label: '甜酷风' },
-      ],
-    },
-    {
-      id: '艺术风格',
-      chips: [
-        { id: 1, isElected: false, label: '拜占庭艺术' },
-        { id: 2, isElected: false, label: '哥特风格' },
-        { id: 3, isElected: false, label: '浪漫主义' },
-        { id: 4, isElected: false, label: '巴洛克风格' },
-        { id: 5, isElected: false, label: '洛可可风格' },
-        { id: 6, isElected: false, label: '洛丽塔风格' },
-        { id: 7, isElected: false, label: '维多利亚风' },
-        { id: 8, isElected: false, label: '欧普风格' },
-        { id: 9, isElected: false, label: '未来主义' },
-        { id: 10, isElected: false, label: '极简主义' },
-        { id: 11, isElected: false, label: '新古典主义' },
-      ],
-    },
-    {
-      id: '色彩搭配',
-      chips: [
-        { id: 1, isElected: false, label: '多巴胺穿搭' },
-        { id: 2, isElected: false, label: '美拉德穿搭' },
-      ],
-    },
-  ];
-
   /**
    * @其他变量定义
    */
@@ -199,14 +175,6 @@ export class GenerateOptionComponent implements OnInit {
   /**
    * @任务链设计
    */
-  //
-  wait(duration: number = 1000) {
-    return new Promise((resolve) => {
-      setTimeout(() => {
-        resolve(true);
-      }, duration);
-    });
-  }
   /**
    * 任务:
    * 1.验证必填资料
@@ -226,53 +194,13 @@ export class GenerateOptionComponent implements OnInit {
     this.taskList = GenerateTaskList;
     TaskExecutor(GenerateTaskList);
   }
-
-
-
-  //点击选项卡事件
-  toggleChip(cardId: string, chipId: number): void {
-    this.cards.forEach((card) => {
-      if (card.id === cardId) {
-        card.chips.forEach((chip) => {
-          chip.isElected = chip.id === chipId; // 当前点击的 chip 设为 active,其他设为 false
-        });
-
-        switch (card.id) {
-          case '体重':
-            this.userProfile.weight = card.chips[chipId - 1].label;
-            break;
-          case '季节':
-            this.userProfile.season = card.chips[chipId - 1].label;
-            break;
-          case '区域风格':
-            this.userProfile.regStyle = card.chips[chipId - 1].label;
-            break;
-          case '场景功能':
-            this.userProfile.sceFunction = card.chips[chipId - 1].label;
-            break;
-          case '设计理念':
-            this.userProfile.dsgPhilosophy = card.chips[chipId - 1].label;
-            break;
-          case '艺术风格':
-            this.userProfile.artStyle = card.chips[chipId - 1].label;
-            break;
-          case '色彩搭配':
-            this.userProfile.color = card.chips[chipId - 1].label;
-            break;
-        }
-      }
-    })
-  };
-
   /**
    * @跳转到聊天面板
    * 调用路由跳转到chatPanel页面,并传递用户需求信息
    */
-  /*goChatPanel() {
-    this.router.navigate(['/chatPanel'], {
-      queryParams: { userPrompt: this.userPrompt }
-    });
-  }*/
+  goChatPanel() {
+    this.router.navigate(['/chatPanel']);
+  }
   /**
    * @风格描述下的帮助文本点击事件
    * 1. 将可选提示词展示或隐藏
@@ -328,7 +256,7 @@ export class GenerateOptionComponent implements OnInit {
     console.log("展示当前读取到的用户信息:");
     console.log(this.userProfile);
     this.doGenerateTask();
-    //this.goChatPanel();
+
   }
 }
 /**

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

@@ -59,4 +59,6 @@
     </div>
 </div>
 <ion-content>
+    <h3>数据库渲染测试 {{userInfoList[1].get('name')}}</h3>
+    <h3 *ngFor="let user of userInfoList">{{user.get('name')}}</h3>
 </ion-content>

+ 14 - 3
E-Cover-app/src/app/tab1/tab1.page.ts

@@ -1,10 +1,12 @@
-import { Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
+import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
-import { IonicModule} from '@ionic/angular';
+import { IonicModule } from '@ionic/angular';
 import { addIcons } from 'ionicons';
 import { notificationsOutline, locationOutline } from 'ionicons/icons';
 import { Router } from '@angular/router';
 import { register } from 'swiper/element/bundle';
+import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { CommonModule } from '@angular/common';
 
 register();
 
@@ -14,14 +16,23 @@ addIcons({ 'location-outline': locationOutline, 'notifications-outline': notific
   templateUrl: 'tab1.page.html',
   styleUrls: ['tab1.page.scss'],
   standalone: true,
-  imports: [ExploreContainerComponent, IonicModule],
+  imports: [ExploreContainerComponent, IonicModule,CommonModule],
   schemas: [CUSTOM_ELEMENTS_SCHEMA],
 })
 
 export class Tab1Page {
   constructor(private router: Router) { }
+  ngOnInit() {
+    this.loadUserInfoList();
+  }
   //转到GenerateOption页面
   goGenerateOption() {
     this.router.navigate(['generateOption']);
   }
+
+  userInfoList: Array<CloudObject> = [];
+  async loadUserInfoList() {
+    let query = new CloudQuery("UserInfo");
+    this.userInfoList = await query.find();
+  }
 }

+ 194 - 0
E-Cover-app/src/lib/ncloud.ts

@@ -0,0 +1,194 @@
+// CloudObject.ts
+export class CloudObject {
+    className: string;
+    id: string | null = null;
+    createdAt:any;
+    updatedAt:any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt", "ACL"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = `http://1.94.237.145:1337/parse/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "hcx"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(`http://1.94.237.145:1337/parse/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "hcx"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = null;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    whereOptions: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    greaterThan(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {};
+        this.whereOptions[key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        this.whereOptions[key] = value;
+    }
+
+    async get(id: string) {
+        const url = `http://1.94.237.145:1337/parse/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "hcx"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        return json || {};
+    }
+
+    async find() {
+        let url = `http://1.94.237.145:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.whereOptions).length) {
+            const whereStr = JSON.stringify(this.whereOptions);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "hcx"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item:any)=>this.dataToObj(item))
+        return objList || [];
+    }
+
+    async first() {
+        let url = `http://1.94.237.145:1337/parse/classes/${this.className}?`;
+
+        if (Object.keys(this.whereOptions).length) {
+            const whereStr = JSON.stringify(this.whereOptions);
+            url += `where=${whereStr}`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "hcx"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists:any):CloudObject{
+        let existsObject = new CloudObject(this.className);
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}

+ 14 - 0
E-Cover-prod/UMLprod/diagram.puml

@@ -0,0 +1,14 @@
+```plantuml
+@startuml
+|用户|
+start
+:请求服务;
+|系统|
+:读取偏好;
+:推荐算法生成结果;
+:推送主界面;
+|用户|
+:展示结果;
+stop
+@enduml
+```

+ 25 - 0
E-Cover-prod/UMLprod/state-diagram1.md

@@ -0,0 +1,25 @@
+```plantuml
+@startuml
+[*] --> check : requestGenerate
+state "检查中" as check{
+    [*] --> ic
+    ic --> [*]:[success]
+    state "输入检查中" as ic
+    ||
+    [*] --> rc
+    rc --> [*]:[success]
+    state "相关性检查中" as rc
+}
+state "生成中" as generate
+
+check --> [*] : [fail]
+check --> generate
+generate --> [*]:[fail]
+
+state "已保存" as saved
+state "已生成" as generated
+generate --> generated : [success]
+generated --> [*] : [fail]
+generated --> saved : save
+@enduml
+```

+ 10 - 0
E-Cover-prod/UMLprod/state-diagram2.md

@@ -0,0 +1,10 @@
+```plantuml
+@startuml
+state "生成中" as generate
+[*] -> generate : requestSuggest
+state "已生成" as generated
+generate -> generated
+state "已推送" 
+generated -> [*]
+@enduml
+```

+ 14 - 0
E-Cover-prod/UMLprod/活动图-智能推荐.md

@@ -0,0 +1,14 @@
+```plantuml
+@startuml
+|用户|
+start
+:请求服务;
+|系统|
+:读取偏好;
+:推荐算法生成结果;
+:推送主界面;
+|用户|
+:展示结果;
+stop
+@enduml
+```

+ 18 - 0
E-Cover-prod/UMLprod/状态转换图-可持续时尚建议.md

@@ -0,0 +1,18 @@
+```plantuml
+@startuml
+state "上传中" as upload
+[*] -> upload : request
+upload -> [*] : [fail]
+state "验证中" as valiate
+upload -> valiate 
+state "生成中" as generate
+valiate -> generate :[内容合理]
+valiate -> [*] : [内容不合理]
+state "已生成" as generated
+generate -> generated :生成完成
+generate --> [*] :生成失败
+state "已保存" as saved
+generated -> saved :保存成功
+saved -> [*] 
+@enduml
+```

+ 100 - 0
E-Cover-prod/UMLprod/类图.md

@@ -0,0 +1,100 @@
+```plantuml
+@startuml
+'用户信息类'
+class UserInfo{
+    - UserID: String
+    - createAt: Date
+    - name: String
+    - gender: String
+    - age:String
+    - styleInfo: List<UserStyleInfo>
+    + get...()
+    + set...()
+    + requestGenerate()
+    + requestSuggest()
+    + save()
+}
+'用户风格信息类,每位用户可设置多个风格信息'
+class UserStyleInfo{
+    - objectID: String
+    - createAt: Date
+    - age: Int
+    - gender: String
+    - height: Int
+    - weight: Int
+    - season: String
+    - customDesc: String
+    - areaStyle: String
+    - function: String
+    - designIdea: String
+    - artStyle: String
+    - color: String
+    + get...()
+    + set...()
+}
+
+class GenerateResult{
+    - GID: String
+    - UID: String
+    - content: String
+    - image: String
+    + get...()
+    + set...()
+    + save(image,content)
+}
+
+class Process{
+    - generationService: GenerationService
+    - suggestService: SuggestService
+    - fashionService: FashionService
+}
+
+class GenerationService{
+    + generate(UserInfo)
+    + getResult(GID)
+}
+
+Process <.. GenerationService
+GenerationService -- GenerateResult
+UserInfo <.. Process
+UserInfo -- UserStyleInfo
+'用户偏好类,记录用户浏览偏好集合'
+class UserPrefer{
+    - objectID: String
+    - UserID: String
+    - ItemID: String
+    - preference: number
+    + get...()
+    + set...()
+}
+
+class ItemInfo{
+    - objectID: String
+    - name: String
+}
+UserInfo -- UserPrefer
+UserPrefer -- ItemInfo
+
+class SuggestService{
+    + suggest(UserInfo)
+}
+
+Process <.. SuggestService
+
+class FashionResult{
+    - FID: String
+    - jumpLink: String
+    - content: String
+    + get...()
+    + set...()
+    + save(jumpLink,content)
+}
+
+class FashionService{
+    + integrate()
+}
+
+FashionService -- FashionResult
+Process <.. FashionService
+@enduml
+```

+ 164 - 0
E-Cover-server/lib/ncloud.js

@@ -0,0 +1,164 @@
+class CloudObject {
+    id //此处id指的是数据库自动生成的objectId
+    className
+    data = {}
+    constructor(className) {
+        this.className = className
+    }
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+    // 控制json数据
+    set(json) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "createdAt", "updatedAt", "ACL"].indexOf(key) > -1) {
+                return
+            }
+            this.data[key] = json[key]
+        })
+    }
+    // 获取data数据
+    get(key) {
+        return this.data[key] || null
+    }
+
+    async save() {
+        let method = "POST"
+        let url = "http://1.94.237.145:1337/parse/classes/" + this.className
+        // 更新
+        if (this.id) {
+            url = url + "/" + this.id
+            method = "PUT"
+        }
+        // 否则新增
+        let body = JSON.stringify(this.data)
+        let response = await fetch(url, {
+            "headers": {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "hcx"
+            },
+            "body": body,
+            "method": method,
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let result = await response?.json();
+        if (result?.objectId) { this.id = result?.objectId }
+        return this
+    }
+    async destory() {
+        if (!this.id) return
+        let response = await fetch("http://1.94.237.145:1337/parse/classes/" + this.className + "/" + this.id, {
+            "headers": {
+                "x-parse-application-id": "hcx"
+            },
+            "body": null,
+            "method": "DELETE",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let result = await response?.json();
+        if (result) {
+            this.id = null
+        }
+        return true
+    }
+}
+
+class CloudQuery {
+    className
+    constructor(className) {
+        this.className = className
+    }
+
+    whereOptions = {}
+    greaterThan(key, value) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gt"] = value
+    }
+    greaterThanAndEqualTo(key, value) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gte"] = value
+    }
+    lessThan(key, value) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lt"] = value
+    }
+    lessThanAndEqualTo(key, value) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lte"] = value
+    }
+    equalTo(key, value) {
+        this.whereOptions[key] = value
+    }
+
+    async get(id) {
+        let url = "http://1.94.237.145:1337/parse/classes/" + this.className + "/" + id + "?"
+
+        let response = await fetch(url, {
+            "headers": {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "hcx"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        return json || {}
+    }
+    async find() {
+        let url = "http://1.94.237.145:1337/parse/classes/" + this.className + "?"
+
+        if (Object.keys(this.whereOptions)?.length) {
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+        }
+
+        let response = await fetch(url, {
+            "headers": {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "hcx"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        return json?.results || []
+    }
+    async first() {
+        let url = "http://1.94.237.145:1337/parse/classes/" + this.className + "?"
+
+        if (Object.keys(this.whereOptions)?.length) {
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+        }
+
+        let response = await fetch(url, {
+            "headers": {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "hcx"
+            },
+            "body": null,
+            "method": "GET",
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let json = await response?.json();
+        let exists = json?.results?.[0] || null
+        if(exists){
+            let existsObject = new CloudObject(this.className)
+            existsObject.set(exists)
+            existsObject.id = exists.objectId
+            existsObject.createdAt = exists.createdAt
+            existsObject.updatedAt = exists.updatedAt
+            return existsObject
+        }
+    }
+}
+
+module.exports.CloudObject = CloudObject
+module.exports.CloudQuery = CloudQuery

+ 358 - 0
E-Cover-server/migration/data.js

@@ -0,0 +1,358 @@
+module.exports.UserInfoList = [
+    {
+        "UserID": "1",
+        "name": "野猪peppa",
+        "gender": "男",
+        "age": "19",
+    },
+    {
+        "UserID": "2",
+        "name": "花开富贵",
+        "gender": "女",
+        "age": "45",
+    },
+    {
+        "UserID": "3",
+        "name": "小兔子跳跳",
+        "gender": "男",
+        "age": "22",
+    },
+    {
+        "UserID": "4",
+        "name": "月亮姐姐",
+        "gender": "女",
+        "age": "30",
+    },
+    {
+        "UserID": "5",
+        "name": "风中的沙",
+        "gender": "男",
+        "age": "28",
+    },
+    {
+        "UserID": "6",
+        "name": "花间一壶酒",
+        "gender": "女",
+        "age": "35",
+    },
+    {
+        "UserID": "7",
+        "name": "孤独的行者",
+        "gender": "男",
+        "age": "40",
+    },
+    {
+        "UserID": "8",
+        "name": "晨曦微露",
+        "gender": "女",
+        "age": "26",
+    },
+    {
+        "UserID": "9",
+        "name": "星辰大海",
+        "gender": "男",
+        "age": "32",
+    },
+    {
+        "UserID": "10",
+        "name": "梦里水乡",
+        "gender": "女",
+        "age": "29",
+    }
+]
+
+module.exports.ItemInfoList = [
+    {
+        "ItemID": "1",
+        "name": "回力小白鞋",
+    },
+    {
+        "ItemID": "2",
+        "name": "AJ1",
+    },
+    {
+        "ItemID": "3",
+        "name": "耐克运动裤",
+    },
+    {
+        "ItemID": "4",
+        "name": "Adidas经典T恤",
+    },
+    {
+        "ItemID": "5",
+        "name": "PUMA跑步鞋",
+    },
+    {
+        "ItemID": "6",
+        "name": "李宁卫裤",
+    },
+    {
+        "ItemID": "7",
+        "name": "匡威帆布鞋",
+    },
+    {
+        "ItemID": "8",
+        "name": "优衣库衬衫",
+    },
+    {
+        "ItemID": "9",
+        "name": "H&M牛仔裤",
+    },
+    {
+        "ItemID": "10",
+        "name": "古驰手表",
+    },
+    {
+        "ItemID": "11",
+        "name": "LV手提包",
+    },
+    {
+        "ItemID": "12",
+        "name": "香奈儿耳环",
+    },
+    {
+        "ItemID": "13",
+        "name": "迪奥太阳镜",
+    },
+    {
+        "ItemID": "14",
+        "name": "巴宝莉风衣",
+    },
+    {
+        "ItemID": "15",
+        "name": "阿迪达斯运动背包",
+    },
+    {
+        "ItemID": "16",
+        "name": "耐克帽子",
+    },
+    {
+        "ItemID": "17",
+        "name": "新百伦跑鞋",
+    },
+    {
+        "ItemID": "18",
+        "name": "杰克琼斯夹克",
+    },
+    {
+        "ItemID": "19",
+        "name": "李宁运动短裤",
+    },
+    {
+        "ItemID": "20",
+        "name": "FILA休闲鞋",
+    }
+]
+
+module.exports.UserPreferList = [
+    {
+        "PreferID": "prefer1",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "1"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "3"
+        },
+        "preference": 5
+    },
+    {
+        "PreferID": "prefer2",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "2"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "4"
+        },
+        "preference": 3
+    },
+    {
+        "PreferID": "prefer3",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "3"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "5"
+        },
+        "preference": 4
+    },
+    {
+        "PreferID": "prefer4",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "4"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "6"
+        },
+        "preference": 2
+    },
+    {
+        "PreferID": "prefer5",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "5"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "7"
+        },
+        "preference": 5
+    },
+    {
+        "PreferID": "prefer6",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "6"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "8"
+        },
+        "preference": 1
+    },
+    {
+        "PreferID": "prefer7",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "7"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "9"
+        },
+        "preference": 4
+    },
+    {
+        "PreferID": "prefer8",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "8"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "10"
+        },
+        "preference": 3
+    },
+    {
+        "PreferID": "prefer9",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "9"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "11"
+        },
+        "preference": 5
+    },
+    {
+        "PreferID": "prefer10",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "10"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "12"
+        },
+        "preference": 2
+    },
+    {
+        "PreferID": "prefer11",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "1"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "13"
+        },
+        "preference": 4
+    },
+    {
+        "PreferID": "prefer12",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "2"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "14"
+        },
+        "preference": 1
+    },
+    {
+        "PreferID": "prefer13",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "3"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "15"
+        },
+        "preference": 3
+    },
+    {
+        "PreferID": "prefer14",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "4"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "16"
+        },
+        "preference": 5
+    },
+    {
+        "PreferID": "prefer15",
+        "UserID": {
+            "__type": "Pointer",
+            "className": "UserInfo",
+            "objectId": "5"
+        },
+        "ItemID": {
+            "__type": "Pointer",
+            "className": "ItemInfo",
+            "objectId": "17"
+        },
+        "preference": 2
+    }
+]

+ 95 - 0
E-Cover-server/migration/import-data.js

@@ -0,0 +1,95 @@
+const { CloudQuery, CloudObject } = require("../lib/ncloud");
+const { UserInfoList, ItemInfoList, UserPreferList } = require("./data");
+// 测试CRUD
+//testQuery();
+async function testQuery() {
+    let query = new CloudQuery("UserInfo");
+    query.equalTo("name", "湖南渣渣辉2");
+    let userInfoList = await query.find();
+    console.log("userInfoList", userInfoList);
+}
+
+async function main() {
+    // 基本的增删改查
+    let query = new CloudQuery("UserInfo");
+    let userInfoList = await query.find();
+    console.log("userInfoList count", userInfoList?.length)
+
+    let newUserInfo = new CloudObject("UserInfo");
+    newUserInfo.set({ "name": "Squirral" });
+    newUserInfo = await newUserInfo.save(newUserInfo);
+    console.log("newUserInfo added", newUserInfo);
+
+    newUserInfo.set({ "name": "Squirral2" });
+    newUserInfo = await newUserInfo.save(newUserInfo);
+    console.log("newUserInfo updated", newUserInfo);
+
+    await newUserInfo.destory();
+    console.log("newUserInfo deleted", newUserInfo);
+}
+//main()
+
+DataMap = {
+    UserInfo: {},
+    UserPrefer: {},
+    ItemInfo: {}
+}
+
+importUserAndPrefer();
+
+async function importUserAndPrefer() {
+    //导入用户数据
+    let userInfoList = UserInfoList
+    for (let index = 0; index < userInfoList.length; index++) {
+        let userInfo = userInfoList[index];
+        userInfo = await importObject("UserInfo", userInfo, "UserID");
+    }
+    //导入物品数据
+    let itemInfoList = ItemInfoList
+    for (let index = 0; index < itemInfoList.length; index++) {
+        let itemInfo = itemInfoList[index];
+        itemInfo = await importObject("ItemInfo", itemInfo,"ItemID");
+    }
+    //导入偏好数据
+    let userPreferList = UserPreferList
+    for (let index = 0; index < userPreferList.length; index++) {
+        let userPrefer = userPreferList[index];
+        userPrefer = await importObject("UserPrefer", userPrefer,"PreferID");
+    }
+    console.log(DataMap);
+}
+
+async function importObject(className, data, keyID) {
+    // 查重:判断导入的数据的data.keyID字段值是否能在数据库对应的keyID字段中查到对应的记录
+    // srcId为导入的表的主键字段值
+    let query = new CloudQuery(className);
+    let keyValue = data[keyID];
+    let srcId = keyValue;
+    query.equalTo(keyID,srcId)
+    let importObj = await query.first()
+    console.log(importObj)
+
+    // 导入
+    // 导入前批量处理Pointer类型数据,进行重定向
+    Object.keys(data).forEach(key => {
+        let field = data[key];
+        if (field && typeof field === 'object' && field.__type === 'Pointer') {
+            // 如果字段是Pointer类型,则进行重定向处理
+            if (DataMap && DataMap[field.className] && DataMap[field.className][field.objectId]) {
+                data[key] = DataMap[field.className][field.objectId].toPointer();
+            }
+        }
+    });
+
+    // 若未添加,则创建新对象并保存
+    if(!importObj?.id){
+        importObj = new CloudObject(className)
+    }
+
+    // 保存或更新data数据,让data中的keyID字段等于srcId的值
+    data[keyID] = srcId;
+    importObj.set(data);
+    importObj = await importObj.save();
+
+    DataMap[className][srcId] = importObj
+}