焦怡璇 1 éve
szülő
commit
626d1b7403
26 módosított fájl, 1161 hozzáadás és 661 törlés
  1. 0 0
      asd.js
  2. 14 8
      heartvoice-app/src/app/gexinhualiaotian/gexinhualiaotian.component.html
  3. 11 0
      heartvoice-app/src/app/gexinhualiaotian/gexinhualiaotian.component.scss
  4. 59 11
      heartvoice-app/src/app/gexinhualiaotian/gexinhualiaotian.component.ts
  5. 1 8
      heartvoice-app/src/app/interlocution/interlocution.component.html
  6. 3 3
      heartvoice-app/src/app/interlocution/interlocution.component.scss
  7. 63 36
      heartvoice-app/src/app/interlocution/interlocution.component.ts
  8. 125 2
      heartvoice-app/src/app/personality-test/personality-test.component.html
  9. 76 0
      heartvoice-app/src/app/personality-test/personality-test.component.scss
  10. 251 19
      heartvoice-app/src/app/personality-test/personality-test.component.ts
  11. 8 23
      heartvoice-app/src/app/tab1/tab1.page.html
  12. 7 7
      heartvoice-app/src/app/tab1/tab1.page.scss
  13. 3 3
      heartvoice-app/src/app/tab2/tab2.page.scss
  14. 54 26
      heartvoice-app/src/app/tab3/tab3.page.html
  15. 1 1
      heartvoice-app/src/app/tab3/tab3.page.scss
  16. 69 23
      heartvoice-app/src/app/tab3/tab3.page.ts
  17. 134 5
      heartvoice-app/src/lib/ncloud.ts
  18. 27 0
      heartvoice-app/src/lib/user/modal-user-edit/modal-user-edit.component.html
  19. 0 0
      heartvoice-app/src/lib/user/modal-user-edit/modal-user-edit.component.scss
  20. 22 0
      heartvoice-app/src/lib/user/modal-user-edit/modal-user-edit.component.spec.ts
  21. 80 0
      heartvoice-app/src/lib/user/modal-user-edit/modal-user-edit.component.ts
  22. 36 0
      heartvoice-app/src/lib/user/modal-user-login/modal-user-login.component.html
  23. 0 0
      heartvoice-app/src/lib/user/modal-user-login/modal-user-login.component.scss
  24. 22 0
      heartvoice-app/src/lib/user/modal-user-login/modal-user-login.component.spec.ts
  25. 95 0
      heartvoice-app/src/lib/user/modal-user-login/modal-user-login.component.ts
  26. 0 486
      heartvoice-server/migration/data.js

+ 0 - 0
asd.js


+ 14 - 8
heartvoice-app/src/app/gexinhualiaotian/gexinhualiaotian.component.html

@@ -14,12 +14,18 @@
       <ion-label>{{ message.content }}</ion-label>
     </ion-item>
   </ion-list>
+</ion-content>
 
-  <!-- 用户输入框 -->
-  <ion-footer>
-    <ion-toolbar>
-      <ion-input placeholder="输入你的消息..." [(ngModel)]="userInput"></ion-input>
-      <ion-button (click)="sendUserMessage()" color="primary">发送</ion-button>
-    </ion-toolbar>
-  </ion-footer>
-</ion-content>
+<!-- 用户输入框 -->
+<ion-footer>
+  <ion-toolbar>
+    <ion-row>
+      <ion-col size="10">
+        <ion-input placeholder="输入你的消息..." [(ngModel)]="userInput"></ion-input>
+      </ion-col>
+      <ion-col size="2">
+        <ion-button (click)="sendUserMessage()"  >></ion-button>
+      </ion-col>
+    </ion-row>
+  </ion-toolbar>
+</ion-footer>

+ 11 - 0
heartvoice-app/src/app/gexinhualiaotian/gexinhualiaotian.component.scss

@@ -12,4 +12,15 @@
     padding: 10px;
     border-radius: 10px;
     margin: 5px 0;
+  }
+
+ 
+
+  ion-button {
+    --background: #f8d7da; /* 按钮背景色 */
+    --color: rgb(5, 5, 5); /* 按钮文字颜色 */
+  }
+  
+  ion-button:hover {
+    --background: #fafab5; /* 悬停时按钮背景色 */
   }

+ 59 - 11
heartvoice-app/src/app/gexinhualiaotian/gexinhualiaotian.component.ts

@@ -1,8 +1,9 @@
 import { Component, OnInit } from '@angular/core';
-import { IonButton, IonButtons, IonContent, IonFooter, IonHeader, IonInput, IonItem, IonLabel, IonList, IonTitle, IonToolbar } from '@ionic/angular/standalone';
+import { IonButton, IonContent, IonHeader, IonToolbar, IonTitle, IonInput, IonItem, IonLabel, IonList, IonFooter, IonRow, IonCol, IonButtons } from '@ionic/angular/standalone';
 import { FmodeChatCompletion } from 'fmode-ng';
 import { FormsModule } from '@angular/forms';
 import { CommonModule } from '@angular/common'; // 导入 CommonModule
+import { CloudObject, CloudUser } from 'src/lib/ncloud';
 
 @Component({
   selector: 'app-home',
@@ -10,14 +11,15 @@ import { CommonModule } from '@angular/common'; // 导入 CommonModule
   templateUrl: './gexinhualiaotian.component.html',
   styleUrls: ['./gexinhualiaotian.component.scss'],
   imports: [
-    IonContent, IonButton, IonInput, IonList, IonHeader, IonToolbar, IonTitle, IonButtons, 
-     IonFooter, IonLabel, IonItem, FormsModule,CommonModule
+    IonContent, IonButton, IonInput, IonList, IonHeader, IonToolbar, IonTitle, 
+    CommonModule, IonFooter, IonLabel, IonItem, FormsModule,IonRow,IonCol,IonButtons
   ],
 })
 export class GexinhualiaotianComponent implements OnInit {
   userInput: string = ''; // 用户输入的消息
   messages: { content: string; sender: string }[] = []; // 消息数组
   isSending: boolean = false; // 标记是否正在发送消息
+  chatContent: any[] = []; // 添加 chatContent 属性
 
   constructor() {}
 
@@ -29,12 +31,46 @@ export class GexinhualiaotianComponent implements OnInit {
       // 将用户输入添加到消息数组
       this.messages.push({ content: this.userInput, sender: 'user' });
       this.sendMessage(); // 发送消息后调用 sendMessage 方法
+      this.saveChat(); // 确保在此处调用 saveChat 方法
     }
   }
 
