cainiao-hue преди 3 месеца
родител
ревизия
c2f34d070e

+ 27 - 6
docs-prod/schema.md

@@ -3,6 +3,20 @@
 ```plantuml
 ' 聊天项目类图英文版
 @startuml
+class ChatAdvice {
+    + objectId: String//建议标识符
+    + title:String//建议标题
+    + contnet:String//建议内容
+    + admin: Pointer<_Admin> // 关联的管理员
+}
+
+class ChatAdmin {
+    + objectId: String // 管理员唯一标识符
+    + password: String // 管理员密码
+    + email: String // 管理员邮箱
+    + realname: String // 管理员真实姓名
+    + manageUser(user: Pointer<_User>): void // 管理用户的方法
+}
 class _User { 
     + objectId: String //用户唯一标识符
     + username: String //用户名
@@ -18,17 +32,23 @@ class _User {
     + summarizeChatHistory(): void
 }
 
-' Store information about chat partners
 class ChatPartner {
     + objectId: String //陪聊师唯一标识符
     + name: String //陪聊师姓名
-    + expertise: String //陪聊师专业领域(普通陪聊师可为空)
+    + expertise: String //陪聊师专业领域
     + avatar: String //陪聊师头像
     + bio: String //陪聊师个人简介
     + provideChat(): String 
 }
 
-' Record chat history between users and chat partners
+class ChatCompanion {
+    + objectId: String //聊天伙伴唯一标识符
+    + name: String //聊天伙伴姓名
+    + avatar: String //聊天伙伴头像
+    + bio: String //聊天伙伴个人简介
+    + provideChat(): String
+}
+
 class ChatRecord {
     + objectId: String //聊天记录唯一标识符
     + title:String //聊天标题
@@ -39,16 +59,17 @@ class ChatRecord {
     + getChatHistory(): List
 }
 
-' Record generated reports
 class Report {
     + objectId: String //报告唯一标识符
     - analysisResult: String //分析结果
     + generateReport(): String //生成报告的方法
 }
 
-User "1" --> "*" ChatRecord     
+_User "1" --> "*" ChatRecord     
 ChatPartner "1" --> "*" ChatRecord 
-ChatRecord "1" -- "1" Report      
+ChatCompanion "1" --> "*" ChatRecord 
+ChatRecord "1" -- "1" Report
+ChatAdmin "1" --> "*" ChatAdvice      
 @enduml
 ```
 # 时序图

+ 14 - 0
soul-app/src/app/customer-service/customer-service.component.html

@@ -0,0 +1,14 @@
+<ion-header>
+  <ion-toolbar>
+    <ion-title>客服帮助</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="closeModal()">关闭</ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+
+<ion-content class="modal-content">
+  <h2>欢迎来到客服帮助中心!</h2> 
+  <p>客服热线:19870628587</p>
+  <p>早上8:00~晚上11:00</p>
+</ion-content>

+ 6 - 0
soul-app/src/app/customer-service/customer-service.component.scss

@@ -0,0 +1,6 @@
+.modal-content {
+  max-width: 500px; /* 限制模态框的最大宽度 */
+  margin: auto; /* 居中 */
+  padding: 20px; /* 内边距 */
+  text-align: center; /* 内容居中 */
+}

+ 24 - 0
soul-app/src/app/customer-service/customer-service.component.spec.ts

