Selaa lähdekoodia

feat: new open consult in tab2

Fmode 5 päivää sitten
vanhempi
commit
cb22110a06

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 637 - 16
package-lock.json


+ 1 - 0
package.json

@@ -27,6 +27,7 @@
     "@capacitor/keyboard": "7.0.1",
     "@capacitor/status-bar": "7.0.1",
     "@ionic/angular": "^8.0.0",
+    "fmode-ng": "^0.0.82",
     "ionicons": "^7.0.0",
     "rxjs": "~7.8.0",
     "swiper": "^11.2.6",

+ 4 - 4
src/app/tab1/tab1.page.html

@@ -193,7 +193,7 @@
         <ion-button fill="clear" size="small">更多</ion-button>
       </div>
       <!-- 替换原来的 ion-slides 部分 -->
-<swiper-container [init]="false" [config]="swiperConfig">
+<!-- <swiper-container [init]="false" [config]="swiperConfig">
   <swiper-slide *ngFor="let course of recentCourses">
     <ion-card class="course-card">
       <img [src]="course.image" alt="course image">
@@ -207,7 +207,7 @@
       <ion-progress-bar *ngIf="course.progress" [value]="course.progress"></ion-progress-bar>
     </ion-card>
   </swiper-slide>
-</swiper-container>
+</swiper-container> -->
     </div>
     
     <!-- 热门课程 -->
@@ -218,7 +218,7 @@
       </div>
 
     </div>
-    <swiper-container [init]="false" [config]="swiperConfig">
+    <!-- <swiper-container [init]="false" [config]="swiperConfig">
       <swiper-slide *ngFor="let course of recentCourses">
         <ion-card class="course-card">
           <img [src]="course.image" alt="course image">
@@ -232,7 +232,7 @@
           <ion-progress-bar *ngIf="course.progress" [value]="course.progress"></ion-progress-bar>
         </ion-card>
       </swiper-slide>
-    </swiper-container>
+    </swiper-container> -->
   </div>
 
   <!-- 为你推荐 -->

+ 7 - 7
src/app/tab1/tab1.page.ts

@@ -21,10 +21,10 @@ import {
 } from '@ionic/angular/standalone';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
-import { register } from 'swiper/element/bundle';
+// import { register } from 'swiper/element/bundle';
 
 // 注册 Swiper 组件