+  // 保存聊天记录到 Chat 表
+  async saveChat() {
+    const currentUser = new CloudUser(); // 假设您有当前用户的信息
+    if (!currentUser?.id) {
+      console.error('当前用户无效,无法保存聊天记录。');
+      return; // 如果没有有效的用户,退出方法
+    }
+
+    const chatObject = new CloudObject("Chat"); // 创建 Chat 对象
+    let ACL: any = { // 公开访客 不可读 不可写
+      "*": { read: false, write: false }
+    };
+    ACL[currentUser?.id] = { read: true, write: true }; // 当前用户 可读 可写
+    let chatContentString = this.userInput;
+    let now = new Date();
+    let dateStr = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
+    // 设置聊天内容和用户信息
+    chatObject.set({
+      chatContent: chatContentString,
+      createdAt: dateStr, // 使用 Date 对象
+      user: currentUser.toPointer(),
+    });
+
+    try {
+      await chatObject.save(); // 保存聊天记录
+      console.log('聊天记录已成功保存:', chatContentString);
+    } catch (error) {
+      console.error('保存聊天记录时出错:', error);
+    }
+    // 清空聊天记录
+    this.chatContent = [];
+  }
+
   // 方法:实例化completion对象,传入消息数组,并订阅生成的可观察对象
   sendMessage() {
-    console.log("create");
+    console.log("创建消息");
     this.isSending = true; // 设置为正在发送状态
 
     let completion = new FmodeChatCompletion([
@@ -43,20 +79,32 @@ export class GexinhualiaotianComponent implements OnInit {
     ]);
 
     completion.sendCompletion().subscribe((message: any) => {
-      // 打印消息体
-      console.log(message.content);
       // 赋值消息内容给组件内属性
       const responseMsg = message.content;
 
-      // 将AI回复添加到消息数组
-      // 等待AI回复完全生成后再添加
+      // 使用 setTimeout 确保消息体完全输出再打印
       setTimeout(() => {
-        this.messages.push({ content: responseMsg, sender: 'ai' });
+        // 打印消息体
+        console.log(responseMsg);
+
+        // 清空之前的AI消息,只添加最后的AI回复
+        if (this.messages[this.messages.length - 1]?.sender === 'ai') {
+          this.messages.pop(); // 移除最后一条AI消息
+        }
+        this.messages.push({ content: responseMsg, sender: 'ai' }); // 添加最后的AI回复
+
         this.isSending = false; // 发送完成,重置状态
-      }, 1000); // 模拟生成延迟,您可以根据实际情况调整时间
+      }, 0); // 立即执行,但确保在当前调用栈结束后执行
 
       // 清空用户输入
       this.userInput = '';
     });
   }
-}
+}
+
+
+
+
+
+
+

+ 1 - 8
heartvoice-app/src/app/interlocution/interlocution.component.html

@@ -1,12 +1,5 @@
 <ion-content>
-  <h1>今天你开心吗</h1>
-  <ion-input [value]="happy" (ionInput)="happyInput($event)"></ion-input>
-<!-- 文本域:生成提示词 -->
-<h1>想和我聊什么</h1>
-<ion-textarea [value]="userPrompt" (ionInput)="promptInput($event)" placeholder="文本提示词" autoGrow="true"></ion-textarea>
-
-<!-- 按钮:执行消息生成函数 -->
-<ion-button (click)="sendMessage()" expand="block">聊天</ion-button>
+  <h1>MBTI</h1>
 <img src="assets/img/1.png" alt="  " width="100%" height="200"/> 
 <!-- 展示:返回消息内容 -->
 <div>{{responseMsg}}</div>

+ 3 - 3
heartvoice-app/src/app/interlocution/interlocution.component.scss