@@ -0,0 +1,24 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+import { IonicModule } from '@ionic/angular';
+
+import { CustomerServiceComponent } from './customer-service.component';
+
+describe('CustomerServiceComponent', () => {
+  let component: CustomerServiceComponent;
+  let fixture: ComponentFixture<CustomerServiceComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      declarations: [ CustomerServiceComponent ],
+      imports: [IonicModule.forRoot()]
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(CustomerServiceComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 22 - 0
soul-app/src/app/customer-service/customer-service.component.ts

@@ -0,0 +1,22 @@
+import { Component, OnInit } from '@angular/core';
+import { IonButton, IonButtons, IonContent, IonHeader, IonTitle, IonToolbar, ModalController } from '@ionic/angular/standalone';
+
+@Component({
+  selector: 'app-customer-service',
+  templateUrl: './customer-service.component.html',
+  styleUrls: ['./customer-service.component.scss'],
+  standalone: true,
+  imports: [IonHeader,IonToolbar,IonTitle,IonButtons,IonButton,
+    IonContent
+  ],
+})
+export class CustomerServiceComponent  implements OnInit {
+
+  constructor(private modalCtrl: ModalController) { }
+
+  ngOnInit() {}
+  closeModal() {
+    this.modalCtrl.dismiss(); // 关闭模态框
+  }
+
+}

+ 22 - 21
soul-app/src/app/tab2/tab2.page.html

@@ -1,12 +1,13 @@
-<ion-header [translucent]="true">
+<ion-header>
   <ion-toolbar>
     <ion-item lines="none">
-      <!-- 图片 -->
-      <ion-avatar slot="start" class="image-container">
-        <img src="/assets/img/3.png" alt="Your Image" class="header-image">
-      </ion-avatar>
       <!-- 搜索框 -->
-      <ion-searchbar slot="end" expand="with-icon"></ion-searchbar>
+      <ion-searchbar slot="end" expand="with-icon" [placeholder]="placeholderText" class="custom-searchbar">
+      </ion-searchbar>
+      <!-- 客服图标 -->
+      <ion-button slot="end" (click)="openCustomerService()" class="customer-service-button" color="light">
+        <ion-icon name="headset" style="color: black;"></ion-icon>
+      </ion-button>
     </ion-item>
   </ion-toolbar>
 </ion-header>
@@ -36,26 +37,26 @@
     </ion-card>
   </section>
 
-  <!-- 聊服务区 -->
+  <!-- 聊服务区 -->
   <section>
-    <!--<ion-card>
-      <ion-card-header>
-        <ion-card-title>普通聊天</ion-card-title>
-      </ion-card-header>
-      <ion-card-content>
-        <p>在这里,无论是生活上的开心,还是工作上的糟糕,你都可以跟我分享,这里是属于你一个人的空间,你可以大胆放心的使用。</p>
-        <ion-button expand="block" color="danger" (click)="goChat()">开始聊天</ion-button>
-      </ion-card-content>
-    </ion-card>-->
     <ion-card>
       <ion-card-header>
-        <div style="display: flex; justify-content: space-between; align-items: center;">
-          <ion-card-title style="flex: 1;">普通聊天</ion-card-title>
-          <ion-button (click)="goChat()" style="margin-left: 0px;">开始聊天</ion-button>
-        </div>
+        <ion-card-title style="flex: 1;">普通聊天</ion-card-title>
       </ion-card-header>
       <ion-card-content>
-        <p style="font-size: 16px; text-indent:2em;">在这里,无论是生活上的开心,还是工作上的糟糕,你都可以跟我分享,这里是属于你一个人的空间,你可以大胆放心的使用。</p>
+        <ion-list>
+        <ion-item *ngFor="let chatcompanion of chatcompanionList">
+          <ion-avatar slot="start">
+            <img [src]="chatcompanion.get('avatar') || '/assets/img/2.png'" [alt]="chatcompanion.get('name')"/>
+          </ion-avatar>
+          <ion-label>
+            <h2>{{ chatcompanion.get('name') }}</h2>
+            <p style="font-size: 15px;">个人简介:{{ chatcompanion.get('bio') }}</p>
+          </ion-label>
+          <ion-button (click)="goChat(chatcompanion)" style="margin-left: 0px;">开始聊天</ion-button>
+        </ion-item>
+        <p style="font-size: 16px; text-indent:2em;">在这里,我是你的聊天伙伴,无论是生活上的开心,还是工作上的糟糕,你都可以跟我分享,这里是属于你一个人的空间,你可以大胆放心的使用。</p>
+        </ion-list>
       </ion-card-content>
     </ion-card>
   </section>

+ 6 - 0
soul-app/src/app/tab2/tab2.page.scss

@@ -48,4 +48,10 @@ ion-avatar {
     margin-top: 8px; /* 上间距,仅在需要时添加 */
     text-transform: none; /* 按钮文字不变形 */
     height: 35px;
+}
+
+
+.custom-searchbar {
+  --border-radius: 20px; /* 调整弯曲程度 */
+  --background: #f0f0f0; /* 可以自定义背景颜色 */
 }

+ 122 - 16
soul-app/src/app/tab2/tab2.page.ts

@@ -2,13 +2,14 @@ import { Component,CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
 import { IonHeader, IonToolbar, IonTitle, IonContent } from '@ionic/angular/standalone';
 import { ExploreContainerComponent } from '../explore-container/explore-container.component';
 import { IonButton, IonCard, IonCardContent, IonCardHeader, IonCardTitle, IonItem, IonLabel,
-   IonList,IonSelect, IonSelectOption } from '@ionic/angular/standalone';
+   IonList,IonSelect, IonSelectOption,IonIcon } from '@ionic/angular/standalone';
 import { CommonModule } from '@angular/common';
 import { Router } from '@angular/router';
 import { ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
 import { ModalController } from '@ionic/angular/standalone';
 import { CloudObject, CloudQuery, CloudUser } from 'src/lib/ncloud';
 import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
+import { CustomerServiceComponent } from '../customer-service/customer-service.component';
 
 @Component({
   selector: 'app-tab2',
@@ -19,17 +20,40 @@ import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-log
     IonCard,IonCardHeader,IonCardTitle,IonCardContent,
     IonItem,IonLabel,
     IonSelect,IonSelectOption,
-    IonButton,IonList,
+    IonButton,IonList,IonIcon,
     CommonModule
   ],
   schemas: [CUSTOM_ELEMENTS_SCHEMA],
 })
 export class Tab2Page {
 
+  
+  isModalOpen: boolean = false; // 定义 isModalOpen 属性
+  placeholderText: string = '';
+  placeholderIndex: number = 0;
+  placeholders: string[] = ['智能陪聊', '普通聊天', '小贴心建议'];
+  cyclePlaceholder() {
+    setInterval(() => {
+      this.placeholderText = this.placeholders[this.placeholderIndex];
+      this.placeholderIndex = (this.placeholderIndex + 1) % this.placeholders.length;
+    }, 2000); // 每2秒切换一次
+  }
+  async openCustomerService() {
+    const modal = await this.modalCtrl.create({
+      component: CustomerServiceComponent,
+      breakpoints: [0.3, 0.4],
+      initialBreakpoint: 0.3,
+    });
+    return await modal.present();
+  }
+
+
+
   private modalCtrl: ModalController;
   constructor(private router: Router,modalCtrl: ModalController) {
     this.modalCtrl = modalCtrl;
     // 其他构造函数代码
+    this.cyclePlaceholder();
   }
   async clickToConsult(chatpartner:CloudObject) {
     // 验证用户登录
@@ -51,9 +75,7 @@ export class Tab2Page {
     if(currentUser?.get("age")){
       userPrompt += `,年龄:${currentUser?.get("age")}`
     }
-    //if (currentUser?.get("avatar")) {
-      //userPrompt += `,头像:${currentUser?.get("avatar")}`;
-    //}
+
     // 弹窗形式聊天:开始聊天
     localStorage.setItem("company","E4KpGvTEto")
     let consult = new CloudObject("ChatRecord")
@@ -82,7 +104,7 @@ export class Tab2Page {
         chat.role.set("bio",chatpartner.get("bio"));
         chat.role.set("expertise", chatpartner.get("expertise"));
         chat.role.set("avatar", chatpartner.get("avatar") || "/assets/img/2.png")//设置陪聊师头像
-        //chat.role.set("avatar", currentUser.get("avatar") || "/assets/img/2.png"); // 设置用户头像
+
         chat.role.set("prompt", `
         # 角色设定
         您是${chatpartner.get("name")},一位${chatpartner.get("bio")},${chatpartner.get("expertise")},需要为用户提供陪伴和支持等积极情绪。
@@ -98,8 +120,6 @@ export class Tab2Page {
         let content:any = message?.content
         if(typeof content == "string"){
           messages.push(content); // 将新消息添加到 messages 数组中
-          //if(content?.indexOf("[祝你有愉快的一天]")>-1){
-            //console.log("对话结束")
             consult.set({
               content: messages.join("\n"), // 将所有消息合并为一个字符串
             });
@@ -119,10 +139,94 @@ export class Tab2Page {
     }
     openChatPanelModal(this.modalCtrl,options)
   }
-  //selectedIssue:string='';
-
   matchedCounselor: { name: string; specialty: string } | null = null;
 
+  async goChat(chatcompanion:CloudObject) {
+    // 验证用户登录
+    let currentUser = new CloudUser();
+    let userPrompt = ``
+    if(!currentUser?.id){
+      console.log("用户未登录,请登录后重试")
+      let user = await openUserLoginModal(this.modalCtrl)
+      if(!user?.id){
+        return
+      }     
+    }
+    if(currentUser?.get("username")){
+      userPrompt += `当前来访的用户,姓名:${currentUser?.get("username")}`
+    }
+    if(currentUser?.get("gender")){
+      userPrompt += `,性别:${currentUser?.get("gender")}`
+    }
+    if(currentUser?.get("age")){
+      userPrompt += `,年龄:${currentUser?.get("age")}`
+    }
+
+    // 弹窗形式聊天:开始聊天
+    localStorage.setItem("company","E4KpGvTEto")
+    let consult = new CloudObject("ChatRecord")
+    let messages: string[] = [];
+    let now = new Date();
+    let dateStr = `${now.getFullYear()}-${now.getMonth()+1}-${now.getDate()}`
+    //对象权限的精确制定
+    let ACL:any = {//公开访客 不可读 不可写
+      "*":{read:false,write:false}
+    }
+    if(currentUser?.id){//当前用户 可读 可写
+      ACL[currentUser?.id] = {read:true,write:true}
+    }
+    consult.set({
+      title:`${chatcompanion.get('expertise') || ""}领域聊天记录${dateStr}-${chatcompanion.get('name')}`,
+      chatcompanion:chatcompanion.toPointer(),
+      user:currentUser.toPointer(),
+      ACL:ACL
+    })
+    let options:ChatPanelOptions = {
+      roleId:"2DXJkRsjXK",
+      onChatInit: (chat: FmodeChat) => {
+        console.log("onChatInit");
+        console.log("预设角色", chat.role);
+        chat.role.set("name", chatcompanion.get("name"));
+        chat.role.set("bio",chatcompanion.get("bio"));
+        chat.role.set("expertise", chatcompanion.get("expertise"));
+        chat.role.set("avatar", chatcompanion.get("avatar") || "/assets/img/2.png")//设置聊天伙伴头像
+
+        chat.role.set("prompt", `
+        # 角色设定
+        您是${chatcompanion.get("name")},一位${chatcompanion.get("bio")}的聊天伙伴,需要为用户提供陪伴和支持等积极情绪。
+        # 开始话语
+        当您准备好了,可以以一个关心用户的朋友的身份,向来访的用户打招呼。
+        # 对话环节
+        耐心与用户聊天,给人一种暖心陪伴的感觉
+        ${userPrompt}
+        `);
+      },
+      onMessage:(chat:FmodeChat,message:FmodeChatMessage)=>{
+        console.log("onMessage",message)
+        let content:any = message?.content
+        if(typeof content == "string"){
+          messages.push(content); // 将新消息添加到 messages 数组中
+            consult.set({
+              content: messages.join("\n"), // 将所有消息合并为一个字符串
+            });
+            //consult.save();
+            consult.save().then(() => {
+              console.log("聊天记录已保存");
+          }).catch((error) => {
+              console.error("保存聊天记录时出错:", error);
+          });
+          //}
+        }
+      },
+        // chat?.chatSession?.id 本次会话的 chatId
+        onChatSaved:(chat:FmodeChat)=>{
+        console.log("onChatSaved",chat,chat?.chatSession,chat?.chatSession?.id)
+      }
+    }
+    openChatPanelModal(this.modalCtrl,options)
+
+  }
+
   questions = [
     {
       title: '如何应对焦虑?',
@@ -141,13 +245,10 @@ export class Tab2Page {
     },
   ];
 
-  matchCounselor() {
-  }
-  goChat(){
-      this.router.navigateByUrl("/chat/session/role/2DXJkRsjXK")
-  } 
+  //matchCounselor() {
   ngOnInit() {
-    this.loadChatPartnerList()
+    this.loadChatPartnerList(),
+    this.loadChatCompanionList()
   }
 
   chatpartnerList:Array<CloudObject>=[]
@@ -155,4 +256,9 @@ export class Tab2Page {
     let query = new CloudQuery("ChatPartner");
     this.chatpartnerList = await query.find()
   }
+  chatcompanionList:Array<CloudObject>=[]
+  async loadChatCompanionList(){
+    let query = new CloudQuery("ChatCompanion");
+    this.chatcompanionList = await query.find()
+  }
 }

+ 2 - 2
soul-app/src/app/tabs/tabs.page.ts

@@ -1,7 +1,7 @@
 import { Component, EnvironmentInjector, inject } from '@angular/core';
 import { IonTabs, IonTabBar, IonTabButton, IonTitle,IonIcon, IonLabel } from '@ionic/angular/standalone';
 import { addIcons } from 'ionicons';
-import { home, chatbubbles, person,chevronBack } from 'ionicons/icons';
+import { home, chatbubbles, person,chevronBack, headset, } from 'ionicons/icons';
 
 
 @Component({
@@ -15,7 +15,7 @@ export class TabsPage {
   public environmentInjector = inject(EnvironmentInjector);
 
   constructor() {
-    addIcons({ home, chatbubbles, person ,chevronBack});
+    addIcons({ home, chatbubbles, person ,chevronBack,headset});
   }
 
 }