-register();
+// register();
 
 @Component({
   selector: 'app-tab1',
@@ -53,11 +53,11 @@ export class Tab1Page {
   currentSearchHint = this.searchHints[0];
   
   // Swiper 配置
-  swiperConfig = JSON.stringify({
-    slidesPerView: 'auto',
-    spaceBetween: 8,
-    freeMode: true
-  });
+  // swiperConfig = JSON.stringify({
+  //   slidesPerView: 'auto',
+  //   spaceBetween: 8,
+  //   freeMode: true
+  // });
 
   // 快速入口分类
   quickAccessItems = [

+ 33 - 4
src/app/tab2/tab2.page.html

@@ -42,17 +42,46 @@
 
   <!-- AI咨询 -->
   <ion-list-header>
-    <ion-label>AI咨询</ion-label>
+    <ion-label>私教咨询(免费)</ion-label>
+    
   </ion-list-header>
 
+  <ion-card class="coach-card">
+      <div class="avatar-container">
+        <img src="/assets/avatars/jiaolian1.jpg" alt="教练头像" class="coach-avatar" />
+      </div>
+      
+      <ion-card-header>
+        <ion-card-title class="coach-name">宋珀尔</ion-card-title>
+        <ion-card-subtitle class="coach-title">专业教练</ion-card-subtitle>
+      </ion-card-header>
+
+      <ion-card-content>
+        <div class="specialties-section">
+          <ion-note color="medium" class="section-title">擅长运动</ion-note>
+          <div class="specialties-container">
+            <ion-chip *ngFor="let specialty of ['跑步', '动感单车']" class="specialty-chip">
+              <ion-label>{{ specialty }}</ion-label>
+            </ion-chip>
+          </div>
+        </div>
+        
+        <ion-button (click)="openConsult()" expand="block" shape="round" class="consult-btn">
+          <ion-icon name="chatbubble-ellipses" slot="start"></ion-icon>
+          咨询
+        </ion-button>
+      </ion-card-content>
+    </ion-card>
+
   <div class="deepseek-input-container">
     <div class="deepseek-input">
-      <ion-textarea 
+      <!-- <ion-textarea 
         rows="1"
         placeholder="输入您的训练问题..."
         class="deepseek-textarea"
-      ></ion-textarea>
-      <ion-button fill="clear" class="send-button">
+      ></ion-textarea> -->
+      <ion-button fill="clear" expand="block">
+         健身教练
         <ion-icon name="send" color="primary"></ion-icon>
       </ion-button>
     </div>

+ 72 - 0
src/app/tab2/tab2.page.scss

@@ -1,3 +1,75 @@
+
+// 教练卡片
+/* coach-card.component.scss */
+.coach-card {
+  padding-top: 60px;
+  max-width: 350px;
+  margin: 16px auto;
+  border-radius: 16px;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+  overflow: visible;
+  
+  .avatar-container {
+    display: flex;
+    justify-content: center;
+    margin-top: -50px;
+  }
+  
+  .coach-avatar {
+    width: 100px;
+    height: 100px;
+    border-radius: 50%;
+    object-fit: cover;
+    border: 4px solid white;
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+  }
+  
+  .coach-name {
+    text-align: center;
+    font-weight: bold;
+    font-size: 1.5rem;
+    margin-top: 8px;
+    color: #2c3e50;
+  }
+  
+  .coach-title {
+    text-align: center;
+    font-size: 0.9rem;
+    color: #7f8c8d;
+  }
+  
+  .specialties-section {
+    margin: 20px 0;
+    
+    .section-title {
+      display: block;
+      margin-bottom: 8px;
+      font-size: 0.9rem;
+    }
+    
+    .specialties-container {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      justify-content: center;
+    }
+    
+    .specialty-chip {
+      --background: #f1f5f9;
+      --color: #334155;
+    }
+  }
+  
+  .consult-btn {
+    --background: linear-gradient(135deg, #3b82f6, #6366f1);
+    --background-hover: #4f46e5;
+    --background-activated: #4f46e5;
+    --color: white;
+    margin-top: 16px;
+    font-weight: 600;
+  }
+}
+
 /* 全局卡片样式 */
 ion-card {
   --background: #ffffff;

+ 162 - 9
src/app/tab2/tab2.page.ts

@@ -2,7 +2,8 @@ import { CommonModule } from '@angular/common';
 import { Component } from '@angular/core';
 import { IonTextarea } from '@ionic/angular/standalone';
 import { 
-  IonHeader, IonToolbar, IonTitle, IonButtons, IonButton, IonIcon,
+  ModalController,
+  IonHeader,IonNote,IonChip, IonToolbar, IonTitle, IonButtons, IonButton, IonIcon,
   IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle,
   IonCardContent, IonProgressBar, IonGrid, IonRow, IonCol, IonListHeader,
   IonLabel, IonList, IonItem, IonAvatar, IonBadge, IonFab, IonFabButton
@@ -14,6 +15,9 @@ import {
   flame, bicycle, trophy
 } from 'ionicons/icons';
 
+// 引用fmode-ng智能体组件
+import { ChatPanelOptions, FmChatModalInput, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
+
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
@@ -21,7 +25,8 @@ import {
   standalone: true,
   imports: [
     CommonModule,
-    IonTextarea,
+    // IonTextarea,
+    IonNote,IonChip,
     IonHeader, IonToolbar, IonTitle, IonButtons, IonButton, IonIcon,
     IonContent, IonCard, IonCardHeader, IonCardTitle, IonCardSubtitle,
     IonCardContent, IonProgressBar, IonGrid, IonRow, IonCol, IonListHeader,
@@ -29,6 +34,161 @@ import {
   ]
 })
 export class Tab2Page {
+
+  openConsult(chatId?:string){
+    localStorage.setItem("company","E4KpGvTEto")
+    let options:ChatPanelOptions = {
+      roleId:"2DXJkRsjXK", // 预设,无需更改
+      // chatId:chatId, // 若存在,则恢复会话。若不存在,则开启新会话
+      onChatInit:(chat:FmodeChat)=>{
+        console.log("onChatInit");
+        console.log("Chat类",chat);
+        console.log("预设角色",chat.role);
+        // 角色名称
+        chat.role.set("name","宋珀尔");
+        // 角色称号
+        chat.role.set("title","专业教练");
+        // 角色描述
+        chat.role.set("desc","一名亲切和蔼的健身教练,宋珀尔,年龄26岁");
+        // 角色标签
+        chat.role.set("tags",['跑步', '动感单车']);
+        // 角色头像
+        chat.role.set("avatar","/assets/avatars/jiaolian1.jpg")
+        // 角色提示词
+        chat.role.set("prompt",`
+# 角色设定
+您是一名亲切和蔼的健身教练,宋珀尔,年龄26岁,需要您解答用户健身方面的专业问题。
+`);
+        // 对话灵感分类
+        let promptCates = [
+          {
+            "img": "/assets/icon/yy.jpg",
+            "name": "有氧"
+          },
+          {
+            "img": "/assets/icon/jz.jpg",
+            "name": "减脂"
+          },
+          {
+            "img": "/assets/icon/zj.jpg",
+            "name": "增肌"
+          }
+        ]
+        setTimeout(() => {
+          chat.role.set("promptCates",promptCates)
+        }, 500);
+        // 对话灵感列表
+        let promptList = [
+          {
+            cate:"有氧",img:"/assets/icon/yy.jpg",
+            messageList:[
+            "有氧运动多久才能有效减脂?",  
+            "跑步和游泳哪个减肥效果更好?",  
+            "空腹有氧真的更燃脂吗?",  
+            "有氧运动会不会掉肌肉?",  
+            "心率控制在多少才能高效燃脂?",  
+            "每天做有氧运动会不会过度疲劳?",  
+            "有氧运动前要不要吃东西?",  
+            "椭圆机和跑步机哪个更适合新手?",  
+            "跳绳会不会伤膝盖?",  
+            "有氧运动后怎么补充能量?"  
+          ]
+          },
+          {
+            cate:"减脂",img:"/assets/icon/jz.jpg",
+            messageList:[
+              "减脂一定要做有氧吗?",  
+              "为什么体重没变但看起来瘦了?",  
+              "局部减脂(如瘦肚子)真的存在吗?",  
+              "减脂期每天应该吃多少热量?",  
+              "低碳饮食和低脂饮食哪个更适合减脂?",  
+              "为什么运动后体重反而增加了?",  
+              "减脂期可以吃零食吗?",  
+              "平台期怎么突破?",  
+              "晚上吃东西会不会更容易长胖?",  
+              "减脂期要不要计算蛋白质摄入?"  
+            ]
+          },
+          {
+            cate:"增肌",img:"/assets/icon/zj.jpg",
+            messageList: [
+            "增肌一定要喝蛋白粉吗?",  
+            "为什么练了很久肌肉不长?",  
+            "增肌期可以同时减脂吗?",  
+            "训练后多久补充蛋白质最有效?",  
+            "增肌需要每天练同一个部位吗?",  
+            "徒手训练(如俯卧撑)能有效增肌吗?",  
+            "增肌期体重不增长是怎么回事?",  
+            "肌肉酸痛还能继续练吗?",  
+            "增肌训练每组做多少次最合适?",  
+            "睡眠对增肌的影响有多大?"  
+          ]
+          },
+        ]
+        let ChatPrompt = Parse.Object.extend("ChatPrompt");
+        setTimeout(() => {
+          chat.promptList = promptList.map(item=>{
+            let prompt = new ChatPrompt();
+            prompt.set(item);
+            prompt.img = item.img;
+            return prompt;
+          })
+        }, 500);
+
+        // 功能按钮区域预设
+        chat.leftButtons = [
+          { // 提示 当角色配置预设提示词时 显示
+           title:"话题灵感", // 按钮标题
+           showTitle:true, // 是否显示标题文字
+           icon:"color-wand-outline", // 标题icon图标
+           onClick:()=>{ // 按钮点击事件
+               chat.isPromptModalOpen = true
+           },
+           show:()=>{ // 按钮显示条件
+             return chat?.promptList?.length // 存在话题提示词时显示
+           }
+         },
+      ]
+
+      },
+      onMessage:(chat:FmodeChat,message:FmodeChatMessage)=>{
+        console.log("onMessage",message)
+        let content:any = message?.content
+        if(typeof content == "string"){
+          // 根据阶段标记判断下一步处理过程
+          if (content.includes('[导诊完成]')) {
+            // 进入问诊环节
+            console.log('进入问诊环节');
+          } else if (content.includes('[问诊完成]')) {
+            // 进入检查环节
+            console.log('进入检查环节');
+          } else if (content.includes('[检查完成]')) {
+            // 进入诊断与处方环节
+            console.log('进入诊断与处方环节');
+          } else if (content.includes('[处方完成]')) {
+            // 结束会话或其他逻辑
+            console.log('结束会话');
+          }
+        }
+      },
+      onChatSaved:(chat:FmodeChat)=>{
+        // chat?.chatSession?.id 本次会话的 chatId
+        console.log("onChatSaved",chat,chat?.chatSession,chat?.chatSession?.id)
+      }
+    }
+    openChatPanelModal(this.modalCtrl,options)
+  }
+
+    constructor(
+    private modalCtrl:ModalController,
+  ) {
+    addIcons({ 
+      notificationsOutline, calendarNumber, checkmarkCircle, ellipseOutline,
+      timeOutline, barbell, body, walk, add, refresh, fitness,
+      flame, bicycle,  trophy
+    });
+  }
+
   // 周训练数据
   weekDays = [
     { shortName: '周一', name: 'Monday', trained: true, active: false },
@@ -103,13 +263,6 @@ export class Tab2Page {
     }
   ];
 
-  constructor() {
-    addIcons({ 
-      notificationsOutline, calendarNumber, checkmarkCircle, ellipseOutline,
-      timeOutline, barbell, body, walk, add, refresh, fitness,
-      flame, bicycle,  trophy
-    });
-  }
 
   refreshPlans() {
     console.log('刷新计划数据');

+ 7 - 7
src/app/tabs/tabs.routes.ts

@@ -6,11 +6,11 @@ export const routes: Routes = [
     path: 'tabs',
     component: TabsPage,
     children: [
-      {
-        path: 'tab1',
-        loadComponent: () =>
-          import('../tab1/tab1.page').then((m) => m.Tab1Page),
-      },
+      // {
+      //   path: 'tab1',
+      //   loadComponent: () =>
+      //     import('../tab1/tab1.page').then((m) => m.Tab1Page),
+      // },
       {
         path: 'tab2',
         loadComponent: () =>
@@ -28,14 +28,14 @@ export const routes: Routes = [
       },
       {
         path: '',
-        redirectTo: '/tabs/tab1',
+        redirectTo: '/tabs/tab2',
         pathMatch: 'full',
       },
     ],
   },
   {
     path: '',
-    redirectTo: '/tabs/tab1',
+    redirectTo: '/tabs/tab2',
     pathMatch: 'full',
   },
 ];

BIN
src/assets/avatars/jiaolian1.jpg


BIN
src/assets/icon/jz.jpg


BIN
src/assets/icon/yy.jpg


BIN
src/assets/icon/zj.jpg


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