@@ -1,5 +1,5 @@
 ion-content {
-    --background: #caf0be; /* 设置背景色 */
+    --background: #f8d7da; /* 设置背景色 */
     padding: 20px; /* 添加内边距 */
     display: flex;
     flex-direction: column;
@@ -12,7 +12,7 @@ ion-content {
     color: #333; /* 标题颜色 */
     margin: 16px 0; /* 标题的上下外边距 */
     text-align: center; /* 标题居中 */
-  }
+  } 
   
   ion-input, ion-textarea {
     width: 100%; /* 输入框宽度 */
@@ -40,7 +40,7 @@ ion-content {
   div {
     margin-top: 20px; /* 消息展示的顶部外边距 */
     padding: 12px; /* 消息展示的内边距 */
-    background-color: #c3f0b6; /* 消息展示背景色 */
+    background-color: #f8d7da; /* 消息展示背景色 */
     border-radius: 8px; /* 消息展示圆角 */
     width: 100%; /* 消息展示宽度 */
     text-align: center; /* 文本居中 */

+ 63 - 36
heartvoice-app/src/app/interlocution/interlocution.component.ts

@@ -1,52 +1,79 @@
 import { Component, OnInit } from '@angular/core';
-import { IonButton } from '@ionic/angular/standalone';
-import { IonContent, IonHeader, IonInput, IonTextarea, IonTitle, IonToolbar } from '@ionic/angular/standalone';
-/** 引用:从fmode-ng库引用FmodeChatCompletion类 */
-import { FmodeChatCompletion } from 'fmode-ng';
+import { ActivatedRoute } from '@angular/router';
+import { FmodeChatCompletion } from 'fmode-ng'; // 引入 FmodeChatCompletion 类
+import { IonicModule } from '@ionic/angular';
+
 @Component({
   selector: 'app-interlocution',
   templateUrl: './interlocution.component.html',
   styleUrls: ['./interlocution.component.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton,
-    IonTextarea,IonInput
-    ],
+  imports: [IonicModule],
+
 })
-export class InterlocutionComponent  implements OnInit {
-  constructor() {}
-  ngOnInit() {}
-  // 用户输入提示词
-  happy:string = "开心"
-  happyInput(ev:any){
-    this.happy = ev.detail.value;
-  }
-  // 用户输入提示词
-   userPrompt:string = "什么能够让人开心"
-  promptInput(ev:any){
-    this.userPrompt = ev.detail.value;
+export class InterlocutionComponent implements OnInit {
+  mbtiType: string | null = null; // 用于存储接收到的 MBTI 类型
+  responseMsg: string = ""; // 用于展示消息内容的变量
+
+  constructor(private route: ActivatedRoute) {}
+
+  ngOnInit() {
+    // 获取路由参数
+    this.route.paramMap.subscribe(params => {
+      this.mbtiType = params.get('message'); // 获取传递的字符串
+      console.log('接收到的 MBTI 类型:', this.mbtiType); // 输出接收到的 MBTI 类型
+      this.sendMessage(); // 自动发送消息
+    });
   }
-  // 属性:组件内用于展示消息内容的变量
-  responseMsg:any = ""
-  // 方法:实例化completion对象,传入消息数组,并订阅生成的可观察对象。
-  sendMessage(){
-    console.log("create")
+
+  // 发送消息的方法
+  sendMessage() {
+    console.log("创建消息");
 
     let PromptTemplate =`
-    你作为一名专业的心理医生,用户心情是${this.happy},请你根据用户说的话,推测用户的性格,与用户聊天,并尽量安抚他。
-    以下是用户的口述:${this.userPrompt}
-     `
+    形如你是一名专业的Mbti 测试的专家,用户的mbti性格为${this.mbtiType},请你分析用户的性格
+    (例子:你是一名infp,调停者。
+     尽管调停者看起来很安静或谦虚,但他们的内心生活充满活力,充满激情。他们富有创造力和想象力,快乐地迷失在白日梦中,在脑海中编造各种故事和对话。这些个性以其敏感性而闻名--调停者可以对音乐,艺术,自然和周围的人产生深刻的情感反应。
+    
+     理想主义和善解人意,调停者渴望建立深厚而深情的关系,他们觉得有责任帮助他人。但由于这种人格类型只占人口的一小部分,调停者有时可能会感到孤独或被忽视,在一个似乎不欣赏他们独一无二的特质的世界里漂泊。
+    
+     凡属金子不一定发光;并不是所有流浪的人都迷失;老骥伏枥志在千里;霜冻无法触及深根。
+    
+     移情的天赋
+    
+     调停者对人性的深处有着切实的好奇心。从本质上讲,他们对自己的想法和感受非常敏锐,但他们也渴望了解周围的人。调停者富有同情心,不会评判,总是愿意听别人的故事。当有人向他们敞开心扉或向他们寻求安慰时,他们感到很荣幸能够倾听并提供帮助。
+    
+     对于调停者来说,在理想的关系中,双方不仅可以分享他们最疯狂的希望和梦想,而且还可以分享他们隐藏的恐惧和弱点。
+    
+     同理心是这种人格类型最优质的天赋之一,但有时可能是一种负担。世界上的麻烦沉重地压在调停者的肩膀上,这些人格很容易内化他人的消极情绪或心态。除非他们学会设定界限,否则调停者可能会因为需要纠正的错误而感到不知所措。
+    
+     说出他们的真相
+    
+     没有什么比假装自己不是调停者更令人不安的了。凭借他们的敏感性和对真实性的承诺,具有这种个性类型的人往往渴望创造性的自我表达机会。因此,毫不奇怪,许多著名的调停者是诗人、作家、演员和艺术家。他们不禁沉思人生的意义和目的,梦想着一路上各种各样的故事、想法和可能性。
+    
+     调停者有自我表达的天赋。他们可能通过隐喻和虚构人物揭示他们内心的想法和秘密。
+    
+     通过这些富有想象力的场景,调停者可以探索自己的内在本质以及在世界上的位置。虽然这是一个美妙的特征,但这些个性有时会表现出白日梦和幻想而不是采取行动的倾向。为了避免感到沮丧、无法实现或无能为力,调停者需要确保他们采取措施将梦想和想法变为现实。
+    
+     寻找一种呼唤
+    
+     这种性格类型的人往往会感到没有方向或被卡住,直到他们与生活的目标感联系在一起。对于许多调停者来说,这个目的与鼓舞他人有关,也与他们能够感觉到他人的痛苦就像是他们自己的痛苦有关。虽然调停者希望帮助每个人,但他们需要集中精力和努力--否则,他们最终可能会筋疲力尽。
+    
+     幸运的是,就像春天的花朵一样,即使在最黑暗的季节之后,调停者的创造力和理想主义也能绽放。尽管他们知道世界永远不会完美,但调停者仍然在乎尽其所能使世界变得更好。这种做正确事情的默默信念可能解释了为什么这些人无论走到哪里,总是激发出同情、善良和美丽。)
+    `;
+
 
+      let completion = new FmodeChatCompletion([
+        { role: "user", content: PromptTemplate }
+      ]);
 
-    let completion = new FmodeChatCompletion([
-      {role:"system",content:""},
-      {role:"user",content:this.userPrompt}
-    ])
-    completion.sendCompletion().subscribe((message:any)=>{
+
+    completion.sendCompletion().subscribe((message: any) => {
       // 打印消息体
-      console.log(message.content)
+      console.log(message.content);
       // 赋值消息内容给组件内属性
-      this.responseMsg = message.content
-    })
+      this.responseMsg = message.content;
+    });
   }
-
 }
+    

+ 125 - 2
heartvoice-app/src/app/personality-test/personality-test.component.html

@@ -1,4 +1,97 @@
-<ion-card>
+
+<ion-content>
+  <ion-card>
+  <ion-card-header>
+    <ion-card-title>MBTI 测试</ion-card-title>
+    <ion-card-subtitle>请回答以下问题</ion-card-subtitle>
+  </ion-card-header>
+  <ion-card-content>
+    <ion-list>
+      <ion-item *ngFor="let question of questionsList">
+        <h3 class="question-text">{{ question.get('questionText') }}</h3>
+        <ion-list>
+          <ng-container *ngIf="question.id">
+            <ion-item *ngFor="let option of getOptionsForQuestion(question.id)" lines="none" class="option-item">
+              <button class="option-button" [ngClass]="{'selected': option.selected}" (click)="selectOption(option, question)">
+                <span></span>
+              </button>
+              <div class="option-info">
+                <p>{{ option.get('optionText') }}</p>
+              </div>
+            </ion-item>
+          </ng-container>
+        </ion-list>
+      </ion-item>
+    </ion-list>
+    <!-- 添加总提交按钮 -->
+    <ion-button expand="full" (click)="submitAll()">提交所有选项</ion-button>
+  </ion-card-content>
+</ion-card>
+</ion-content>
+
+
+
+
+
+
+
+<!-- <ion-card>
+  <ion-card-header>
+    <ion-card-title>MBTI 测试</ion-card-title>
+    <ion-card-subtitle>请回答以下问题</ion-card-subtitle>
+  </ion-card-header>
+  <ion-card-content>
+    <ion-list>
+      <ion-item *ngFor="let question of questionsList">
+        <h3 class="question-text">{{ question.get('questionText') }}</h3>
+        <ion-list>
+          <ng-container *ngIf="question.id">
+            <ion-item *ngFor="let option of getOptionsForQuestion(question.id)" lines="none" class="option-item">
+              <div class="option-info">
+                <p>{{ option.get('optionText') }}</p>
+              </div>
+            </ion-item>
+          </ng-container>
+        </ion-list>
+      </ion-item>
+    </ion-list>
+  </ion-card-content>
+</ion-card> -->
+
+
+
+
+<!-- <ion-card>
+  <ion-card-header>
+    <ion-card-title>MBTI 测试</ion-card-title>
+    <ion-card-subtitle>请回答以下问题</ion-card-subtitle>
+  </ion-card-header>
+  <ion-card-content>
+    <ion-list>
+      <ion-item *ngFor="let question of questionsList">
+        <h3>{{ question.get('questionText') }}</h3>
+        <ion-list>
+          <ng-container *ngIf="question.id"> 
+            <ion-item *ngFor="let option of getOptionsForQuestion(question.id)" lines="none">
+              <div class="option-info">
+                <p>{{ option.get('optionText') }}</p>
+              </div>
+            </ion-item>
+          </ng-container>
+        </ion-list>
+      </ion-item>
+    </ion-list>
+  </ion-card-content>
+</ion-card> -->
+
+
+
+
+
+
+
+
+<!-- <ion-card>
   <ion-card-header>
     <ion-card-title>mbti</ion-card-title>
     <ion-card-subtitle>请回答以下问题</ion-card-subtitle>
@@ -8,8 +101,38 @@
       <ion-item *ngFor="let question of questionsList">
         <h3>{{ question.get('questionText') }}</h3>
         <ion-list>
+          <ng-container *ngIf="question.id"> 
+            <ion-item *ngFor="let option of getOptionsForQuestion(question.id)">
+              <div class="option-info">
+                <p>{{ option.get('optionText') }}</p>
+              </div>
+            </ion-item>
+          </ng-container>
+        </ion-list>
+      </ion-item>
+    </ion-list>
+  </ion-card-content>
+</ion-card> -->
 
+
+
+
+<!-- <ion-card>
+  <ion-card-header>
+    <ion-card-title>mbti</ion-card-title>
+    <ion-card-subtitle>请回答以下问题</ion-card-subtitle>
+  </ion-card-header>
+  <ion-card-content>
+    <ion-list>
+      <ion-item *ngFor="let question of questionsList">
+        <h3>{{ question.get('questionText') }}</h3>
+        <ion-list>
+          <ion-item *ngFor="let option of getOptionsForQuestion(question.id)">
+            <div class="option-info">
+              <p>{{ option.get('optionText') }}</p>
+            </div>
+          </ion-item>
         </ion-list>
       </ion-item>
     </ion-list>
-  </ion-card-content>
+  </ion-card-content> -->

+ 76 - 0
heartvoice-app/src/app/personality-test/personality-test.component.scss

@@ -0,0 +1,76 @@
+.option-button {
+    width: 15px;           // 按钮宽度
+    height: 15px;          // 按钮高度
+    border-radius: 50%;    // 圆形按钮
+    background-color: gray; // 默认灰色
+    border: none;          // 去掉边框
+    cursor: pointer;       // 鼠标悬停时显示为指针
+    margin-right: 10px;    // 按钮与文本之间的间距
+    display: flex;         // 使用 flexbox 布局
+    align-items: center;   // 垂直居中对齐
+    justify-content: center; // 水平居中对齐
+  }
+  
+  .option-button.selected {
+    background-color: red; // 选中时变为红色
+  }
+
+
+
+
+
+
+.question-text {
+
+    font-size: 16px;      // 字体大小可以根据需要调整
+    margin-bottom: 10px;  // 题目与选项之间的间隔
+  }
+  
+  .option-item {
+    display: flex;        // 使用 flexbox 布局
+    align-items: center; // 垂直居中对齐
+    padding: 1px 0;     // 上下内边距
+    white-space: nowrap;  // 强制选项在一行显示
+  }
+  
+  .option-info p {
+    margin: 0;           // 去掉段落的默认外边距
+    font-size: 14px;     // 字体大小可以根据需要调整
+  }
+
+
+
+ion-card {
+    margin: 16px; // 为卡片添加外边距
+    max-height: 100vh; // 设置卡片的最大高度
+    overflow-y: auto; // 启用垂直滚动
+}
+
+ion-list {
+    margin: 0; // 去掉列表的默认边距
+}
+
+.option-info {
+    padding: 8px 16px; // 为选项添加内边距
+    background-color: #f9f9f9; // 选项的背景颜色
+    border-radius: 4px; // 圆角效果
+    margin: 4px 0; // 选项之间的间距
+}
+
+h3 {
+    margin: 0; // 去掉标题的默认边距
+    font-size: 1.2em; // 调整标题字体大小
+}
+
+ion-button {
+  --background: #f8d7da; /* 按钮背景色 */
+  --color: rgb(5, 5, 5); /* 按钮文字颜色 */
+}
+
+ion-button:hover {
+  --background: #fafab5; /* 悬停时按钮背景色 */
+}
+
+// ion-item {
+//     display: block; // 确保每个 ion-item 为块级元素
+// }

+ 251 - 19
heartvoice-app/src/app/personality-test/personality-test.component.ts

@@ -1,58 +1,290 @@
 import { CommonModule } from '@angular/common';
 import { Component, OnInit } from '@angular/core';
-import { IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonItem, IonList } from '@ionic/angular/standalone';
-import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+import { IonContent,IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonItem, IonList, IonButton } from '@ionic/angular/standalone';
+import { CloudObject, CloudQuery,CloudUser } from 'src/lib/ncloud';
+import { Router } from '@angular/router';
+
+// 定义 Option 接口,扩展 CloudObject
+interface Option extends CloudObject {
+  selected?: boolean; // 添加 selected 属性
+}
+
+// 定义 DimensionWeight 接口
+interface DimensionWeight {
+  dimension: string; // 存储维度
+  totalWeight: number; // 存储权重总和
+}
+
+// 定义 Question 接口,扩展 CloudObject
+interface Question extends CloudObject {
+  selectedOptionWeight?: number | null; // 选项权重
+  dimensionWeights?: DimensionWeight[]; // 存储每个维度的权重总和
+}
 
 @Component({
   selector: 'app-personality-test',
   templateUrl: './personality-test.component.html',
   styleUrls: ['./personality-test.component.scss'],
   standalone: true,
-  imports: [IonCard, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCardContent, IonList, IonItem, CommonModule],
+  imports: [IonContent,IonCard, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCardContent, IonList, IonItem, IonButton, CommonModule],
 })
 export class PersonalityTestComponent implements OnInit {
+  questionsList: Array<Question> = []; // 使用 Question 类型
+  optionList: Array<Option> = []; // 使用 Option 类型
 
-  constructor() { }
+  constructor(private router: Router) {}
 
   ngOnInit() {
-    // 生命周期:页面加载后,运行列表加载函数
     this.loadQuestionsList();
   }
 
-  questionsList: Array<CloudObject> = [];
-  optionList: Array<CloudObject> = [];
-
   // 查询并加载问题列表的函数
   async loadQuestionsList() {
     let query = new CloudQuery("Questions");
-    this.questionsList = await query.find();
-
-    // 打印加载的问题列表
+    this.questionsList = await query.find() as Question[]; // 确保问题列表为 Question 类型
     console.log("加载的问题列表:", this.questionsList);
-
-    await this.loadOptionsList(); // 调用加载选项的函数
+    await this.loadOptionsList(); // 加载选项列表
   }
 
   // 查询并加载选项列表的函数
   async loadOptionsList() {
     let query = new CloudQuery("option");
-    this.optionList = await query.find();
-
-    // 打印加载的选项列表
+    this.optionList = await query.find() as Option[]; // 确保选项列表为 Option 类型
     console.log("加载的选项列表:", this.optionList);
   }
 
   // 根据问题 ID 获取相关选项
-  getOptionsForQuestion(questionId: string) {
+  getOptionsForQuestion(questionId: string): Option[] {
     return this.optionList.filter(option => 
       option.get('question')?.objectId === questionId
-    );
+    ) as Option[]; // 强制转换为 Option[]
   }
 
+  // 处理选项按钮点击事件
+  selectOption(option: Option, question: Question) {
+    // 切换选项的选中状态
+    option.selected = !option.selected;
+
+    // 从问题中获取 dimension
+    const dimension = question.get('dimension'); // 从 Questions 表中获取 dimension
+    const weightString = option.get('weight'); // 从 option 表中获取 weight
+
+    // 将 weight 从 string 转换为 number
+    const weight = parseFloat(weightString); // 使用 parseFloat 进行转换
+
+    // 更新问题的 dimensionWeights
+    if (!question.dimensionWeights) {
+        question.dimensionWeights = []; // 初始化 dimensionWeights
+    }
+
+    // 如果选项被选中,则增加权重
+    if (option.selected) {
+        // 查找当前维度是否已存在
+        const existingDimension = question.dimensionWeights.find(dw => dw.dimension === dimension);
+        if (existingDimension) {
+            existingDimension.totalWeight += weight; // 增加权重
+        } else {
+            // 如果不存在,则添加新的维度
+            question.dimensionWeights.push({ dimension, totalWeight: weight });
+        }
+    } else {
+        // 如果选项被取消选择,则减少权重
+        const existingDimension = question.dimensionWeights.find(dw => dw.dimension === dimension);
+        if (existingDimension) {
+            existingDimension.totalWeight -= weight; // 减少权重
+            if (existingDimension.totalWeight <= 0) {
+                // 如果总权重小于等于 0,则移除该维度
+                question.dimensionWeights = question.dimensionWeights.filter(dw => dw.dimension !== dimension);
+            }
+        }
+    }
+
+    // 保存问题的更改
+    question.save().then(() => {
+        console.log('问题选项权重已保存:', question.dimensionWeights);
+    }).catch(error => {
+        console.error('保存问题时出错:', error);
+    });
+  }
+  
+  // 提交所有选项的处理方法
+  submitAll() {
+    // 遍历所有问题,准备提交的数据
+    const submissionData = this.questionsList.map(question => {
+        return {
+            questionId: question.id,
+            dimensionWeights: question.dimensionWeights,
+        };
+    });
+
+    // 打印每个相同 dimension 的总权重及对应的 dimension 的值
+    const dimensionTotals: { [key: string]: number } = {}; // 用于存储每个 dimension 的总权重
+
+    // 遍历每个问题的 dimensionWeights
+    submissionData.forEach(data => {
+        data.dimensionWeights?.forEach(dw => {
+            if (dimensionTotals[dw.dimension]) {
+                dimensionTotals[dw.dimension] += dw.totalWeight; // 累加权重
+            } else {
+                dimensionTotals[dw.dimension] = dw.totalWeight; // 初始化权重
+            }
+        });
+    });
+
+    // 打印结果
+    console.log('每个相同 dimension 的总权重及对应的 dimension 的值:');
+    for (const dimension in dimensionTotals) {
+        console.log(`Dimension: ${dimension}, Total Weight: ${dimensionTotals[dimension]}`);
+    }
+
+    // 生成 MBTI 类型
+    let mbtiType = '';
+
+    // 根据维度和总权重确定 MBTI 类型字符
+    mbtiType += dimensionTotals['E/I'] >= 0 ? 'E' : 'I'; // E/I
+    mbtiType += dimensionTotals['S/N'] >= 0 ? 'S' : 'N'; // S/N
+    mbtiType += dimensionTotals['T/F'] >= 0 ? 'T' : 'F'; // T/F
+    mbtiType += dimensionTotals['J/P'] >= 0 ? 'J' : 'P'; // J/P
+
+    console.log('合成的 MBTI 类型:', mbtiType);
+    let currentUser = new CloudUser();
+    // 假设你有一个 API 方法来保存 UserResponse
+    const userResponse = new CloudObject("Userresponse"); // 创建 UserResponse 对象
+    let ACL:any = { // 公开访客 不可读 不可写
+      "*":{read:false,write:false}
+    }
+    if(currentUser?.id){ // 当前用户 可读 可写
+      ACL[currentUser?.id] = {read:true,write:true}
+    }
+
+    let now = new Date();
+    let dateStr = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
+    userResponse.set({
+      // ACL:ACL,
+        mbtiType: mbtiType,
+        createdAt: dateStr, // 创建时间
+        user:currentUser.toPointer(),
+    });
+
+    // 保存 UserResponse 对象
+    userResponse.save().then(() => {
+        console.log('MBTI 类型已成功保存:', mbtiType);
+        
+        // 导航到 InterlocutionComponent,并传递 mbtiType
+        this.router.navigate(['/tabs/interlocution', { message: mbtiType }]);
+    }).catch(error => {
+        console.error('保存 MBTI 类型时出错:', error);
+    });
+  }
+}
 
 
 
-}
+
+// import { CommonModule } from '@angular/common';
+// import { Component, OnInit } from '@angular/core';
+// import { IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonItem, IonList } from '@ionic/angular/standalone';
+// import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+
+// @Component({
+//   selector: 'app-personality-test',
+//   templateUrl: './personality-test.component.html',
+//   styleUrls: ['./personality-test.component.scss'],
+//   standalone: true,
+//   imports: [IonCard, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCardContent, IonList, IonItem, CommonModule],
+// })
+// export class PersonalityTestComponent implements OnInit {
+
+//   constructor() { }
+
+//   ngOnInit() {
+//     this.loadQuestionsList();
+//   }
+
+//   questionsList: Array<CloudObject> = [];
+//   optionList: Array<CloudObject> = [];
+
+//   // 查询并加载问题列表的函数
+//   async loadQuestionsList() {
+//     let query = new CloudQuery("Questions");
+//     this.questionsList = await query.find();
+//     console.log("加载的问题列表:", this.questionsList);
+//     await this.loadOptionsList(); // 加载选项列表
+//   }
+
+//   // 查询并加载选项列表的函数
+//   async loadOptionsList() {
+//     let query = new CloudQuery("option");
+//     this.optionList = await query.find();
+//     console.log("加载的选项列表:", this.optionList);
+//   }
+
+//   // 根据问题 ID 获取相关选项
+//   getOptionsForQuestion(questionId: string) {
+//     return this.optionList.filter(option => 
+//       option.get('question')?.objectId === questionId
+//     );
+//   }
+// }
+
+
+
+
+// import { CommonModule } from '@angular/common';
+// import { Component, OnInit } from '@angular/core';
+// import { IonCard, IonCardContent, IonCardHeader, IonCardSubtitle, IonCardTitle, IonItem, IonList } from '@ionic/angular/standalone';
+// import { CloudObject, CloudQuery } from 'src/lib/ncloud';
+
+// @Component({
+//   selector: 'app-personality-test',
+//   templateUrl: './personality-test.component.html',
+//   styleUrls: ['./personality-test.component.scss'],
+//   standalone: true,
+//   imports: [IonCard, IonCardHeader, IonCardSubtitle, IonCardTitle, IonCardContent, IonList, IonItem, CommonModule],
+// })
+// export class PersonalityTestComponent implements OnInit {
+
+//   constructor() { }
+
+//   ngOnInit() {
+//     // 生命周期:页面加载后,运行列表加载函数
+//     this.loadQuestionsList();
+//   }
+
+//   questionsList: Array<CloudObject> = [];
+//   optionList: Array<CloudObject> = [];
+
+//   // 查询并加载问题列表的函数
+//   async loadQuestionsList() {
+//     let query = new CloudQuery("Questions");
+//     this.questionsList = await query.find();
+
+//     // 打印加载的问题列表
+//     console.log("加载的问题列表:", this.questionsList);
+
+//     await this.loadOptionsList(); // 调用加载选项的函数
+//   }
+
+//   // 查询并加载选项列表的函数
+//   async loadOptionsList() {
+//     let query = new CloudQuery("option");
+//     this.optionList = await query.find();
+
+//     // 打印加载的选项列表
+//     console.log("加载的选项列表:", this.optionList);
+//   }
+
+//   // 根据问题 ID 获取相关选项
+//   getOptionsForQuestion(questionId: string) {
+//     return this.optionList.filter(option => 
+//       option.get('question')?.objectId === questionId
+//     );
+//   }
+
+
+
+
+// }
 
 
 

+ 8 - 23
heartvoice-app/src/app/tab1/tab1.page.html

@@ -4,15 +4,11 @@
       <img src="assets/img/8.png" alt="用户4" width="50" height="50"/>
     </ion-avatar>
     <ion-title>心语</ion-title>
-    <ion-buttons slot="end">
-      <ion-button routerLink="/login">登录</ion-button>
-      <ion-button routerLink="/register">注册</ion-button>
-    </ion-buttons>
   </ion-toolbar>
 </ion-header>
 
-<app-edit-tag (onTagChange)="setTagsValue($event)"></app-edit-tag>
-<h1></h1>
+<!-- <app-edit-tag (onTagChange)="setTagsValue($event)"></app-edit-tag>
+<h1></h1> -->
   
 
 <ion-content>
@@ -20,11 +16,7 @@
     <ion-row>
       <img src="assets/img/2.png" alt="心理健康" width="130" height="70" />
       <h2>关注你的心理健康</h2>
-      <p>个性化心理支持,随时随地</p>
-      <ion-button expand="full" routerLink="/personality-test" >开始测试</ion-button>
-      <h2>随时与AI聊天</h2>
-      <p>24/7提供个性化心理疏通对话。</p>
-      <ion-button expand="full"  (click)="goTo()" >开始聊天</ion-button>
+      <p>个性化心理支持,随时随地<strong>开始测试</strong></p>
       <img src="assets/img/7.png" alt="心理支持" width="140" height="70" />
   </ion-row>
 
@@ -47,10 +39,10 @@
         <ion-card>
           <ion-icon name="chatbubbles" slot="start"></ion-icon>
           <ion-card-header>
-            <ion-card-title>个性化聊天</ion-card-title>
+            <ion-card-title>开始聊天</ion-card-title>
           </ion-card-header>
           <ion-card-content>
-            24/7提供个性化心理疏通对话。
+            我们提供个性化心理疏通对话。    
           </ion-card-content>
           <ion-button expand="full" (click)="liaoTian()">开始聊天</ion-button>
         </ion-card>
@@ -59,7 +51,7 @@
         <ion-card>
           <ion-icon name="thumbs-up" slot="start"></ion-icon>
           <ion-card-header>
-            <ion-card-title>反馈优化</ion-card-title>
+            <ion-card-title>反馈优化</ion-card-title>
           </ion-card-header>
           <ion-card-content>
             你的反馈将帮助我们不断改善服务。
@@ -98,16 +90,9 @@
 
 <ion-footer>
   <ion-toolbar>
-    <ion-buttons slot="start">
-      <ion-button routerLink="/terms">使用条款</ion-button>
-      <ion-button routerLink="/privacy">隐私政策</ion-button>
-    </ion-buttons>
+
     <ion-title>© 2024 心理健康服务平台</ion-title>
-    <ion-buttons slot="end">
-      <ion-icon name="logo-wechat"></ion-icon>
-      <ion-icon name="logo-weibo"></ion-icon>
-      <ion-icon name="logo-qq"></ion-icon>
-    </ion-buttons>
+
   </ion-toolbar>
 </ion-footer>
 

+ 7 - 7
heartvoice-app/src/app/tab1/tab1.page.scss

@@ -1,7 +1,7 @@
 /* 首页样式 */
 
 ion-header {
-    background-color: #c3f0b6; /* 顶部背景色 */
+    background-color: #f8d7da; /* 顶部背景色 */
     color: white; /* 文字颜色 */
   }
   
@@ -11,7 +11,7 @@ ion-header {
     font: size 20px;
   }
   ion-toolbar {
-    --background: #c3f0b6; /* 顶部工具栏背景色 */
+    --background: #f8d7da; /* 顶部工具栏背景色 */
   }
   
   h2 {
@@ -43,7 +43,7 @@ ion-header {
   }
   
   ion-card-header {
-    background-color: #dcec9e; /* 卡片头部背景色 */
+    background-color: rgba(255, 221, 51, 0.1); /* 卡片头部背景色 */
   }
   
   ion-card-title {
@@ -65,7 +65,7 @@ ion-header {
   }
   
   ion-footer {
-    background-color: #c3f0b6; /* 底部背景色 */
+    background-color: #f8d7da; /* 底部背景色 */
     color: white; /* 底部文字颜色 */
   }
   
@@ -74,12 +74,12 @@ ion-header {
   }
   
   ion-button {
-    --background: #c3f0b6; /* 按钮背景色 */
+    --background: #f8d7da; /* 按钮背景色 */
     --color: rgb(5, 5, 5); /* 按钮文字颜色 */
   }
   
   ion-button:hover {
-    --background: #f6f665; /* 悬停时按钮背景色 */
+    --background: rgba(255, 221, 51, 0.1); /* 悬停时按钮背景色 */
   }
   
   /* 响应式设计 */
@@ -103,7 +103,7 @@ ion-header {
     margin: 20px 0; /* 上下外边距 */
     padding: 10px; /* 内边距 */
     background-color: rgba(255, 221, 51, 0.1); /* 淡黄色背景 */
-    border: 1px solid rgba(255, 221, 51, 0.5); /* 边框颜色 */
+    border: 1px solid #e6fb6e; /* 边框颜色 */
     border-radius: 8px; /* 圆角边框 */
     box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 阴影效果 */
   }

+ 3 - 3
heartvoice-app/src/app/tab2/tab2.page.scss

@@ -1,5 +1,5 @@
 ion-toolbar {
-  --background: #c3f0b6; /* 顶部工具栏背景色 */
+  --background: #f8d7da; /* 顶部工具栏背景色 */
 }
 
 ion-content {
@@ -31,7 +31,7 @@ ion-content {
   ion-item {
     margin: 10px 0; /* 列表项的上下外边距 */
     border-radius: 8px; /* 列表项圆角 */
-    background-color: #e8f4a7; /* 列表项背景色 */
+    background-color: rgba(255, 221, 51, 0.1); /* 列表项背景色 */
     box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 列表项阴影效果 */
   }
   ion-title{
@@ -62,7 +62,7 @@ ion-content {
   
   ion-button {
     margin-top: 16px; /* 按钮的顶部外边距 */
-    background-color: #3880ff; /* 按钮背景色 */
+    background-color: #f8d7da; /* 按钮背景色 */
     color: white; /* 按钮文字颜色 */
     border-radius: 8px; /* 按钮圆角 */
   }

+ 54 - 26
heartvoice-app/src/app/tab3/tab3.page.html

@@ -1,33 +1,61 @@
-<ion-header>
-  <ion-toolbar>
-    <ion-title>我的</ion-title>
+<ion-header [translucent]="true">
+  <ion-toolbar class="custom-toolbar">
+    <ion-title class="custom-title">
+      我的
+    </ion-title>
   </ion-toolbar>
 </ion-header>
 
-<ion-content>
-  <!-- 个人信息名片 -->
+<ion-content [fullscreen]="true">
+  <ion-refresher slot="fixed" (ionRefresh)="handleRefresh($event)">
+    <ion-refresher-content></ion-refresher-content>
+  </ion-refresher>
+
+  <!-- 用户登录状态 -->
   <ion-card>
-    <ion-card-header>
-      <ion-card-title>个人信息</ion-card-title>
-    </ion-card-header>
+    <!-- 未登录 -->
+    @if(!currentUser?.id) {
+      <ion-card class="login-card">
+        <ion-card-header>
+          <ion-card-title>请登录</ion-card-title>
+          <ion-card-subtitle>暂无信息</ion-card-subtitle>
+        </ion-card-header>
+      </ion-card>
+    }
+
+    <!-- 已登录 -->
+    @if(currentUser?.id) {
+      <ion-card>
+      <ion-card-header>
+        <div>
+            <ion-card-title>个人信息</ion-card-title>
+          </div>
+        </ion-card-header>
+        <ion-card-content>
+          <ion-item>
+            <ion-avatar slot="start">
+              <img src="assets/img/1.png" alt="用户头像" />
+            </ion-avatar>
+            <ion-label>
+              <h2>姓名: {{currentUser?.get("realname") || "-"}}</h2>
+              <p>账号:{{currentUser?.get("username")}}</p>
+              <p>性别: {{currentUser?.get("gender") || "-"}}</p>
+              <p>年龄: {{currentUser?.get("age") || "-"}}</p>
+            </ion-label>
+          </ion-item>
+        </ion-card-content>
+      </ion-card>
+    }
+
     <ion-card-content>
-      <ion-item>
-        <ion-avatar slot="start">
-          <img src="assets/img/1.png" alt="用户头像" />
-        </ion-avatar>
-        <ion-label>
-          <h2>张三</h2>
-          <p>心理健康爱好者</p>
-        </ion-label>
-      </ion-item>
-      <ion-button expand="full" color="primary" routerLink="/edit-profile">编辑个人信息</ion-button>
+      @if(!currentUser?.id) {
+        <ion-button expand="block" (click)="signup()" color="success">注册</ion-button>
+        <ion-button expand="block" (click)="login()" color="success">登录</ion-button>
+      }
+      @if(currentUser?.id) {
+        <ion-button expand="block" (click)="editUser()" color="success">编辑资料</ion-button>
+        <ion-button expand="block" (click)="logout()" color="medium">登出</ion-button>
+      }
     </ion-card-content>
   </ion-card>
-
-  <!-- 列表清单 -->
-  <ion-list>
-    
-
-  </ion-list>
-
-  
+</ion-content>

+ 1 - 1
heartvoice-app/src/app/tab3/tab3.page.scss

@@ -1,5 +1,5 @@
 ion-toolbar {
-    --background: #c3f0b6; /* 顶部工具栏背景色 */
+    --background: #f8d7da; /* 顶部工具栏背景色 */
   }
   ion-title{
     font-family: "宋体";

+ 69 - 23
heartvoice-app/src/app/tab3/tab3.page.ts

@@ -1,34 +1,80 @@
-import { Component, NgModule } from '@angular/core';
-import { IonCardContent,IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardHeader, IonCardTitle, IonItem, IonAvatar, IonLabel, IonButton, IonList, IonIcon } from '@ionic/angular/standalone';
-import { ExploreContainerComponent } from '../explore-container/explore-container.component';
-import { CommonModule } from '@angular/common';
-import { FormsModule } from '@angular/forms';
-import { IonicModule } from '@ionic/angular';
-
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonRefresher, IonRefresherContent, IonItem, IonAvatar, IonLabel, IonList } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+import { openUserEditModal } from 'src/lib/user/modal-user-edit/modal-user-edit.component';
+import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
+import { Router } from '@angular/router';
+import { EditTagComponent } from '../edit-tag/edit-tag.component';
 
 @Component({
   selector: 'app-tab3',
   templateUrl: 'tab3.page.html',
   styleUrls: ['tab3.page.scss'],
   standalone: true,
-  imports: [IonHeader, IonToolbar, IonTitle, IonContent, ExploreContainerComponent,
-  IonCard,IonCardContent,IonCardHeader,IonCardTitle,IonItem,IonAvatar,IonLabel,IonButton,IonList,IonIcon,],
+  imports: [IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard,IonCardContent,IonButton,IonCardHeader,IonCardTitle,IonCardSubtitle,
+    EditTagComponent,IonRefresher,IonRefresherContent,IonItem,IonAvatar,IonLabel,IonList
+  ],
 })
 export class Tab3Page {
-  historyRecords = [
-    { title: '性格测试结果', date: new Date('2024-01-01') },
-    { title: '心情分析报告', date: new Date('2024-01-15') },
-    { title: 'AI心理疏通记录', date: new Date('2024-02-05') },
-  ];
+  handleRefresh(event:any) {
+    setTimeout(() => {
+      // Any calls to load data go here
+      this.currentUser = new CloudUser();
+      event.target.complete();
+    }, 2000);
+  }
 
-  // 示例反馈问题数据
-  feedbackList = [
-    { title: '反馈关于应用崩溃', status: '已解决' },
-    { title: '建议增加新功能', status: '待处理' },
-  ];
+  goToCollection(){
+    console.log("goToCollection");
+  }
 
-  // 控制显示的状态
-  showHistory = false;
-  showFeedback = false;
-}
+  goToAvatar(){
+    console.log(['route'])
+    this.router.navigate(['/tabs/picture'])
+  }
+
+  currentUser:CloudUser|undefined
+  constructor(
+    private router: Router,
+    private modalCtrl:ModalController) {
+    this.currentUser = new CloudUser();
+  }
+  async login(){
+    // 弹出登录窗口
+    let user = await openUserLoginModal(this.modalCtrl);
+    if(user?.id){
+      this.currentUser = user
+    }
+  }
+  async signup(){
+    // 弹出注册窗口
+    let user = await openUserLoginModal(this.modalCtrl,"signup");
+    if(user?.id){
+      this.currentUser = user
+    }
+  }
+  logout(){
+    this.currentUser?.logout();
+  }
 
+  editUser(){
+    openUserEditModal(this.modalCtrl)
+  }
+
+  editTags:Array<String>=[]
+   async setTagsValue(ev:any){
+    let currentUser = new CloudUser();
+    let userPrompt = ``
+    if(!currentUser?.id){
+      console.log("用户未登录,请登录后重试");
+      let user = await openUserLoginModal(this.modalCtrl);
+      if(!user?.id){
+        return
+      }
+      currentUser = user;
+    }
+  //console.log("setTagsValue",ev);
+  this.editTags=ev;
+}
+}

+ 134 - 5
heartvoice-app/src/lib/ncloud.ts

@@ -1,4 +1,5 @@
 // CloudObject.ts
+
 export class CloudObject {
     className: string;
     id: string | null = null;
@@ -31,7 +32,7 @@ export class CloudObject {
 
     async save() {
         let method = "POST";
-        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}`;
+        let url = `https://dev.fmode.cn/parse/classes/${this.className}`;
 
         // 更新
         if (this.id) {
@@ -63,7 +64,7 @@ export class CloudObject {
 
     async destroy() {
         if (!this.id) return;
-        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+        const response = await fetch(`https://dev.fmode.cn/parse/classes/${this.className}/${this.id}`, {
             headers: {
                 "x-parse-application-id": "dev"
             },
@@ -115,7 +116,7 @@ export class CloudQuery {
     }
 
     async get(id: string) {
-        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}?`;
+        const url = `https://dev.fmode.cn/parse/classes/${this.className}/${id}?`;
 
         const response = await fetch(url, {
             headers: {
@@ -133,7 +134,7 @@ export class CloudQuery {
     }
 
     async find() {
-        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+        let url = `https://dev.fmode.cn/parse/classes/${this.className}?`;
 
         if (Object.keys(this.whereOptions).length) {
             const whereStr = JSON.stringify(this.whereOptions);
@@ -158,7 +159,7 @@ export class CloudQuery {
     }
 
     async first() {
-        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+        let url = `https://dev.fmode.cn/parse/classes/${this.className}?`;
 
         if (Object.keys(this.whereOptions).length) {
             const whereStr = JSON.stringify(this.whereOptions);
@@ -193,4 +194,132 @@ export class CloudQuery {
         existsObject.updatedAt = exists.updatedAt;
         return existsObject;
     }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if(userCacheStr){
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken:string|null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        return this;
+    }
+        
+    //     const response = await fetch(`https://dev.fmode.cn/parse/users/me`, {
+    //         headers: {
+    //             "x-parse-application-id": "dev",
+    //             "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+    //         },
+    //         method: "GET"
+    //     });
+
+    //     const result = await response?.json();
+    //     if (result?.error) {
+    //         console.error(result?.error);
+    //         return null;
+    //     }
+    //     return result;
+    // }
+
+    /** 登录 */
+    async login(username: string, password: string):Promise<CloudUser|null> {
+        const response = await fetch(`https://dev.fmode.cn/parse/login`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify({ username, password }),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+        
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User",JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(`https://dev.fmode.cn/parse/logout`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken
+            },
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return false;
+        }
+
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = null;
+        this.sessionToken = null;
+        this.data = {};
+        return true;
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(`https://dev.fmode.cn/parse/users`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(userData),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        return this;
+    }
 }

+ 27 - 0
heartvoice-app/src/lib/user/modal-user-edit/modal-user-edit.component.html

@@ -0,0 +1,27 @@
+<!-- 用户登录状态 -->
+<ion-card>
+  <ion-card-header>
+    <ion-card-title>
+      用户名:{{currentUser?.get("username")}}
+    </ion-card-title>
+    <ion-card-subtitle>请输入您的详细资料</ion-card-subtitle>
+   </ion-card-header>
+ <ion-card-content>
+
+   <ion-item>
+     <ion-input [value]="userData['realname']" (ionChange)="userDataChange('realname',$event)" label="姓名" placeholder="请您输入真实姓名"></ion-input>
+   </ion-item>
+   <ion-item>
+     <ion-input type="number" [value]="userData['age']" (ionChange)="userDataChange('age',$event)" label="年龄" placeholder="请您输入年龄"></ion-input>
+    </ion-item>
+  <ion-item>
+     <ion-input [value]="userData['gender']" (ionChange)="userDataChange('gender',$event)" label="性别" placeholder="请您输入男/女"></ion-input>
+    </ion-item>
+  
+
+   <ion-button expand="block" (click)="save()">保存</ion-button>
+   <ion-button expand="block" (click)="cancel()">取消</ion-button>
+ 
+
+</ion-card-content>
+</ion-card>

+ 0 - 0
heartvoice-app/src/lib/user/modal-user-edit/modal-user-edit.component.scss


+ 22 - 0
heartvoice-app/src/lib/user/modal-user-edit/modal-user-edit.component.spec.ts

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

+ 80 - 0
heartvoice-app/src/lib/user/modal-user-edit/modal-user-edit.component.ts

@@ -0,0 +1,80 @@
+import { Input, OnInit } from '@angular/core';
+import { Component } from '@angular/core';
+import { IonHeader, IonToolbar, IonTitle, IonContent, IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle, ModalController, IonInput, IonItem, IonSegment, IonSegmentButton, IonLabel } from '@ionic/angular/standalone';
+import { CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-modal-user-edit',
+  templateUrl: './modal-user-edit.component.html',
+  styleUrls: ['./modal-user-edit.component.scss'],
+  standalone: true,
+  imports: [
+    IonHeader, IonToolbar, IonTitle, IonContent, 
+    IonCard, IonCardContent, IonButton, IonCardHeader, IonCardTitle, IonCardSubtitle,
+    IonInput, IonItem,
+    IonSegment, IonSegmentButton, IonLabel
+  ],
+})
+export class ModalUserEditComponent implements OnInit {
+  currentUser: CloudUser | undefined;
+  
+  userData: any = {};
+
+   
+  constructor(private modalCtrl: ModalController) { 
+    this.currentUser = new CloudUser(); // 假设您有当前用户的信息
+    this.userData = this.currentUser.data;
+  }
+
+  ngOnInit() {}
+
+  userDataChange(key: string, ev: any) {
+    let value = ev?.detail?.value;
+    if (value) {
+      this.userData[key] = value;
+    }
+  }
+
+async save() {
+  // 确保年龄字段为数字
+  Object.keys(this.userData).forEach(key => {
+    if (key === "age") {
+      this.userData[key] = Number(this.userData[key]); // 确保年龄为数字
+    }
+  });
+
+  // 删除不需要的字段
+  delete this.userData.createdAt; // 删除 createdAt 字段
+  delete this.userData.updatedAt; // 删除 updatedAt 字段
+
+  this.currentUser?.set(this.userData); // 设置用户数据
+
+  try {
+    await this.currentUser?.save(); // 保存用户数据
+    this.modalCtrl.dismiss(this.currentUser, "confirm");
+  } catch (error) {
+    console.error('保存用户时出错:', error);
+  }
+}
+
+  cancel() {
+    this.modalCtrl.dismiss(null, "cancel");
+  }
+}
+
+// 打开用户编辑模态框的函数
+export async function openUserEditModal(modalCtrl: ModalController): Promise<CloudUser | null> {
+  const modal = await modalCtrl.create({
+    component: ModalUserEditComponent,
+    breakpoints: [0.7, 1.0],
+    initialBreakpoint: 0.7
+  });
+  modal.present();
+
+  const { data, role } = await modal.onWillDismiss();
+
+  if (role === 'confirm') {
+    return data;
+  }
+  return null;
+}

+ 36 - 0
heartvoice-app/src/lib/user/modal-user-login/modal-user-login.component.html

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

+ 0 - 0
heartvoice-app/src/lib/user/modal-user-login/modal-user-login.component.scss


+ 22 - 0
heartvoice-app/src/lib/user/modal-user-login/modal-user-login.component.spec.ts

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

+ 95 - 0
heartvoice-app/src/lib/user/modal-user-login/modal-user-login.component.ts

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

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 0 - 486
heartvoice-server/migration/data.js


Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott