Browse Source

update;page-crm-training

0235701 16 hours ago
parent
commit
02ede558d7

+ 14 - 1
.hintrc

@@ -17,6 +17,19 @@
         ]
       }
     ],
-    "meta-viewport": "off"
+    "meta-viewport": "off",
+    "axe/forms": [
+      "default",
+      {
+        "select-name": "off"
+      }
+    ],
+    "axe/aria": [
+      "default",
+      {
+        "aria-allowed-attr": "off",
+        "aria-required-parent": "off"
+      }
+    ]
   }
 }

+ 19 - 5
ai-assisant/src/app/app.ts

@@ -1,9 +1,11 @@
 import { Component } from '@angular/core';
 import { RouterModule, RouterOutlet } from '@angular/router';
-/*
-import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
-import { PageCrmHome } from '../modules/crm/mobile/page-crm-home/page-crm-home';
-*/
+ import { NgModule } from '@angular/core';
+ import { FormsModule } from '@angular/forms';
+
+ import { PageCrmTraining } from '../modules/crm/mobile/page-crm-training/page-crm-training';
+ import { TrainingReportsComponent } from '../modules/crm/mobile/page-crm-training/report-viewer.component';
+
 @Component({
   selector: 'app-root',
   standalone: true,
@@ -13,4 +15,16 @@ import { PageCrmHome } from '../modules/crm/mobile/page-crm-home/page-crm-home';
 })
 export class App {
   protected title = 'ai-assistant';
-}
+}
+ @NgModule({
+  imports: [
+     FormsModule,
+    PageCrmTraining,
+     TrainingReportsComponent,
+    // 其他模块...
+  ],
+  declarations: [
+  ]
+
+})
+ export class CrmModule { }

+ 25 - 3
ai-assisant/src/lib/ncloud.ts

@@ -269,7 +269,7 @@ export class CloudUser extends CloudObject {
             // 设置用户信息
             this.id = userData?.objectId;
             this.sessionToken = userData?.sessionToken;
-            this.data = userData; // 保存用户数据
+            this.set(userData); // 保存用户数据
         }
     }
 
@@ -280,6 +280,28 @@ export class CloudUser extends CloudObject {
             console.error("用户未登录");
             return null;
         }
+        // 补充:从服务器拉取最新数据(可选,确保数据新鲜)
+           try {
+    const response = await fetch(serverURL + `/users/me`, {
+      headers: {
+        "x-parse-application-id": "dev",
+        "x-parse-session-token": this.sessionToken
+      },
+      method: "GET"
+    });
+
+    if (!response.ok) {
+      throw new Error("获取用户信息失败");
+    }
+
+    const result = await response.json();
+    this.set(result); // 接口成功时更新数据
+    return this;
+  } catch (error) {
+    console.error("current() 调用失败:", error);
+    this.clearUserCache(); // 出错时清除缓存,避免使用旧数据
+    return null;
+  }
         return this;
         // const response = await fetch(serverURL + `/users/me`, {
         //     headers: {
@@ -317,7 +339,7 @@ export class CloudUser extends CloudObject {
         // 设置用户信息
         this.id = result?.objectId;
         this.sessionToken = result?.sessionToken;
-        this.data = result; // 保存用户数据
+         this.set(result);  // 保存用户数据
         // 缓存用户信息
         console.log(result)
         localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
@@ -390,7 +412,7 @@ export class CloudUser extends CloudObject {
         localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
         this.id = result?.objectId;
         this.sessionToken = result?.sessionToken;
-        this.data = result; // 保存用户数据
+        this.set(result); // 保存用户数据
         return this;
     }
 

+ 75 - 41
ai-assisant/src/modules/crm/mobile/page-crm-home/page-crm-home.html

@@ -41,7 +41,7 @@
         
         <!-- 欢迎区域 -->
         <div class="welcome-section">
-            <h1 class="greeting">你好,<span>销售精英</span> 👋</h1>
+  <h1 class="greeting">你好,<span>{{ '销售精英' }}</span> 👋</h1>
             <div class="motivational-text">
                 {{ motivationalText }}
             </div>
@@ -165,47 +165,81 @@
             </div>
         </div>
 
-        <!-- 个人信息弹窗 -->
-        <div class="popup-overlay" *ngIf="showProfilePopup" (click)="toggleProfilePopup()">
-            <div class="popup-container" (click)="$event.stopPropagation()">
-                <div class="popup-header">
-                    <h3>个人信息</h3>
-                    <button (click)="toggleProfilePopup()"><fa-icon [icon]="icons.faTimes"></fa-icon></button>
-                </div>
-                <div class="profile-content">
-                    <div class="profile-avatar">
-                        <fa-icon [icon]="icons.faUserCircle"></fa-icon>
-                    </div>
-                    <div class="profile-details">
-                        <div class="detail-item">
-                            <span class="detail-label">姓名:</span>
-                            <span class="detail-value">{{ user.name }}</span>
-                        </div>
-                        <div class="detail-item">
-                            <span class="detail-label">职位:</span>
-                            <span class="detail-value">{{ user.role }}</span>
-                        </div>
-                        <div class="detail-item">
-                            <span class="detail-label">年龄:</span>
-                            <span class="detail-value">{{ user.age }}</span>
-                        </div>
-                        <div class="detail-item">
-                            <span class="detail-label">出生日期:</span>
-                            <span class="detail-value">{{ user.birthDate }}</span>
-                        </div>
-                        <div class="detail-item">
-                            <span class="detail-label">训练次数:</span>
-                            <span class="detail-value">{{ user.trainingCount }} 次</span>
-                        </div>
-                        <div class="detail-item">
-                            <button class="btn-danger" (click)="logout()">
-                                <i class="fas fa-sign-out-alt"></i> 登出
-                            </button>
-                        </div>
-                    </div>
-                </div>
-            </div>
+       <!-- 个人信息弹窗 -->
+<div class="popup-overlay" *ngIf="showProfilePopup" (click)="toggleProfilePopup()">
+  <div class="popup-container" (click)="$event.stopPropagation()">
+    <div class="popup-header">
+      <h3>个人信息</h3>
+      <button (click)="toggleProfilePopup()">
+        <fa-icon [icon]="icons.faTimes"></fa-icon>
+      </button>
+    </div>
+
+    <div class="profile-content">
+      <!-- 加载状态 -->
+      <div *ngIf="loadingProfile" class="loading">
+        <i class="fas fa-spinner fa-spin"></i> 加载中...
+      </div>
+
+      <!-- 未登录状态 -->
+      <div *ngIf="!loadingProfile && !isLoggedIn" class="not-logged-in">
+        请先登录查看个人信息
+      </div>
+
+      <!-- 已登录状态:显示完整信息 -->
+      <div *ngIf="!loadingProfile && isLoggedIn && currentUser" class="profile-info">
+        <div class="profile-avatar">
+          <fa-icon [icon]="icons.faUserCircle"></fa-icon>
+        </div>
+
+        <div class="profile-details">
+          <!-- 基础信息 -->
+          <div class="detail-item">
+            <span class="detail-label">用户名:</span>
+            <span class="detail-value">{{ currentUser.username || '未设置' }}</span>
+          </div>
+
+          <!-- 注册时填写的扩展字段 -->
+          
+
+          <div class="detail-item">
+            <span class="detail-label">年龄:</span>
+            <span class="detail-value">{{ currentUser.age || '未设置' }}</span>
+          </div>
+
+          <div class="detail-item">
+            <span class="detail-label">出生日期:</span>
+            <span class="detail-value">
+           {{ currentUser.data.birthDate ? (currentUser.birthDate | date:'yyyy-MM-dd') : '未设置' }} </span>
+          </div>
+
+          <div class="detail-item">
+            <span class="detail-label">职位:</span>
+            <span class="detail-value">{{ currentUser.data.position || '未设置' }}</span>
+          </div>
+
+          <!-- 系统自动记录的字段 -->
+          <div class="detail-item">
+            <span class="detail-label">角色:</span>
+            <span class="detail-value">{{ currentUser.data.role || '未设置' }}</span>
+          </div>
+
+          <div class="detail-item">
+            <span class="detail-label">训练次数:</span>
+            <span class="detail-value">{{ currentUser.data?.trainingCount || 0 }} 次</span>
+          </div>
+
+          <!-- 登出按钮 -->
+          <div class="detail-item action-item">
+            <button class="btn-danger" (click)="logout()">
+              <i class="fas fa-sign-out-alt"></i> 退出登录
+            </button>
+          </div>
         </div>
+      </div>
+    </div>
+  </div>
+</div>
         
         <!-- 底部版权信息 -->
         <div class="footer">

+ 68 - 0
ai-assisant/src/modules/crm/mobile/page-crm-home/page-crm-home.scss

@@ -912,4 +912,72 @@ app-login-modal {
   .modal-overlay {
     pointer-events: auto; /* 恢复弹窗区域的点击 */
   }
+}
+
+.profile-content {
+  padding: 20px;
+}
+
+.loading {
+  text-align: center;
+  padding: 30px 0;
+  color: #666;
+}
+
+.not-logged-in {
+  text-align: center;
+  padding: 30px 0;
+  color: #666;
+  font-size: 14px;
+}
+
+.profile-info {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.profile-avatar {
+  margin-bottom: 20px;
+  font-size: 60px;
+  color: #007bff;
+}
+
+.detail-item {
+  width: 100%;
+  padding: 8px 0;
+  border-bottom: 1px solid #f0f0f0;
+  display: flex;
+  justify-content: space-between;
+}
+
+.detail-label {
+  color: #666;
+  font-weight: 500;
+}
+
+.detail-value {
+  color: #333;
+}
+
+.action-item {
+  margin-top: 15px;
+  border-bottom: none;
+  justify-content: center;
+}
+
+.btn-danger {
+  background-color: #dc3545;
+  color: white;
+  border: none;
+  padding: 6px 16px;
+  border-radius: 4px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 5px;
+}
+
+.btn-danger:hover {
+  background-color: #bb2d3b;
 }

+ 60 - 66
ai-assisant/src/modules/crm/mobile/page-crm-home/page-crm-home.ts

@@ -34,9 +34,10 @@ export class PageCrmHome implements OnInit {
   loginError: string = '';
   registerError: string = '';
 
-  // 当前用户信息
+  // 当前用户信息(包含扩展字段)
   currentUser: any = null;
   isLoggedIn: boolean = false;
+  loadingProfile: boolean = false; // 新增:加载状态
 
   // 弹窗状态
   showMessagePopup: boolean = false;
@@ -126,7 +127,7 @@ export class PageCrmHome implements OnInit {
   ];
   
   user = {
-    name: '张明',
+    username: '张明',
     role: '高级销售经理',
     age: 32,
     birthDate: '1991-05-15',
@@ -143,36 +144,60 @@ export class PageCrmHome implements OnInit {
     "您不是一个人在战斗,AI是您最强大的后援!"
   ];
 
-  // 使用ViewChild直接引用模态框组件
   @ViewChild(LoginModalComponent) loginModal!: LoginModalComponent;
   @ViewChild(RegisterModalComponent) registerModal!: RegisterModalComponent;
 
-  // 跟踪打开的模态框数量
+// 在 PageCrmHome 类中添加(建议放在 logout 方法下方)
+async loadUserProfile() {
+  this.loadingProfile = true; // 开始加载
+  try {
+    const cloudUser = new CloudUser();
+    const currentUser = await cloudUser.current(); // 获取当前用户
+    
+    if (currentUser && currentUser.sessionToken) {
+      this.isLoggedIn = true;
+      // 绑定完整用户信息(包含注册时的扩展字段)
+      this.currentUser = {
+        username: currentUser.get('username') || '用户',
+        role: currentUser.get('role') || '销售',
+        // 扩展字段(注册时填写的信息)
+        age: currentUser.get('age') || '未设置',   // 年龄
+        birthDate: currentUser.get('birthDate') || '未设置', // 出生日期
+        position: currentUser.get('position') || '未设置',   // 职位
+        // 系统字段
+        trainingCount: currentUser.get('trainingCount') || 0
+      };
+      // 更新 user 对象,确保页面数据同步
+      this.user = {
+        username: this.currentUser.username,
+        role: this.currentUser.position,
+        age: this.currentUser.age,
+        birthDate: this.currentUser.birthDate,
+        trainingCount: this.currentUser.trainingCount
+      };
+    }
+  } catch (error) {
+    console.error('加载用户信息失败:', error);
+    this.currentUser = null;
+    this.isLoggedIn = false;
+  } finally {
+    this.loadingProfile = false; // 结束加载
+  }
+}
+
+
+
+
+
+
   private modalOpenCount = 0;
-  // 记录模态框关闭原因
   private modalCloseReason: string = '';
 
   ngOnInit(): void {
     this.updateUnreadCount();
     this.updateMotivationalText();
+    this.loadUserProfile(); // 初始化加载用户信息
     
-    // 检查用户是否已登录
-    const cloudUser = new CloudUser();
-    cloudUser.current().then(currentUser => {
-      if (currentUser && currentUser.sessionToken) {
-        this.isLoggedIn = true;
-        this.currentUser = {
-          name: currentUser.get('username') || '用户',
-          role: currentUser.get('role') || '销售',
-          age: currentUser.get('age') || 30,
-          birthDate: currentUser.get('birthDate') || '1990-01-01',
-          trainingCount: currentUser.get('trainingCount') || 0
-        };
-        this.user = this.currentUser;
-      }
-    });
-    
-    // 每10秒更新一次激励语句
     setInterval(() => this.updateMotivationalText(), 10000);
   }
 
@@ -187,7 +212,7 @@ export class PageCrmHome implements OnInit {
     this.isLoggedIn = false;
     this.currentUser = null;
     this.user = {
-      name: '访客',
+      username: '访客',
       role: '未登录用户',
       age: 0,
       birthDate: '',
@@ -203,8 +228,12 @@ export class PageCrmHome implements OnInit {
     }
   }
 
+  // 优化:打开个人信息弹窗时刷新数据
   toggleProfilePopup(): void {
     this.showProfilePopup = !this.showProfilePopup;
+    if (this.showProfilePopup && this.isLoggedIn) {
+      this.loadUserProfile(); // 重新加载最新信息
+    }
   }
 
   showMessageDetail(message: any, event?: Event): void {
@@ -248,16 +277,11 @@ export class PageCrmHome implements OnInit {
     console.log('导航至:', featureId);
   }
 
-  // 改进的模态框控制方法
   openLoginModal(): void {
-    // 确保其他模态框已关闭
     this.showRegisterModal = false;
-    
-    // 重置登录表单状态
     if (this.loginModal) {
       this.loginModal.resetForm();
     }
-    
     this.showLoginModal = true;
     this.modalOpenCount++;
     this.modalCloseReason = '';
@@ -265,19 +289,16 @@ export class PageCrmHome implements OnInit {
   }
 
   openRegisterModal(): void {
-    console.log('接收到注册模态框打开请求'); // 添加日志
-    // 确保其他模态框已关闭
+    console.log('接收到注册模态框打开请求');
     this.showLoginModal = false;
-        this.showRegisterModal = true;
-
-    // 重置注册表单状态
+    this.showRegisterModal = true;
     if (this.registerModal) {
       this.registerModal.resetForm();
     }
     console.log('打开注册模态框后状态:', {
-    showLoginModal: this.showLoginModal,
-    showRegisterModal: this.showRegisterModal
-  });
+      showLoginModal: this.showLoginModal,
+      showRegisterModal: this.showRegisterModal
+    });
     this.modalOpenCount++;
     this.modalCloseReason = '';
     document.body.classList.add('modal-open');
@@ -285,64 +306,37 @@ export class PageCrmHome implements OnInit {
 
   closeModals(reason: string = 'manual'): void {
     this.modalCloseReason = reason;
-    
-    // 逐步关闭模态框,避免状态冲突
     if (this.showRegisterModal) {
       this.showRegisterModal = false;
       this.registerError = '';
     }
-    
     if (this.showLoginModal) {
       this.showLoginModal = false;
       this.loginError = '';
     }
-    
     this.modalOpenCount--;
-    
     if (this.modalOpenCount <= 0) {
       this.modalOpenCount = 0;
       document.body.classList.remove('modal-open');
-      
-      // 确保页面滚动位置恢复
       setTimeout(() => {
         window.scrollTo(0, 0);
       }, 100);
     }
   }
 
-  // 登录/注册成功处理
+  // 优化:登录成功后重新加载用户信息
   async handleLoginSuccess(): Promise<void> {
     this.closeModals('login-success');
-    
-    // 延迟获取用户信息,确保会话已建立
     setTimeout(async () => {
-      const cloudUser = new CloudUser();
-      const currentUser = await cloudUser.current();
-      if (currentUser) {
-        this.isLoggedIn = true;
-        this.currentUser = {
-          name: currentUser.get('username') || '用户',
-          role: currentUser.get('role') || '销售',
-          age: currentUser.get('age') || 30,
-          birthDate: currentUser.get('birthDate') || '1990-01-01',
-          trainingCount: currentUser.get('trainingCount') || 0
-        };
-        this.user = this.currentUser;
-        
-        // 登录成功提示
-        console.log('登录成功:', this.currentUser);
-      }
+      await this.loadUserProfile(); // 加载最新用户信息
+      console.log('登录成功,用户信息:', this.currentUser);
     }, 300);
   }
 
   handleRegisterSuccess(): void {
     this.closeModals('register-success');
-    
-    // 注册成功后自动切换到登录页面
     setTimeout(() => {
       this.openLoginModal();
-      
-      // 显示注册成功提示
       if (this.loginModal) {
         this.loginModal.showSuccessMessage('注册成功,请登录');
       }

+ 105 - 33
ai-assisant/src/modules/crm/mobile/page-crm-home/register.component.ts

@@ -31,7 +31,49 @@ import { CloudUser } from '../../../../lib/ncloud';
                 用户名不能为空
               </div>
             </div>
+
+
+            <div class="form-group">
+                <label for="age">年龄</label>
+                <input 
+                  type="number" 
+                  id="age" 
+                  formControlName="age" 
+                  placeholder="请输入你的年龄"
+                  [ngClass]="{ 'error-border': age.touched && age.invalid }">
+                <div class="error-message" *ngIf="age.touched && age.hasError('required')">
+                  年龄不能为空
+                </div>
+                <div class="error-message" *ngIf="age.touched && (age.hasError('min') || age.hasError('max'))">
+                  年龄需在 1 - 150 之间
+                </div>
+              </div>
             
+
+              <div class="form-group">
+                  <label for="birthDate">出生日期</label>
+                  <input 
+                    type="date" 
+                    id="birthDate" 
+                    formControlName="birthDate" 
+                    [ngClass]="{ 'error-border': birthDate.touched && birthDate.invalid }">
+                  <div class="error-message" *ngIf="birthDate.touched && birthDate.hasError('required')">
+                    出生日期不能为空
+                  </div>
+                </div>
+
+                <div class="form-group">
+                  <label for="position">职位</label>
+                  <input 
+                    type="text" 
+                    id="position" 
+                    formControlName="position" 
+                    placeholder="请输入你的职位"
+                    [ngClass]="{ 'error-border': position.touched && position.invalid }">
+                  <div class="error-message" *ngIf="position.touched && position.hasError('required')">
+                    职位不能为空
+                  </div>
+                </div>
             <div class="form-group">
               <label for="password">密码</label>
               <input 
@@ -84,6 +126,19 @@ import { CloudUser } from '../../../../lib/ncloud';
           </div>
         </div>
       </div>
+      <<!-- 注册表单底部添加 -->
+<div style="color: red; margin-top: 10px;">
+  <!-- 表单级错误 -->
+  表单级错误:{{ registerForm.errors | json }}<br>
+  
+  <!-- 所有控件的错误 -->
+  用户名错误:{{ username.errors | json }}<br>
+  密码错误:{{ password.errors | json }}<br>
+  确认密码错误:{{ confirmPassword.errors | json }}<br>
+  年龄错误:{{ age.errors | json }}<br>
+  出生日期错误:{{ birthDate.errors | json }}<br>
+  职位错误:{{ position.errors | json }}<br>
+</div>
     </div>
   `,
   styles: [`
@@ -287,7 +342,10 @@ export class RegisterModalComponent implements OnChanges, AfterViewInit, OnDestr
     this.registerForm = this.fb.group({
       username: ['', Validators.required],
       password: ['', [Validators.required, Validators.minLength(6)]],
-      confirmPassword: ['', Validators.required]
+      confirmPassword: ['', Validators.required],
+        age: [null, [Validators.required, Validators.min(1), Validators.max(150)]], // 年龄
+        birthDate: [null, Validators.required], // 出生日期
+        position: ['', Validators.required] // 职位
     }, { validator: this.passwordMatchValidator });
   }
 
@@ -406,45 +464,59 @@ export class RegisterModalComponent implements OnChanges, AfterViewInit, OnDestr
   }
 
   async onRegister(event: Event) {
-    event.preventDefault();
-    
-    if (this.registerForm.invalid || this.isLoading) {
-      return;
-    }
+  event.preventDefault();
+  if (this.registerForm.invalid || this.isLoading) {
+     // 表单无效时触发表单验证
+    Object.values(this.registerForm.controls).forEach(control => {
+      control.markAsTouched(); // 标记为已触碰,触发错误提示
+    });
+    return;
+  }
 
-    this.isLoading = true;
-    this.errorMessage = '';
+  this.isLoading = true;
+  this.errorMessage = '';
 
-    try {
-      const { username, password } = this.registerForm.value;
-      const cloudUser = new CloudUser();
-      
-      // 调用后端注册接口
-      const user = await cloudUser.signUp(username, password, {
-  role: 'sales',
-  trainingCount: 0,
-});
+  try {
+    // 1. 获取表单中所有字段(包括新增的 name、age 等)
+    const { username, password,  age, birthDate, position } = this.registerForm.value;
+    const cloudUser = new CloudUser();
+    
+    // 2. 构造扩展数据(包含新增字段和原有字段)
+    const additionalData = {
+      role: 'sales',
+      trainingCount: 0,
+      age: Number(age), // 确保为数字类型
+      birthDate: new Date(birthDate),
+      position
+    };
 
-      if (user) {
-        this.registerSuccess.emit();
-        this.visible = false;
-      } else {
-        this.errorMessage = '注册失败,请稍后重试';
-      }
-    } catch (error: any) {
-      // 处理常见错误(如用户名已存在)
-      if (error.message.includes('用户名已存在')) {
-        this.errorMessage = '该用户名已被注册,请更换其他用户名';
-      } else {
-        this.errorMessage = error?.message || '注册失败,请检查网络连接';
-      }
-      console.error('注册错误:', error);
-    } finally {
-      this.isLoading = false;
+    // 3. 调用注册接口,传递所有数据
+    const user = await cloudUser.signUp(username, password, additionalData);
+
+    if (user) {
+      this.registerSuccess.emit();
+      this.visible = false;
+    } else {
+      this.errorMessage = '注册失败,请稍后重试';
+    }
+  } catch (error: any) {
+    // 错误处理
+    if (error.message.includes('username already exists')) {
+      this.errorMessage = '该用户名已被注册,请更换其他用户名';
+    } else {
+      this.errorMessage = error?.message || '注册失败,请检查网络连接';
     }
+    console.error('注册错误:', error);
+  } finally {
+    this.isLoading = false;
   }
+}
 
   get username() { return this.registerForm.get('username')!; }
   get password() { return this.registerForm.get('password')!; }
   get confirmPassword() { return this.registerForm.get('confirmPassword')!; }
+  // 在类的末尾添加(与其他 getter 方法并列)
+  get age() { return this.registerForm.get('age')!; }         // 年龄控件
+  get birthDate() { return this.registerForm.get('birthDate')!; } // 出生日期控件
+  get position() { return this.registerForm.get('position')!; }   // 职位控件
 }

+ 91 - 310
ai-assisant/src/modules/crm/mobile/page-crm-training/page-crm-training.html

@@ -1,327 +1,108 @@
-<div class="training-container">
-  <!-- 头部导航 -->
-  <header class="header">
-    <div class="nav-top">
-      <button class="back-btn" (click)="goBack()" [disabled]="!canGoBack()">
-        <i class="fas fa-arrow-left"></i>
-      </button>
-      <h1 class="page-title">虚拟陪练舱</h1>
-      <div></div>
-    </div>
-  </header>
-  
-  <!-- 场景选择卡 -->
-  <section class="section-card">
-    <div class="section-title">
-      <div>
-        <i class="fas fa-layer-group"></i>
-        选择陪练场景
-      </div>
-      <button class="edit-btn" (click)="toggleEditMode()">
-        <i class="fas" [ngClass]="{'fa-edit': !isEditing, 'fa-check': isEditing}"></i>
-        {{isEditing ? '完成编辑' : '编辑分类'}}
-      </button>
-    </div>
-    
-    <div class="difficulty-tabs">
-      @for (tab of difficultyTabs; track tab.label) {
-        <div class="tab" 
-             [class.active]="tab.active"
-             (click)="selectTab(tab)">
-          {{tab.label}}
-        </div>
-      }
-    </div>
-    
-    <div class="customer-bubbles">
-      @for (type of customerTypes; track type.id) {
-        <div class="bubble" 
-             [class.active]="type.active"
-             (click)="selectCustomerType(type)">
-          {{type.name}}
-          @if (isEditing) {
-            <button class="delete-btn" (click)="deleteCustomerType(type); $event.stopPropagation()">
-              <i class="fas fa-times"></i>
-            </button>
-          }
-        </div>
-      }
-      @if (isEditing) {
-        <div class="add-bubble" (click)="openAddCustomerModal()">
-          <i class="fas fa-plus"></i>
-          添加类型
-        </div>
-      }
-    </div>
-    
-    @if (!currentScenario && customerTypes.length > 0) {
-      <button class="start-btn" (click)="startTraining()">
-        <i class="fas fa-play"></i>
-        开始训练
-      </button>
-    }
-  </section>
-  
-  <!-- 对话主界面 -->
-  @if (currentScenario) {
-    <section class="conversation-section">
-      <div class="avatar-container">
-        <div class="customer-avatar">
-          <i class="fas" [ngClass]="getCustomerIcon()"></i>
-        </div>
-      </div>
-      
-      <div class="conversation-container" #conversationContainer>
-        @for (message of messages; track $index) {
-          <div class="message" 
-               [class.customer-message]="message.isCustomer"
-               [class.user-message]="!message.isCustomer">
-            {{message.text}}
-          </div>
-        }
-      </div>
-      
-      <div class="input-area">
-        <button class="voice-btn" 
-                [class.active]="isRecording"
-                (click)="toggleRecording()">
-          <i class="fas" [ngClass]="{'fa-microphone': !isRecording, 'fa-square': isRecording}"></i>
-        </button>
-        <input type="text" 
-               class="text-input" 
-               placeholder="输入回复内容..."
-               [(ngModel)]="userInput"
-               (keyup.enter)="sendMessage()">
-        <button class="send-btn" (click)="sendMessage()" [disabled]="!userInput.trim()">
-          <i class="fas fa-paper-plane"></i>
-        </button>
-      </div>
-    </section>
-    
-    <!-- 能力仪表盘 -->
-    <section class="dashboard">
-      <h2 class="section-title">
-        <i class="fas fa-chart-line"></i>
-        能力评估
-      </h2>
-      
-      <div class="progress-container">
-        <svg class="progress-circle" viewBox="0 0 180 180">
-          <circle cx="90" cy="90" r="80" stroke="#e2e8f0" stroke-width="10" fill="none" />
-          <circle cx="90" cy="90" r="80" stroke="#4285f4" stroke-width="10" fill="none" 
-                  [attr.stroke-dasharray]="502" [attr.stroke-dashoffset]="getProgressOffset()" stroke-linecap="round" />
-        </svg>
-        <div class="progress-value">{{currentScore}}</div>
-      </div>
+<div class="container">
+  <!-- 头部标题 -->
+  <div class="header">
+    <h1>虚拟训练功能</h1>
+  </div>
+
+  <!-- 主体内容 -->
+  <div class="main">
+    <!-- 陪练场景选择侧边栏 -->
+    <aside class="sidebar">
+      <h2>陪练场景选择</h2>
       
-      <div class="skills-container">
-        @for (skill of skills; track skill.name) {
-          <div class="skill">
-            <div class="skill-name">{{skill.name}}</div>
-            <div class="skill-progress">
-              <div class="skill-progress-fill" 
-                   [style.width.%]="skill.value" 
-                   [style.background]="getSkillColor(skill.value)"></div>
-            </div>
-          </div>
-        }
+      <!-- 场景难度设置 -->
+      <div class="form-group">
+        <label for="sceneLevel">场景难度设置</label>
+        <select 
+          id="sceneLevel" 
+          [(ngModel)]="selectedLevel"
+          (change)="filterScenes()"
+        >
+          <option value="">全部难度</option>
+          <option *ngFor="let level of sceneLevels" [value]="level.value">
+            {{level.label}}
+          </option>
+        </select>
       </div>
-    </section>
-    
-    <!-- 存档面板 -->
-    @if (messages.length >= 8) {
-      <section class="report-card">
-        <div class="report-header">
-          <h3 class="report-title">本次陪练报告</h3>
-          <div class="comparison" [class.down]="scoreChange < 0">
-            <i class="fas" [ngClass]="{'fa-arrow-up': scoreChange >= 0, 'fa-arrow-down': scoreChange < 0}"></i>
-            <span>{{Math.abs(scoreChange)}}%</span>
-          </div>
-        </div>
-        
-        <p class="report-summary">
-          {{currentReport.summary}}
-        </p>
-        
-        <div class="tags-container">
-          @for (tag of currentReport.tags; track tag) {
-            <div class="tag">{{tag}}</div>
-          }
-        </div>
-        
-        <div class="actions">
-          <button class="action-btn view-btn" (click)="openFullReportModal()">
-            <i class="fas fa-file-alt"></i>
-            查看完整报告
-          </button>
-          <button class="action-btn history-btn" (click)="openHistoryReportModal()">
-            <i class="fas fa-history"></i>
-            历史报告
-          </button>
-        </div>
-      </section>
-    }
-  }
-</div>
 
-<!-- 添加客户类型弹窗 -->
-@if (showAddCustomerModal) {
-  <div class="modal-overlay active">
-    <div class="modal">
-      <div class="modal-header">
-        <button class="back-btn" (click)="closeAddCustomerModal()">
-          <i class="fas fa-arrow-left"></i>
-        </button>
-        <h3 class="modal-title">添加客户类型</h3>
-        <button class="close-modal" (click)="closeAddCustomerModal()">
-          <i class="fas fa-times"></i>
-        </button>
+      <!-- 客户角色设定 -->
+      <div class="form-group">
+        <label for="customerType">客户角色设定</label>
+        <select 
+          id="customerType" 
+          [(ngModel)]="selectedCustomerType"
+          (change)="filterScenes()"
+        >
+          <option value="">全部角色</option>
+          <option *ngFor="let customer of customerTypes" [value]="customer.value">
+            {{customer.label}}
+          </option>
+        </select>
       </div>
-      <div class="modal-content">
-        <div class="form-group">
-          <label for="customerType">客户类型名称</label>
-          <input type="text" id="customerType" placeholder="例如:新婚客户" [(ngModel)]="newCustomerTypeName">
-        </div>
-        <div class="form-group">
-          <label for="difficulty">难度级别</label>
-          <select id="difficulty" [(ngModel)]="newCustomerDifficulty">
-            @for (tab of difficultyTabs; track tab.value) {
-              <option [value]="tab.value">{{tab.label}}</option>
-            }
-          </select>
-        </div>
-      </div>
-      <div class="modal-actions">
-        <button class="modal-btn cancel-btn" (click)="closeAddCustomerModal()">取消</button>
-        <button class="modal-btn save-btn" (click)="saveCustomerType()" [disabled]="!newCustomerTypeName.trim()">保存</button>
-      </div>
-    </div>
-  </div>
-}
 
-<!-- 完整报告弹窗 -->
-@if (showFullReportModal) {
-  <div class="modal-overlay active">
-    <div class="modal">
-      <div class="modal-header">
-        <button class="back-btn" (click)="closeFullReportModal()">
-          <i class="fas fa-arrow-left"></i>
-        </button>
-        <h3 class="modal-title">陪练报告详情</h3>
-        <button class="close-modal" (click)="closeFullReportModal()">
-          <i class="fas fa-times"></i>
-        </button>
-      </div>
-      <div class="modal-content">
-        <div class="report-detail">
-          <div class="report-info">
-            <div class="info-item">
-              <span class="info-label">日期</span>
-              <span class="info-value">{{currentReport.date | date: 'yyyy年MM月dd日'}}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">客户类型</span>
-              <span class="info-value">{{currentReport.customerType}}</span>
-            </div>
-            <div class="info-item">
-              <span class="info-label">难度</span>
-              <span class="info-value">{{currentReport.difficulty}}</span>
-            </div>
+      <!-- 场景列表 -->
+      <div class="form-group scene-list-container">
+        <label>可选场景</label>
+        <div class="scene-list">
+          <div *ngIf="filteredScenes.length === 0" class="no-scene">
+            暂无符合条件的场景
           </div>
-          
-          <div class="section">
-            <div class="section-title">
-              <i class="fas fa-comments"></i>
-              对话内容
-            </div>
-            <div class="conversation-history">
-              @for (message of messages; track $index) {
-                <div class="message-history" 
-                     [class.customer-message-history]="message.isCustomer"
-                     [class.user-message-history]="!message.isCustomer">
-                  {{message.text}}
-                </div>
-              }
-            </div>
-          </div>
-          
-          <div class="section">
-            <div class="section-title">
-              <i class="fas fa-star"></i>
-              能力评价
-            </div>
-            <div class="evaluation-content">
-              <p>{{currentReport.evaluation.intro}}</p>
-              <ul>
-                @for (point of currentReport.evaluation.points; track point) {
-                  <li>{{point}}</li>
-                }
-              </ul>
-              <div class="score-tag" [ngClass]="getScoreTagClass()">
-                综合评分: {{currentScore}}/100
-              </div>
-            </div>
-          </div>
-          
-          <div class="section">
-            <div class="section-title">
-              <i class="fas fa-lightbulb"></i>
-              总结方法
-            </div>
-            <div class="summary-content">
-              <p>{{currentReport.summaryMethods.intro}}</p>
-              <ol>
-                @for (method of currentReport.summaryMethods.methods; track method) {
-                  <li>{{method}}</li>
-                }
-              </ol>
-              <div class="tag-list">
-                @for (tag of currentReport.summaryMethods.tags; track tag) {
-                  <div class="tag">{{tag}}</div>
-                }
+          <div *ngFor="let scene of filteredScenes" class="scene-item">
+            <div class="scene-info">
+              <div class="scene-name">{{scene.name}}</div>
+              <div class="scene-desc">{{scene.description}}</div>
+              <div class="scene-meta">
+                <span>难度: {{getLevelLabel(scene.level)}}</span>
+                <span>角色: {{getCustomerLabel(scene.customerType)}}</span>
               </div>
             </div>
+            <button 
+              class="select-scene-btn" 
+              (click)="selectScene(scene)"
+              [disabled]="selectedScene?.id === scene.id"
+            >
+              {{selectedScene?.id === scene.id ? '已选择' : '选择'}}
+            </button>
           </div>
         </div>
       </div>
-    </div>
-  </div>
-}
+    </aside>
 
-<!-- 历史报告弹窗 -->
-@if (showHistoryReportModal) {
-  <div class="modal-overlay active">
-    <div class="modal">
-      <div class="modal-header">
-        <button class="back-btn" (click)="closeHistoryReportModal()">
-          <i class="fas fa-arrow-left"></i>
-        </button>
-        <h3 class="modal-title">历史陪练报告</h3>
-        <button class="close-modal" (click)="closeHistoryReportModal()">
-          <i class="fas fa-times"></i>
-        </button>
+    <!-- 对话训练模拟区域 -->
+    <section class="chat-section">
+      <h2>对话训练模拟 - {{selectedScene?.name || '未选择场景'}}</h2>
+      
+      <div class="training-info" *ngIf="selectedScene">
+        <span>轮次: {{currentRound}}/{{maxRounds}}</span>
+        <span *ngIf="conversationScore !== null">当前得分: {{conversationScore}}</span>
       </div>
-      <div class="modal-content">
-        <div class="history-list">
-          @for (report of historyReports; track report.id) {
-            <div class="history-item" (click)="viewHistoryReport(report)">
-              <div class="history-header">
-                <span class="history-date">{{report.date | date: 'yyyy年MM月dd日 HH:mm'}}</span>
-                <span class="history-type">{{report.customerType}}</span>
-              </div>
-              <div class="history-summary">
-                {{report.summary}}
-              </div>
-            </div>
-          }
-          @if (historyReports.length === 0) {
-            <div class="no-history">
-              <i class="fas fa-inbox"></i>
-              <p>暂无历史报告</p>
-            </div>
-          }
+      
+      <div class="chat-messages" #chatMessagesContainer>
+        <p *ngIf="!selectedScene" class="system-message">请先从左侧选择训练场景</p>
+        <div *ngFor="let message of chatMessages" class="message" [class.user]="message.type === 'user'" [class.bot]="message.type === 'bot'">
+          <div class="message-content">{{message.content}}</div>
+          <div class="message-time">{{message.timestamp | date:'HH:mm'}}</div>
         </div>
       </div>
+      
+      <div class="chat-input" *ngIf="selectedScene">
+        <input 
+          type="text" 
+          [(ngModel)]="chatInput" 
+          placeholder="输入回复内容..."
+          (keyup.enter)="sendMessage()"
+        >
+        <button (click)="sendMessage()" [disabled]="!chatInput.trim()">发送</button>
+        <button (click)="resetConversation()" class="reset-btn">重置对话</button>
+      </div>
+    </section>
+
+    <!-- 训练报告区域 -->
+    <div class="eval-report">
+      <app-training-reports 
+        [reports]="generatedReports"
+        (reportSelected)="viewReport($event)"
+      ></app-training-reports>
     </div>
   </div>
-}
+</div>

+ 338 - 773
ai-assisant/src/modules/crm/mobile/page-crm-training/page-crm-training.scss

@@ -1,857 +1,422 @@
-:root {
-  --primary: #4285f4;
-  --primary-light: #8ab4f8;
-  --primary-dark: #0a192f;
-  --text-dark: #000000;
-  --text-light: #000000;
-  --card-bg: #ffffff;
-  --border-color: #eaeaea;
-  --shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
-  --success: #34a853;
-  --warning: #f9ab00;
-  --danger: #ea4335;
-  --info: #4285f4;
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: "Microsoft YaHei", sans-serif;
 }
 
-.training-container {
-  max-width: 480px;
+.container {
+  max-width: 1200px;
   margin: 0 auto;
-  padding: 0 16px;
-  padding-bottom: 40px;
-  background-color: #f8fafc;
-  color: var(--text-dark);
-  line-height: 1.6;
+  background-color: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+  margin-top: 20px;
 }
 
-/* 头部导航 */
 .header {
-  position: sticky;
-  top: 0;
-  background: rgba(255, 255, 255, 0.95);
-  backdrop-filter: blur(10px);
-  z-index: 100;
-  padding: 16px 0;
-  border-bottom: 1px solid var(--border-color);
-}
-
-.nav-top {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.back-btn {
-  background: rgba(0, 0, 0, 0.1);
-  width: 36px;
-  height: 36px;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  border: none;
-  font-size: 16px;
-  color: var(--text-dark);
-}
-
-.page-title {
-  font-size: 18px;
-  font-weight: 700;
-  color: var(--text-dark);
-}
-
-/* 场景选择卡 */
-.section-card {
-  background-color: var(--card-bg);
-  border-radius: 16px;
-  padding: 16px;
-  margin: 16px 0;
-  box-shadow: var(--shadow);
-}
-
-.section-title {
-  font-size: 16px;
-  font-weight: 600;
-  margin-bottom: 12px;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  color: var(--text-dark);
-}
-
-.section-title i {
-  margin-right: 8px;
-  color: var(--primary);
-}
-
-.edit-btn {
-  background: none;
-  border: none;
-  color: var(--text-dark);
-  font-size: 14px;
-  cursor: pointer;
-  display: flex;
-  align-items: center;
-  gap: 4px;
-  min-width: 44px;
-  min-height: 44px;
-}
-
-.difficulty-tabs {
-  display: flex;
-  gap: 12px;
-  overflow-x: auto;
-  padding: 8px 0 16px;
-  -ms-overflow-style: none;
-  scrollbar-width: none;
-}
-
-.difficulty-tabs::-webkit-scrollbar {
-  display: none;
-}
-
-.tab {
-  flex: 0 0 auto;
-  padding: 8px 16px;
-  border-radius: 20px;
-  background-color: var(--card-bg);
-  border: 1px solid var(--border-color);
-  box-shadow: var(--shadow);
-  font-size: 14px;
-  cursor: pointer;
-  transition: all 0.3s ease;
-  min-width: 44px;
-  min-height: 44px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  color: var(--text-dark);
-}
-
-.tab.active {
-  background-color: var(--primary);
-  color: white;
-  border-color: var(--primary);
-  font-weight: 600;
+  padding: 20px;
+  background-color: #f8f9fc;
+  border-bottom: 1px solid #e5e7eb;
+  
+  h1 {
+    font-size: 24px;
+    color: #333;
+    text-align: center;
+  }
 }
 
-.customer-bubbles {
+.main {
   display: flex;
   flex-wrap: wrap;
-  gap: 12px;
-  margin-top: 8px;
-}
-
-.bubble {
-  padding: 10px 16px;
-  border-radius: 20px;
-  background-color: var(--card-bg);
-  border: 1px solid var(--border-color);
-  box-shadow: var(--shadow);
-  font-size: 14px;
-  cursor: pointer;
-  transition: all 0.2s ease;
-  position: relative;
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  min-width: 44px;
-  min-height: 44px;
-  color: var(--text-dark);
-}
-
-.bubble.active {
-  background-color: var(--primary-dark);
-  color: white;
-  border-color: var(--primary-dark);
-}
-
-.bubble .delete-btn {
-  display: inline-flex;
-  background: rgba(0, 0, 0, 0.5);
-  border-radius: 50%;
-  width: 18px;
-  height: 18px;
-  align-items: center;
-  justify-content: center;
-  color: white !important;
-  margin-left: 4px;
-  border: none;
-  cursor: pointer;
+  padding: 20px;
+  gap: 20px;
 }
 
-.bubble.active .delete-btn {
-  background: rgba(255, 255, 255, 0.3);
-}
+/* 陪练场景选择侧边栏样式 */
+.sidebar {
+  width: 320px;
+  background-color: #f8f9fc;
+  border-radius: 8px;
+  padding: 20px;
+  border: 1px solid #e5e7eb;
+  height: fit-content;
 
-.add-bubble {
-  padding: 10px 16px;
-  border-radius: 20px;
-  background-color: var(--card-bg);
-  border: 1px dashed var(--border-color);
-  font-size: 14px;
-  cursor: pointer;
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  color: var(--text-dark);
-  min-width: 44px;
-  min-height: 44px;
+  h2 {
+    font-size: 18px;
+    color: #333;
+    margin-bottom: 20px;
+    border-left: 4px solid #007bff;
+    padding-left: 10px;
+  }
 }
 
-.start-btn {
-  width: 100%;
-  padding: 12px;
-  background-color: var(--primary);
-  color: white;
+.form-group {
+  margin-bottom: 20px;
+  
+  label {
+    display: block;
+    margin-bottom: 8px;
+    color: #666;
+    font-weight: 500;
+  }
+  
+  select, input {
+    width: 100%;
+    padding: 10px;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    outline: none;
+    font-size: 14px;
+    transition: border-color 0.2s;
+    
+    &:focus {
+      border-color: #007bff;
+    }
+  }
+}
+
+.btn-add {
+  padding: 8px 15px;
+  background-color: #007bff;
+  color: #fff;
   border: none;
-  border-radius: 12px;
-  font-size: 16px;
-  font-weight: 600;
-  margin-top: 16px;
+  border-radius: 4px;
   cursor: pointer;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  gap: 8px;
+  white-space: nowrap;
+  
+  &:hover {
+    background-color: #0056b3;
+  }
 }
 
-/* 对话主界面 */
-.conversation-section {
-  margin-top: 24px;
+/* 场景列表样式 */
+.scene-list-container {
+  margin-top: 30px;
 }
 
-.avatar-container {
-  display: flex;
-  justify-content: center;
-  margin: 20px 0;
-}
-
-.customer-avatar {
-  width: 100px;
-  height: 100px;
-  border-radius: 50%;
-  background: linear-gradient(135deg, var(--primary), var(--primary-dark));
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  position: relative;
-  overflow: hidden;
-  box-shadow: var(--shadow);
-  transition: all 0.3s ease;
-}
-
-.customer-avatar::before {
-  content: "";
-  position: absolute;
-  width: 140px;
-  height: 140px;
-  background: radial-gradient(circle, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 70%);
-  border-radius: 50%;
-}
-
-.customer-avatar i {
-  font-size: 36px;
-  color: white;
-}
-
-.conversation-container {
-  background-color: var(--card-bg);
-  border-radius: 20px;
-  box-shadow: var(--shadow);
-  padding: 16px;
-  margin: 16px 0;
-  height: 320px;
+.scene-list {
+  border: 1px solid #e5e7eb;
+  border-radius: 4px;
+  padding: 10px;
+  max-height: 300px;
   overflow-y: auto;
+  background-color: #fff;
 }
 
-.message {
-  max-width: 80%;
-  padding: 12px;
-  margin-bottom: 16px;
-  border-radius: 18px;
-  position: relative;
-  animation: fadeIn 0.3s ease;
-  color: var(--text-dark);
+.scene-category {
+  margin-bottom: 15px;
+  padding-bottom: 10px;
+  border-bottom: 1px dashed #e5e7eb;
+  
+  h4 {
+    font-size: 15px;
+    margin-bottom: 10px;
+    color: #2c3e50;
+  }
 }
 
-@keyframes fadeIn {
-  from { opacity: 0; transform: translateY(10px); }
-  to { opacity: 1; transform: translateY(0); }
-}
-
-.customer-message {
-  background-color: #f1f5f9;
-  border-bottom-left-radius: 4px;
-  align-self: flex-start;
-}
-
-.user-message {
-  background-color: var(--primary);
-  color: white;
-  border-bottom-right-radius: 4px;
-  margin-left: auto;
-}
-
-.input-area {
-  display: flex;
-  gap: 12px;
-  align-items: center;
-  background-color: var(--card-bg);
-  border-radius: 30px;
-  padding: 12px 20px;
-  box-shadow: var(--shadow);
-  margin-top: 16px;
-}
-
-.voice-btn {
-  width: 48px;
-  height: 48px;
-  border-radius: 50%;
-  background-color: var(--primary);
-  color: white;
+.scene-item {
   display: flex;
+  justify-content: space-between;
   align-items: center;
-  justify-content: center;
+  padding: 10px;
+  background-color: #f9f9f9;
+  border-radius: 4px;
+  margin-bottom: 10px;
   cursor: pointer;
-  border: none;
-  font-size: 20px;
-  transition: all 0.3s ease;
-}
-
-.voice-btn.active {
-  background-color: var(--danger);
-  color: white;
-  animation: pulse 1.5s infinite;
+  transition: background-color 0.2s;
+  
+  &:hover {
+    background-color: #f0f7ff;
+  }
 }
 
-@keyframes pulse {
-  0% { transform: scale(1); }
-  50% { transform: scale(1.05); }
-  100% { transform: scale(1); }
+.scene-info {
+  flex: 1;
+  margin-right: 10px;
 }
 
-.text-input {
-  flex: 1;
-  padding: 12px 16px;
-  border: 1px solid var(--border-color);
-  border-radius: 24px;
-  font-size: 16px;
-  outline: none;
-  transition: border-color 0.3s;
-  color: var(--text-dark);
+.scene-name {
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 3px;
 }
 
-.text-input:focus {
-  border-color: var(--primary);
+.scene-desc {
+  font-size: 13px;
+  color: #666;
+  line-height: 1.4;
 }
 
-.send-btn {
-  width: 48px;
-  height: 48px;
-  border-radius: 50%;
-  background-color: var(--primary-dark);
+.select-scene-btn {
+  padding: 6px 12px;
+  background-color: #28a745;
   color: white;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  cursor: pointer;
   border: none;
-  font-size: 20px;
+  border-radius: 4px;
+  font-size: 13px;
+  cursor: pointer;
+  transition: background-color 0.2s;
+  
+  &:hover {
+    background-color: #218838;
+  }
 }
 
-/* 能力仪表盘 */
-.dashboard {
-  background-color: var(--card-bg);
-  border-radius: 20px;
-  box-shadow: var(--shadow);
-  padding: 24px;
-  margin: 24px 0;
+.no-scene {
+  color: #999;
   text-align: center;
+  padding: 20px 0;
+  font-size: 14px;
 }
 
-.progress-container {
-  position: relative;
-  width: 180px;
-  height: 180px;
-  margin: 0 auto 20px;
-}
-
-.progress-circle {
+/* 对话模拟区域 */
+.chat-section {
+  flex: 1;
+  min-width: 300px;
+  background-color: #fff;
+  border-radius: 8px;
+  padding: 20px;
+  border: 1px solid #e5e7eb;
+  height: fit-content;
+  min-height: 480px;
+
+  h2 {
+    font-size: 18px;
+    color: #333;
+    margin-bottom: 15px;
+    border-left: 4px solid #007bff;
+    padding-left: 10px;
+  }
+  
+  .chat-messages {
+    height: 320px;
+    overflow-y: auto;
+    margin-bottom: 15px;
+    border: 1px solid #e5e7eb;
+    border-radius: 4px;
+    padding: 15px;
+    background-color: #fafafa;
+    
+    .system-message {
+      color: #666;
+      text-align: center;
+      padding: 10px;
+      font-style: italic;
+    }
+    
+    p {
+      margin: 8px 0;
+      padding: 10px 12px;
+      border-radius: 6px;
+      max-width: 70%;
+      word-wrap: break-word;
+      line-height: 1.5;
+    }
+    
+    p.user {
+      background-color: #d1ecf1;
+      color: #0c5460;
+      margin-left: auto;
+      box-shadow: 0 1px 2px rgba(0,0,0,0.05);
+    }
+    
+    p.bot {
+      background-color: #f1f3f5;
+      color: #495057;
+      box-shadow: 0 1px 2px rgba(0,0,0,0.05);
+    }
+  }
+  
+  .chat-input {
+    display: flex;
+    gap: 10px;
+    
+    input {
+      flex: 1;
+      padding: 10px;
+      border: 1px solid #ddd;
+      border-radius: 4px;
+      outline: none;
+    }
+    
+    button {
+      padding: 0 20px;
+      background-color: #007bff;
+      color: #fff;
+      border: none;
+      border-radius: 4px;
+      cursor: pointer;
+      font-weight: 500;
+      
+      &:hover {
+        background-color: #0056b3;
+      }
+    }
+  }
+}
+
+/* 评估与报告区域 */
+.eval-report {
   width: 100%;
-  height: 100%;
-}
-
-.progress-value {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  font-size: 32px;
-  font-weight: 700;
-  color: var(--text-dark);
-}
-
-.skills-container {
   display: flex;
-  flex-wrap: wrap;
-  justify-content: center;
-  gap: 16px;
-  margin-top: 20px;
-}
-
-.skill {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-
-.skill-progress {
-  width: 80px;
-  height: 6px;
-  background-color: #e2e8f0;
-  border-radius: 3px;
-  margin-top: 8px;
-  overflow: hidden;
-}
-
-.skill-progress-fill {
-  height: 100%;
-  background-color: var(--primary);
-  border-radius: 3px;
-}
-
-.skill-name {
-  font-size: 12px;
-  color: var(--text-dark);
-  margin-top: 4px;
+  gap: 20px;
+  margin-top: 10px;
 }
 
-/* 存档面板 */
-.report-card {
-  background-color: var(--card-bg);
-  border-radius: 20px;
-  box-shadow: var(--shadow);
+.card {
+  flex: 1;
+  min-width: 250px;
+  background-color: #fff;
+  border-radius: 8px;
   padding: 20px;
-  margin-top: 24px;
-}
-
-.report-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16px;
-}
-
-.report-title {
-  font-size: 18px;
-  font-weight: 700;
-  color: var(--text-dark);
-}
-
-.comparison {
+  border: 1px solid #e5e7eb;
+  box-shadow: 0 1px 3px rgba(0,0,0,0.05);
+  
+  h3 {
+    font-size: 16px;
+    color: #333;
+    margin-bottom: 20px;
+    padding-bottom: 10px;
+    border-bottom: 1px solid #f1f3f5;
+  }
+}
+
+.score-circle {
+  width: 120px;
+  height: 120px;
+  border-radius: 50%;
+  background-color: #f3f4f6;
+  margin: 0 auto 20px;
   display: flex;
   align-items: center;
-  font-size: 14px;
-  color: var(--text-dark);
-}
-
-.comparison.down {
-  color: var(--danger);
-}
-
-.comparison i {
-  margin-right: 4px;
-}
-
-.report-summary {
-  font-size: 14px;
-  line-height: 1.7;
-  margin-bottom: 20px;
-  color: var(--text-dark);
+  justify-content: center;
+  font-size: 28px;
+  color: #007bff;
+  font-weight: bold;
+  position: relative;
+  box-shadow: inset 0 0 0 12px #fff, inset 0 0 0 13px #e5e7eb;
 }
 
-.tags-container {
+.score-details {
   display: flex;
+  justify-content: space-around;
+  color: #666;
   flex-wrap: wrap;
-  gap: 8px;
-  margin-bottom: 20px;
-}
-
-.tag {
+  gap: 10px;
+  text-align: center;
+  
+  span {
+    font-size: 14px;
+    padding: 5px 0;
+    flex: 1;
+    min-width: 80px;
+    border-bottom: 2px solid #f1f3f5;
+  }
+}
+
+.report-list {
+  list-style: none;
+  padding: 0;
+  
+  li {
+    padding: 12px 0;
+    border-bottom: 1px solid #f1f3f5;
+    color: #333;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    
+    span {
+      color: #666;
+      font-size: 14px;
+    }
+  }
+}
+
+.btn-report {
   padding: 6px 12px;
-  background-color: #e2e8f0;
-  border-radius: 16px;
-  font-size: 12px;
-  color: var(--text-dark);
-}
-
-.actions {
-  display: flex;
-  gap: 12px;
-}
-
-.action-btn {
-  flex: 1;
-  padding: 12px;
-  border-radius: 12px;
+  background-color: #007bff;
+  color: #fff;
   border: none;
-  font-weight: 600;
+  border-radius: 4px;
   cursor: pointer;
+  text-decoration: none;
+  font-size: 14px;
+  
+  &:hover {
+    background-color: #0056b3;
+  }
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+  .main {
+    flex-direction: column;
+  }
+  
+  .sidebar {
+    width: 100%;
+    margin-right: 0;
+  }
+  
+  .eval-report {
+    flex-direction: column;
+  }
+}
+// 新增轮次和评分样式
+.training-info {
   display: flex;
-  align-items: center;
-  justify-content: center;
-  gap: 8px;
-  transition: all 0.3s ease;
-  color: white;
-}
-
-.view-btn {
-  background-color: var(--primary);
-}
-
-.history-btn {
-  background-color: var(--primary-dark);
+  justify-content: space-between;
+  margin-bottom: 10px;
+  color: #666;
+  font-size: 14px;
 }
+/* page-crm-training.scss (新增) */
 
-/* 弹窗样式 */
-.modal-overlay {
-   position: fixed;
+.report-modal {
+  position: fixed;
   top: 0;
   left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.6); /* 加深背景透明度,减少透底 */
-  z-index: 9999; /* 提高层级,确保覆盖所有主页面元素 */
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  backdrop-filter: blur(3px); /* 可选:添加背景模糊,增强层次感 */
-}
-
-.modal-overlay.active {
-  opacity: 1;
-  pointer-events: all;
-}
-
-.modal {
-  background-color: white;
-  border-radius: 16px;
-  width: 90%;
-  max-width: 500px;
-  max-height: 90vh;
-  overflow-y: auto;
-  padding: 24px;
-  box-shadow: var(--shadow);
-  transform: translateY(20px);
-  transition: transform 0.3s ease;
-}
-
-.modal-overlay.active .modal {
-  transform: translateY(0);
-}
-
-.modal-header {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20px;
-  padding-bottom: 16px;
-  border-bottom: 1px solid var(--border-color);
-}
-
-.modal-title {
-  font-size: 18px;
-  font-weight: 700;
-  color: var(--text-dark);
-  margin-left: 12px;
-  flex: 1;
-  text-align: center;
-}
-
-.close-modal {
-  background: none;
-  border: none;
-  font-size: 20px;
-  cursor: pointer;
-  color: var(--text-dark);
-  min-width: 44px;
-  min-height: 44px;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.5);
   display: flex;
-  align-items: center;
   justify-content: center;
-}
-
-.modal-header .back-btn {
-  background: none;
-  border: none;
-  font-size: 20px;
-  color: var(--text-dark);
-  cursor: pointer;
-  margin-right: 12px;
-  min-width: 44px;
-  min-height: 44px;
-  display: flex;
   align-items: center;
-  justify-content: center;
+  z-index: 100;
 }
 
 .modal-content {
-  margin-bottom: 24px;
-  color: var(--text-dark);
-}
-
-.report-detail {
   background-color: white;
-  padding: 20px;
-  border-radius: 16px;
+  border-radius: 8px;
+  max-width: 600px;
+  width: 90%;
+  max-height: 80vh;
+  overflow: auto;
 }
 
-.report-info {
+.modal-header {
   display: flex;
   justify-content: space-between;
-  margin-bottom: 20px;
-  padding-bottom: 15px;
-  border-bottom: 1px solid var(--border-color);
-}
-
-.info-item {
-  display: flex;
-  flex-direction: column;
-}
-
-.info-label {
-  font-size: 12px;
-  color: var(--text-dark);
-  margin-bottom: 4px;
-}
-
-.info-value {
-  font-size: 14px;
-  font-weight: 600;
-  color: var(--text-dark);
-}
-
-.section {
-  margin-bottom: 24px;
-}
-
-.section-title {
-  font-size: 16px;
-  font-weight: 600;
-  margin-bottom: 12px;
-  color: var(--text-dark);
-  display: flex;
   align-items: center;
-  gap: 8px;
-}
-
-.section-title i {
-  color: var(--primary);
-}
-
-.conversation-history {
-  background-color: #f8fafc;
-  border-radius: 12px;
   padding: 16px;
-  max-height: 300px;
-  overflow-y: auto;
-}
-
-.message-history {
-  padding: 10px 12px;
-  margin-bottom: 12px;
-  border-radius: 12px;
-  font-size: 14px;
-  color: var(--text-dark);
-}
-
-.customer-message-history {
-  background-color: #e2e8f0;
-  align-self: flex-start;
-  max-width: 80%;
+  border-bottom: 1px solid #eee;
 }
 
-.user-message-history {
-  background-color: var(--primary-light);
-  color: var(--text-dark);
-  margin-left: auto;
-  max-width: 80%;
-}
-
-.evaluation-content {
-  background-color: #f8fafc;
-  border-radius: 12px;
+.modal-body {
   padding: 16px;
-  font-size: 14px;
-  line-height: 1.6;
-  color: var(--text-dark);
-}
-
-.summary-content {
-  background-color: #f8fafc;
-  border-radius: 12px;
-  padding: 16px;
-  font-size: 14px;
-  line-height: 1.6;
-  color: var(--text-dark);
-}
-
-.tag-list {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 8px;
-  margin-top: 12px;
-}
-
-.history-list {
-  background-color: #f8fafc;
-  border-radius: 12px;
-  padding: 16px;
-  max-height: 400px;
-  overflow-y: auto;
 }
 
-.history-item {
-  padding: 12px;
-  background-color: white;
-  border-radius: 12px;
-  margin-bottom: 12px;
-  box-shadow: var(--shadow);
-  cursor: pointer;
-  transition: all 0.2s ease;
-  color: var(--text-dark);
-}
-
-.history-item:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
-}
-
-.history-header {
-  display: flex;
-  justify-content: space-between;
-  margin-bottom: 8px;
-}
-
-.history-date {
-  font-weight: 600;
-  color: var(--text-dark);
-}
-
-.history-type {
-  background-color: var(--primary);
-  color: white;
-  padding: 2px 8px;
-  border-radius: 10px;
-  font-size: 12px;
-}
-
-.history-summary {
-  font-size: 14px;
-  color: var(--text-dark);
-  display: -webkit-box;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
-  overflow: hidden;
-}
-
-.no-history {
-  text-align: center;
-  padding: 20px;
-  color: var(--text-dark);
-}
-
-/* 评分标签 */
-.score-tag {
-  display: inline-block;
-  padding: 4px 8px;
-  border-radius: 12px;
-  font-size: 12px;
-  font-weight: 600;
-  margin-top: 8px;
-  color: var(--text-dark);
-}
-
-.high-score {
-  background-color: #d1fae5;
-  color: var(--text-dark);
-}
-
-.medium-score {
-  background-color: #fef3c7;
-  color: var(--text-dark);
-}
-
-.low-score {
-  background-color: #fee2e2;
-  color: var(--text-dark);
-}
-
-/* 表单样式 */
-.form-group {
-  margin-bottom: 20px;
-}
-
-.form-group label {
-  display: block;
-  margin-bottom: 8px;
-  font-size: 14px;
-  color: var(--text-dark);
-}
-
-.form-group input,
-.form-group select {
-  width: 100%;
-  padding: 12px 16px;
-  border: 1px solid var(--border-color);
-  border-radius: 8px;
-  font-size: 14px;
-  outline: none;
-  transition: border-color 0.3s;
-  color: var(--text-dark);
-}
-
-.form-group input:focus,
-.form-group select:focus {
-  border-color: var(--primary);
-}
-
-.modal-actions {
-  display: flex;
-  gap: 12px;
-  justify-content: flex-end;
-}
-
-.modal-btn {
-  padding: 10px 20px;
-  border-radius: 8px;
-  font-size: 14px;
-  font-weight: 600;
-  cursor: pointer;
-  transition: all 0.3s ease;
-  min-width: 44px;
-  min-height: 44px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.cancel-btn {
-  background-color: #f1f5f9;
-  color: var(--text-dark);
-  border: none;
-}
-
-.save-btn {
-  background-color: var(--primary);
-  color: white;
-  border: none;
+.report-date {
+  color: #666;
+  margin-bottom: 16px;
 }
 
-/* 确保所有按钮有足够点击区域 */
-button {
-  min-width: 44px;
-  min-height: 44px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
+.report-content pre {
+  white-space: pre-wrap;
+  font-family: inherit;
+  line-height: 1.6;
 }

+ 151 - 499
ai-assisant/src/modules/crm/mobile/page-crm-training/page-crm-training.ts

@@ -1,560 +1,212 @@
-import { Component, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
+import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
+import { TrainingReportsComponent } from './report-viewer.component';
 import { TrainingService } from './training.service';
-import { CloudObject, CloudQuery } from '../../../../lib/ncloud';
+import { Report, Scene } from './report.interface';
 
 @Component({
-  selector: 'app-page-crm-training',
+  selector: 'page-crm-training',
   standalone: true,
-  imports: [CommonModule, FormsModule],
+  imports: [CommonModule, FormsModule, TrainingReportsComponent],
   templateUrl: './page-crm-training.html',
   styleUrls: ['./page-crm-training.scss']
 })
-export class PageCrmTraining implements AfterViewInit {
-  @ViewChild('conversationContainer') conversationContainer!: ElementRef;
-  private trainingService = inject(TrainingService);
-
-  // 状态控制
-  isEditing = false;
-  isRecording = false;
-  showAddCustomerModal = false;
-  showFullReportModal = false;
-  showHistoryReportModal = false;
-
-  // 表单数据
-  userInput = '';
-  newCustomerTypeName = '';
-  newCustomerDifficulty = 1;
-
-  // 评分数据
-  currentScore = 0;
-  scoreChange = 0;
+export class PageCrmTraining implements OnInit {
+  @ViewChild('chatMessagesContainer') chatMessagesContainer!: ElementRef;
+
+  // 场景数据
+  sceneLevels: { value: string; label: string }[] = [];
+  customerTypes: { value: string; label: string }[] = [];
+  allScenes: Scene[] = [];
+  filteredScenes: Scene[] = [];
+  selectedLevel: string = '';
+  selectedCustomerType: string = '';
+  selectedScene: Scene | null = null;
 
   // 对话数据
-  messages: Message[] = [];
-  currentScenario: any = null;
-  currentRecordId: string | null = null;
-
-  // 客户类型数据
-  customerTypes: CustomerType[] = [];
-  difficultyTabs: DifficultyTab[] = [
-    { label: '⭐ 初级', value: 1, active: true },
-    { label: '⭐⭐ 中级', value: 2, active: false },
-    { label: '⭐⭐⭐ 高级', value: 3, active: false },
-    { label: '专家挑战', value: 4, active: false }
-  ];
-
-  // 技能数据
-  skills: Skill[] = [
-    { name: '反应力', value: 0, key: 'reactivity' },
-    { name: '话术', value: 0, key: 'technology' },
-    { name: '说服力', value: 0, key: 'persuasiveness' },
-    { name: '专业度', value: 0, key: 'professionalism' }
-  ];
-
-  // 当前报告数据
-  currentReport: Report = {
-    date: new Date(),
-    customerType: '',
-    difficulty: '',
-    summary: '',
-    tags: [],
-    evaluation: {
-      intro: '',
-      points: []
-    },
-    summaryMethods: {
-      intro: '',
-      methods: [],
-      tags: []
-    },
-    score: 0
-  };
-
-  // 历史报告数据
-  historyReports: Report[] = [];
+  chatMessages: Array<{
+    type: 'user' | 'bot';
+    content: string;
+    timestamp: Date;
+  }> = [];
+  chatInput: string = '';
+  currentRound: number = 1;
+  maxRounds: number = 5;
+  conversationScore: number | null = null;
 
-  // 解决模板中 Math 报错
-  Math = Math;
+  // 报告数据
+  generatedReports: Report[] = [];
+  selectedReport: Report | null = null;
 
-  async ngAfterViewInit() {
-    await this.loadTrainingScenarios();
-    await this.loadTrainingHistory();
-    this.scrollToBottom();
-  }
+  constructor(private trainingService: TrainingService) {}
 
-  /* 核心功能方法 */
-  scrollToBottom(): void {
-    try {
-      setTimeout(() => {
-        this.conversationContainer.nativeElement.scrollTop = 
-          this.conversationContainer.nativeElement.scrollHeight;
-      }, 100);
-    } catch(err) { }
+  ngOnInit(): void {
+    this.loadInitialData();
   }
 
-  // 智能返回方法
-  goBack(): void {
-    if (this.showFullReportModal) {
-      this.closeFullReportModal();
-    } else if (this.showHistoryReportModal) {
-      this.closeHistoryReportModal();
-    } else if (this.showAddCustomerModal) {
-      this.closeAddCustomerModal();
-    } else {
-      console.log('返回上一页');
-    }
-  }
-
-  canGoBack(): boolean {
-    return this.showFullReportModal || 
-           this.showHistoryReportModal || 
-           this.showAddCustomerModal;
-  }
+  private loadInitialData(): void {
+    this.trainingService.getSceneLevels().subscribe(levels => {
+      this.sceneLevels = levels;
+    });
 
-  /* 数据加载方法 */
-  async loadTrainingScenarios() {
-    try {
-      const activeTab = this.difficultyTabs.find(t => t.active);
-      const scenarios = await this.trainingService.getTrainingScenarios({
-        difficulty: activeTab?.value
-      });
-      
-      this.customerTypes = scenarios.map((s: any) => ({
-        id: s.id,
-        name: s.get('name'),
-        active: false,
-        icon: this.getRandomIcon(),
-        difficulty: s.get('difficulty'),
-        customerRole: s.get('customerRole')
-      }));
-      
-      if (this.customerTypes.length > 0) {
-        this.customerTypes[0].active = true;
-      }
-    } catch (error) {
-      console.error('加载训练场景失败:', error);
-    }
-  }
+    this.trainingService.getCustomerTypes().subscribe(types => {
+      this.customerTypes = types;
+    });
 
-  async loadTrainingHistory() {
-    try {
-      const records = await this.trainingService.getUserTrainingHistory(5);
-      this.historyReports = await Promise.all(records.map(async (r: any) => {
-        const scenario = await new CloudQuery("TrainingScenario").get(r.get('scenarioId').objectId);
-        
-        return {
-          id: r.id,
-          date: new Date(r.get('createdAt')),
-          customerType: r.get('customerType'),
-          difficulty: this.getDifficultyLabel(scenario?.get('difficulty')),
-          summary: r.get('analysisResult') || '暂无分析结果',
-          tags: [],
-          evaluation: {
-            intro: '',
-            points: []
-          },
-          summaryMethods: {
-            intro: '',
-            methods: [],
-            tags: []
-          },
-          score: 0
-        };
-      }));
-    } catch (error) {
-      console.error('加载训练历史失败:', error);
-    }
+    this.trainingService.getAllScenes().subscribe(scenes => {
+      this.allScenes = scenes;
+      this.filteredScenes = [...scenes];
+    });
   }
 
-  /* 训练场景相关方法 */
-  async selectTab(tab: DifficultyTab) {
-    this.difficultyTabs.forEach(t => t.active = false);
-    tab.active = true;
-    await this.loadTrainingScenarios();
+  filterScenes(): void {
+    this.filteredScenes = this.allScenes.filter(scene => {
+      const levelMatch = !this.selectedLevel || scene.level === this.selectedLevel;
+      const typeMatch = !this.selectedCustomerType || scene.customerType === this.selectedCustomerType;
+      return levelMatch && typeMatch;
+    });
   }
 
-  selectCustomerType(type: CustomerType) {
-    if (this.isEditing) return;
-    
-    this.customerTypes.forEach(t => t.active = false);
-    type.active = true;
+  getLevelLabel(levelValue: string): string {
+    const level = this.sceneLevels.find(l => l.value === levelValue);
+    return level ? level.label : levelValue;
   }
 
-  toggleEditMode() {
-    this.isEditing = !this.isEditing;
+  getCustomerLabel(typeValue: string): string {
+    const type = this.customerTypes.find(t => t.value === typeValue);
+    return type ? type.label : typeValue;
   }
 
-  async deleteCustomerType(type: CustomerType) {
-    if (this.customerTypes.length <= 1) {
-      alert('至少需要保留一个客户类型');
-      return;
-    }
-    
-    try {
-      await this.trainingService.deleteCustomerType(type.id);
-      const index = this.customerTypes.indexOf(type);
-      if (index !== -1) {
-        this.customerTypes.splice(index, 1);
-        if (type.active && this.customerTypes.length > 0) {
-          this.customerTypes[0].active = true;
-        }
-      }
-    } catch (error) {
-      console.error('删除客户类型失败:', error);
-      alert('删除失败,请稍后重试');
-    }
+  selectScene(scene: Scene): void {
+    this.selectedScene = scene;
+    this.currentRound = 1;
+    this.conversationScore = null;
+    this.chatMessages = [{
+      type: 'bot',
+      content: scene.initialMessage,
+      timestamp: new Date()
+    }];
+    this.scrollToBottom();
   }
 
-  /* 客户类型管理 */
-  openAddCustomerModal() {
-    this.showAddCustomerModal = true;
-  }
+  sendMessage(): void {
+    if (!this.chatInput.trim() || !this.selectedScene) return;
 
-  closeAddCustomerModal() {
-    this.showAddCustomerModal = false;
-    this.newCustomerTypeName = '';
-    this.newCustomerDifficulty = 1;
-  }
+    // 添加用户消息
+    const userMessage = {
+      type: 'user' as const,
+      content: this.chatInput,
+      timestamp: new Date()
+    };
+    this.chatMessages.push(userMessage);
+    this.chatInput = '';
+    this.scrollToBottom();
 
-  async saveCustomerType() {
-    if (!this.newCustomerTypeName.trim()) return;
-    
-    try {
-      const newType = await this.trainingService.addCustomerType(
-        this.newCustomerTypeName,
-        this.newCustomerDifficulty,
-        '自定义角色'
+    // 模拟AI回复
+    setTimeout(() => {
+      const botResponse = this.trainingService.generateResponse(
+        this.selectedScene!.id, 
+        userMessage.content
       );
       
-      this.customerTypes.push({
-        id: newType.id|| '',
-        name: this.newCustomerTypeName,
-        active: false,
-        icon: this.getRandomIcon(),
-        difficulty: this.newCustomerDifficulty,
-        customerRole: '自定义角色'
+      this.chatMessages.push({
+        type: 'bot' as const,
+        content: botResponse,
+        timestamp: new Date()
       });
-      
-      this.closeAddCustomerModal();
-    } catch (error) {
-      console.error('添加客户类型失败:', error);
-      alert('添加失败,请稍后重试');
-    }
-  }
 
-  getRandomIcon(): string {
-    const icons = [
-      'fa-user', 'fa-user-tie', 'fa-users', 
-      'fa-user-graduate', 'fa-user-md', 'fa-user-ninja'
-    ];
-    return icons[Math.floor(Math.random() * icons.length)];
-  }
+      // 更新轮次和评分
+      this.currentRound = Math.ceil(this.chatMessages.length / 2);
+      if (this.currentRound <= this.maxRounds) {
+        this.updateScore();
+      }
 
-  /* 训练相关方法 */
-  async startTraining() {
-    const activeType = this.customerTypes.find(type => type.active);
-    if (!activeType) return;
-    
-    try {
-      const record = await this.trainingService.createTrainingRecord(activeType.id, activeType.name);
-      this.currentRecordId = record.id|| null;
-      
-      this.currentScenario = {
-        id: activeType.id,
-        name: activeType.name,
-        difficulty: activeType.difficulty,
-        customerRole: activeType.customerRole
-      };
-      
-      this.messages = [{
-        text: `您好,我是${activeType.customerRole},${this.generateCustomerOpening(activeType.name)}`,
-        isCustomer: true
-      }];
-      
-      this.currentScore = 70;
-      this.scoreChange = 0;
-      this.skills.forEach(s => s.value = 70);
-      
       this.scrollToBottom();
-    } catch (error) {
-      console.error('开始训练失败:', error);
-    }
+    }, 800);
   }
 
-  generateCustomerOpening(type: string): string {
-    const openings: Record<string, string[]> = {
-      '商务客户': [
-        "我想咨询一下贵公司的企业服务方案。",
-        "我们需要预订一批商务客房,能提供优惠吗?",
-        "关于贵公司的VIP服务,我有几个问题想了解。"
-      ],
-      '家庭客户': [
-        "我们全家计划下周出游,能推荐适合的套餐吗?",
-        "带孩子入住有什么需要注意的事项吗?",
-        "请问有家庭优惠活动吗?"
-      ],
-      'VIP客户': [
-        "作为VIP会员,我应该享受哪些专属权益?",
-        "这次入住希望能升级房型,可以安排吗?",
-        "我的积分可以兑换什么服务?"
-      ]
-    };
-    
-    const defaultOpenings = [
-      "我想咨询一些服务细节。",
-      "能介绍一下你们的主要产品吗?",
-      "我对你们的服务很感兴趣。"
-    ];
+  private updateScore(): void {
+    const baseScore = 80;
+    const roundBonus = (this.currentRound - 1) * 5;
+    const randomVariation = Math.floor(Math.random() * 10);
     
-    return openings[type] 
-      ? openings[type][Math.floor(Math.random() * openings[type].length)]
-      : defaultOpenings[Math.floor(Math.random() * defaultOpenings.length)];
-  }
+    this.conversationScore = Math.min(baseScore + roundBonus + randomVariation, 100);
 
-  /* 对话功能 */
-  toggleRecording() {
-    this.isRecording = !this.isRecording;
-    if (this.isRecording) {
-      this.startVoiceRecording();
-    } else {
-      this.stopVoiceRecording();
+    // 如果完成所有轮次,生成报告
+    if (this.currentRound === this.maxRounds) {
+      this.generateReport();
     }
   }
 
-  startVoiceRecording() {
-    console.log('开始录音...');
-  }
-
-  stopVoiceRecording() {
-    console.log('停止录音...');
-  }
+  private generateReport(): void {
+    if (!this.selectedScene) return;
+
+    const newReport: Report = {
+      id: `report-${Date.now()}`,
+      name: this.selectedScene.name,
+      sceneId: this.selectedScene.id,
+      date: new Date().toISOString(),
+      difficulty: this.selectedScene.level,
+      customerRole: this.selectedScene.customerType,
+      score: this.conversationScore || 0,
+      evaluations: {
+        responsiveness: Math.min(this.conversationScore! + Math.floor(Math.random() * 10) - 5, 100),
+        话术: Math.min((this.conversationScore || 80) + Math.floor(Math.random() * 8) - 4, 100),
+        persuasion: Math.min((this.conversationScore || 75) + Math.floor(Math.random() * 12) - 6, 100),
+        professionalism: Math.min((this.conversationScore || 85) + Math.floor(Math.random() * 6) - 3, 100)
+      },
+      summary: this.generateSummaryText()
+    };
 
-  async sendMessage() {
-    if (!this.userInput.trim() || !this.currentRecordId) return;
-    
-    this.messages.push({ 
-      text: this.userInput, 
-      isCustomer: false 
-    });
-    this.userInput = '';
-    this.scrollToBottom();
-    
-    setTimeout(async () => {
-      const aiResponse = this.generateAIResponse();
-      this.messages.push({ 
-        text: aiResponse, 
-        isCustomer: true 
-      });
-      
-      this.updateScore();
-      this.scrollToBottom();
-      
-      if (this.messages.length >= 8) {
-        await this.saveAnalysisResult();
-      }
-    }, 1000 + Math.random() * 1000);
+    this.generatedReports.unshift(newReport);
   }
 
-  generateAIResponse(): string {
-    const responses = [
-      "听起来不错,我会考虑这个方案。能提供一份详细报价吗?",
-      "这个方案我需要再考虑一下,能否提供其他选择?",
-      "感谢您的建议,我会和团队讨论后给您回复。",
-      "这个价格可以接受,请问包含哪些服务?",
-      "作为VIP会员,我希望能有更多优惠,可以吗?"
+  private generateSummaryText(): string {
+    const strengths = [
+      '展现了出色的沟通技巧',
+      '对产品知识掌握扎实',
+      '能够有效处理客户异议',
+      '表现出了高度的专业性',
+      '对话流畅自然',
+      '反应迅速准确'
     ];
-    return responses[Math.floor(Math.random() * responses.length)];
-  }
-
-  /* 评分系统 */
-  updateScore() {
-    const change = Math.floor(Math.random() * 5) - 2;
-    this.currentScore = Math.min(100, Math.max(0, this.currentScore + change));
-    this.scoreChange = Math.floor(Math.random() * 10) - 3;
     
-    this.skills.forEach(skill => {
-      skill.value = Math.min(100, Math.max(0, skill.value + (Math.floor(Math.random() * 5) - 1)))
-    });
-  }
-
-  getProgressOffset(): number {
-    return (100 - this.currentScore) / 100 * 502;
-  }
-
-  getSkillColor(value: number): string {
-    if (value >= 80) return '#34a853';
-    if (value >= 60) return '#f9ab00';
-    return '#ea4335';
-  }
-
-  /* 报告系统 */
-  async saveAnalysisResult() {
-    if (!this.currentRecordId) return;
-    
-    const analysis = `本次训练评分: ${this.currentScore}\n\n技能评估:\n` +
-      this.skills.map(s => `${s.name}: ${s.value}`).join('\n') +
-      `\n\n总结: ${this.generateSummary()}`;
-    
-    try {
-      await this.trainingService.updateTrainingRecordAnalysis(
-        this.currentRecordId,
-        analysis
-      );
-      
-      this.currentReport = {
-        date: new Date(),
-        customerType: this.getSelectedCustomerType(),
-        difficulty: this.getSelectedDifficulty(),
-        summary: analysis,
-        tags: ['训练完成', '技能评估'],
-        evaluation: {
-          intro: '训练结果分析:',
-          points: this.skills.map(s => `${s.name}得分: ${s.value}`)
-        },
-        summaryMethods: {
-          intro: '建议改进方向:',
-          methods: this.generateImprovementMethods(),
-          tags: ['改进建议']
-        },
-        score: this.currentScore
-      };
-      
-      await this.loadTrainingHistory();
-    } catch (error) {
-      console.error('保存分析结果失败:', error);
-    }
-  }
+    const improvements = [
+      '可以进一步提高反应速度',
+      '建议丰富案例说明',
+      '需要加强价格谈判技巧',
+      '可优化话术结构',
+      '应更关注客户需求',
+      '可增加数据支持论点'
+    ];
 
-  generateImprovementMethods(): string[] {
-    const methods = [];
-    if (this.skills[0].value < 80) {
-      methods.push('加强反应速度训练,提高即时应对能力');
-    }
-    if (this.skills[1].value < 80) {
-      methods.push('学习更多话术技巧,丰富表达方式');
-    }
-    if (this.skills[2].value < 80) {
-      methods.push('提高说服力,学习心理学技巧');
-    }
-    if (this.skills[3].value < 80) {
-      methods.push('加强专业知识学习,提高专业度');
-    }
-    return methods.length > 0 ? methods : ['各方面表现良好,继续保持!'];
+    return `在${this.selectedScene?.name}场景训练中,${
+      strengths[Math.floor(Math.random() * strengths.length)]
+    },${improvements[Math.floor(Math.random() * improvements.length)]}。`;
   }
 
-  generateSummary(): string {
-    const avgScore = this.skills.reduce((sum, skill) => sum + skill.value, 0) / this.skills.length;
-    if (avgScore >= 80) {
-      return '表现优秀!继续保持当前训练强度,可以挑战更高难度场景。';
-    } else if (avgScore >= 60) {
-      return '表现良好,但仍有提升空间。建议针对薄弱环节加强训练。';
-    } else {
-      return '表现有待提高。建议从基础场景开始,逐步提升各项能力。';
+  resetConversation(): void {
+    if (this.selectedScene) {
+      this.selectScene(this.selectedScene);
     }
   }
 
-  openFullReportModal() {
-    this.showFullReportModal = true;
-  }
-
-  closeFullReportModal() {
-    this.showFullReportModal = false;
+  viewReport(report: Report): void {
+    this.selectedReport = report;
+    console.log('查看完整报告:', report);
   }
 
-  openHistoryReportModal() {
-    this.showHistoryReportModal = true;
+  closeReport(): void {
+    this.selectedReport = null;
   }
 
-  closeHistoryReportModal() {
-    this.showHistoryReportModal = false;
-  }
-
-  async viewHistoryReport(report: Report) {
-    this.currentReport = report;
-    this.currentScore = report.score;
-    this.closeHistoryReportModal();
+  private scrollToBottom(): void {
     setTimeout(() => {
-      this.openFullReportModal();
-    }, 300);
-  }
-
-  /* 辅助方法 */
-  getCustomerIcon(): string {
-    const activeType = this.customerTypes.find(type => type.active);
-    return activeType ? activeType.icon : 'fa-user';
-  }
-
-  getSelectedCustomerType(): string {
-    const activeType = this.customerTypes.find(type => type.active);
-    return activeType ? activeType.name : '';
-  }
-
-  getSelectedDifficulty(): string {
-    const activeTab = this.difficultyTabs.find(tab => tab.active);
-    return activeTab ? activeTab.label : '';
-  }
-
-  getDifficultyLabel(difficulty?: number): string {
-    if (difficulty === undefined) return '';
-    const labels = ['⭐ 初级', '⭐⭐ 中级', '⭐⭐⭐ 高级', '专家挑战'];
-    return labels[difficulty - 1] || '';
-  }
-
-  getScoreTagClass(): string {
-    if (this.currentScore >= 80) return 'high-score';
-    if (this.currentScore >= 60) return 'medium-score';
-    return 'low-score';
+      if (this.chatMessagesContainer) {
+        this.chatMessagesContainer.nativeElement.scrollTop = 
+          this.chatMessagesContainer.nativeElement.scrollHeight;
+      }
+    }, 100);
   }
-}
-
-/* 类型定义 */
-interface Message {
-  text: string;
-  isCustomer: boolean;
-}
-
-interface CustomerType {
-  id: string;
-  name: string;
-  active: boolean;
-  icon: string;
-  difficulty: number;
-  customerRole: string;
-}
-
-interface DifficultyTab {
-  label: string;
-  value: number;
-  active: boolean;
-}
-
-interface Skill {
-  name: string;
-  value: number;
-  key: string;
-}
-
-interface Evaluation {
-  intro: string;
-  points: string[];
-}
-
-interface SummaryMethods {
-  intro: string;
-  methods: string[];
-  tags: string[];
-}
-
-interface Report {
-  id?: string;
-  date: Date;
-  customerType: string;
-  difficulty: string;
-  summary: string;
-  tags: string[];
-  evaluation: Evaluation;
-  summaryMethods: SummaryMethods;
-  score: number;
 }

+ 284 - 0
ai-assisant/src/modules/crm/mobile/page-crm-training/report-viewer.component.ts

@@ -0,0 +1,284 @@
+import { Component, Input, Output, EventEmitter } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Report } from './report.interface';
+
+interface EvaluationItem {
+  label: string;
+  score: number;
+}
+
+@Component({
+  selector: 'app-training-reports',
+  standalone: true,
+  imports: [CommonModule],
+  template: `
+    <div class="training-reports">
+      <h2>训练报告</h2>
+      
+      <div class="report-list">
+        <div *ngFor="let report of reports" 
+             class="report-card"
+             (click)="selectReport(report)">
+          <div class="report-header">
+            <h3>{{report.name}}</h3>
+            <span>{{report.date | date:'yyyy-MM-dd HH:mm'}}</span>
+          </div>
+          <div class="report-meta">
+            <span>难度: {{getDifficultyLabel(report.difficulty)}}</span>
+            <span>角色: {{report.customerRole}}</span>
+            <span class="score-badge">得分: {{report.score || 0}}/100</span>
+          </div>
+        </div>
+      </div>
+
+      <div *ngIf="selectedReport" class="modal-overlay" (click)="closeReport($event)">
+        <div class="modal-content" (click)="$event.stopPropagation()">
+          <div class="report-detail">
+            <div class="detail-header">
+              <h2>{{selectedReport.name}}</h2>
+              <button class="close-btn" (click)="closeReport()">×</button>
+            </div>
+            <div class="detail-meta">
+              <span><strong>日期:</strong> {{selectedReport.date | date:'yyyy-MM-dd HH:mm'}}</span>
+              <span><strong>难度:</strong> {{getDifficultyLabel(selectedReport.difficulty)}}</span>
+              <span><strong>角色:</strong> {{selectedReport.customerRole}}</span>
+              <span><strong>总分:</strong> {{selectedReport.score || 0}}/100</span>
+            </div>
+            
+            <div class="detail-evaluations">
+              <h3>能力评估</h3>
+              <div class="eval-grid">
+                <div *ngFor="let eval of getEvaluations(selectedReport)" class="eval-card">
+                  <div class="eval-score">{{eval.score}}</div>
+                  <div class="eval-label">{{eval.label}}</div>
+                  <div class="progress-bar">
+                    <div class="progress-fill" [style.width.%]="eval.score"></div>
+                  </div>
+                </div>
+              </div>
+            </div>
+            
+            <div class="detail-summary">
+              <h3>训练总结</h3>
+              <p>{{selectedReport.summary}}</p>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  `,
+  styles: [`
+    .training-reports {
+      padding: 20px;
+      max-width: 1200px;
+      margin: 0 auto;
+    }
+    
+    .report-list {
+      display: grid;
+      gap: 15px;
+    }
+    
+    .report-card {
+      background: white;
+      border-radius: 8px;
+      padding: 15px;
+      cursor: pointer;
+      transition: all 0.2s ease;
+      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+      border-left: 4px solid #4CAF50;
+    }
+    
+    .report-card:hover {
+      transform: translateY(-2px);
+      box-shadow: 0 4px 8px rgba(0,0,0,0.15);
+    }
+    
+    .report-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 10px;
+    }
+    
+    .report-header h3 {
+      margin: 0;
+      color: #333;
+    }
+    
+    .report-meta {
+      display: flex;
+      gap: 15px;
+      color: #666;
+      font-size: 0.9rem;
+      flex-wrap: wrap;
+    }
+    
+    .score-badge {
+      background: #4CAF50;
+      color: white;
+      padding: 2px 8px;
+      border-radius: 12px;
+      font-size: 0.8rem;
+    }
+    
+    .modal-overlay {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background: rgba(0,0,0,0.5);
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      z-index: 1000;
+    }
+    
+    .modal-content {
+      background: white;
+      border-radius: 8px;
+      width: 90%;
+      max-width: 800px;
+      max-height: 90vh;
+      overflow: auto;
+      box-shadow: 0 4px 20px rgba(0,0,0,0.2);
+    }
+    
+    .report-detail {
+      padding: 25px;
+    }
+    
+    .detail-header {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      margin-bottom: 20px;
+      border-bottom: 1px solid #eee;
+      padding-bottom: 15px;
+    }
+    
+    .detail-header h2 {
+      margin: 0;
+      color: #333;
+    }
+    
+    .close-btn {
+      background: none;
+      border: none;
+      font-size: 24px;
+      cursor: pointer;
+      color: #666;
+    }
+    
+    .detail-meta {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 15px;
+      margin-bottom: 20px;
+      color: #555;
+    }
+    
+    .detail-meta span {
+      display: flex;
+      align-items: center;
+      gap: 5px;
+    }
+    
+    .detail-meta strong {
+      color: #333;
+    }
+    
+    .eval-grid {
+      display: grid;
+      grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
+      gap: 15px;
+      margin-top: 15px;
+    }
+    
+    .eval-card {
+      background: #f8f9fa;
+      border-radius: 8px;
+      padding: 15px;
+      text-align: center;
+    }
+    
+    .eval-score {
+      font-size: 28px;
+      font-weight: bold;
+      color: #333;
+      margin-bottom: 5px;
+    }
+    
+    .eval-label {
+      color: #666;
+      font-size: 0.9rem;
+      margin-bottom: 10px;
+    }
+    
+    .progress-bar {
+      height: 6px;
+      background: #e0e0e0;
+      border-radius: 3px;
+      overflow: hidden;
+    }
+    
+    .progress-fill {
+      height: 100%;
+      background: #4CAF50;
+      transition: width 0.3s ease;
+    }
+    
+    .detail-summary {
+      margin-top: 25px;
+      padding-top: 20px;
+      border-top: 1px solid #eee;
+    }
+    
+    .detail-summary h3 {
+      color: #333;
+      margin-top: 0;
+    }
+    
+    .detail-summary p {
+      line-height: 1.6;
+      color: #444;
+    }
+  `]
+})
+export class TrainingReportsComponent {
+  @Input() reports: Report[] = [];
+  @Output() reportSelected = new EventEmitter<Report>();
+
+  selectedReport: Report | null = null;
+
+  getDifficultyLabel(difficulty: string): string {
+    const labels: Record<string, string> = {
+      'easy': '初级',
+      'medium': '中级',
+      'hard': '高级',
+      'expert': '专家级'
+    };
+    return labels[difficulty] || difficulty;
+  }
+
+  getEvaluations(report: Report): EvaluationItem[] {
+    return [
+      { label: '反应力', score: report.evaluations.responsiveness },
+      { label: '话术', score: report.evaluations.话术 },
+      { label: '说服力', score: report.evaluations.persuasion },
+      { label: '专业度', score: report.evaluations.professionalism }
+    ];
+  }
+
+  selectReport(report: Report): void {
+    this.selectedReport = report;
+    this.reportSelected.emit(report);
+  }
+
+  closeReport(event?: MouseEvent): void {
+    if (!event || (event.target as HTMLElement).classList.contains('modal-overlay')) {
+      this.selectedReport = null;
+    }
+  }
+}

+ 40 - 0
ai-assisant/src/modules/crm/mobile/page-crm-training/report.interface.ts

@@ -0,0 +1,40 @@
+// src/modules/crm/mobile/page-crm-training/report.interface.ts
+
+// 场景接口
+export interface Scene {
+  id: string;
+  name: string;
+  description: string;
+  level: string;          // 对应难度值:easy/medium/hard
+  customerType: string;   // 对应客户类型值
+  initialMessage: string; // 场景初始消息
+}
+
+// 报告接口
+export interface Report {
+  id: string;
+  name: string;               // 报告名称
+  sceneId?: string;           // 关联场景ID(可选)
+  date: string;               // 报告日期
+  difficulty: string;         // 难度级别
+  customerRole: string;       // 客户角色
+  score?: number;             // 总分(可选)
+  evaluations: {              // 各项评估分数
+    responsiveness: number;   // 反应力
+    话术: number;             // 话术
+    persuasion: number;       // 说服力
+    professionalism: number;  // 专业度
+  };
+  summary: string;            // 总结评语
+}
+
+// 可选:其他相关类型
+export interface SceneLevel {
+  value: string;  // easy/medium/hard
+  label: string;  // 初级/中级/高级
+}
+
+export interface CustomerType {
+  value: string;  // new/returning/vip 等
+  label: string;  // 新客户/回头客/VIP客户 等
+}

+ 137 - 81
ai-assisant/src/modules/crm/mobile/page-crm-training/training.service.ts

@@ -1,103 +1,159 @@
 import { Injectable } from '@angular/core';
-import { CloudObject, CloudQuery, CloudUser } from '../../../../lib/ncloud';
+import { Observable, of } from 'rxjs';
+import { delay } from 'rxjs/operators';
 
 @Injectable({
   providedIn: 'root'
 })
 export class TrainingService {
-  private currentUser = new CloudUser();
+  // 模拟数据库
+  private sceneLevels: SceneLevel[] = [
+    { value: 'easy', label: '初级' },
+    { value: 'medium', label: '中级' },
+    { value: 'hard', label: '高级' }
+  ];
 
-  constructor() { }
-
-  async getCurrentUser() {
-    return await this.currentUser.current();
-  }
+  private customerTypes: CustomerType[] = [
+    { value: 'new', label: '新客户' },
+    { value: 'returning', label: '回头客' },
+    { value: 'vip', label: 'VIP客户' },
+    { value: 'complaining', label: '投诉客户' }
+  ];
 
-  async getTrainingScenarios(options?: {
-    difficulty?: number;
-    customerRole?: string;
-    limit?: number;
-    skip?: number;
-    order?: string;
-  }) {
-    const query = new CloudQuery("TrainingScenario");
-    
-    if (options?.difficulty) {
-      query.equalTo("difficulty", options.difficulty);
-    }
-    
-    if (options?.customerRole) {
-      query.equalTo("customerRole", options.customerRole);
-    }
-    
-    if (options?.limit) {
-      query.limit(options.limit);
-    }
-    
-    if (options?.skip) {
-      query.skip(options.skip);
+  private allScenes: Scene[] = [
+    {
+      id: 'scene-1',
+      name: '产品初次介绍',
+      description: '向新客户介绍公司主打产品',
+      level: 'easy',
+      customerType: 'new',
+      initialMessage: '您好,我对贵公司的产品很感兴趣,能简单介绍一下吗?'
+    },
+    {
+      id: 'scene-2',
+      name: '处理价格异议',
+      description: '应对客户对价格的质疑',
+      level: 'medium',
+      customerType: 'returning',
+      initialMessage: '你们的产品价格比竞争对手高不少,能解释下为什么吗?'
+    },
+    {
+      id: 'scene-3',
+      name: 'VIP客户维护',
+      description: '与重要客户保持良好关系',
+      level: 'medium',
+      customerType: 'vip',
+      initialMessage: '王总,感谢您一直以来的支持,最近使用我们的产品还满意吗?'
+    },
+    {
+      id: 'scene-4',
+      name: '处理严重投诉',
+      description: '解决客户重大不满问题',
+      level: 'hard',
+      customerType: 'complaining',
+      initialMessage: '你们的产品简直太差了!我要退货并要求赔偿!'
     }
-    
-    if (options?.order) {
-      const [field, direction] = options.order.split('_');
-      query.orderBy(field, direction as 'asc' | 'desc');
+  ];
+
+  private sampleReports: Report[] = [
+    {
+      id: 'report-1',
+      sceneId: 'scene-1',
+      sceneName: '产品初次介绍',
+      date: new Date('2023-06-15').toISOString(),
+      score: 88,
+      evaluations: {
+        responsiveness: 85,
+        话术: 82,
+        persuasion: 78,
+        professionalism: 90
+      },
+      summary: '产品介绍流畅,但未能有效突出产品独特卖点'
     }
-    
-    return await query.find();
+  ];
+
+  constructor() { }
+
+  getSceneLevels(): Observable<SceneLevel[]> {
+    return of(this.sceneLevels).pipe(delay(300));
   }
 
-  async createTrainingRecord(scenarioId: string, customerType: string) {
-    const user = await this.getCurrentUser();
-    if (!user || !user.id) throw new Error("用户未登录");
-    
-    const record = new CloudObject("TrainingRecord");
-    record.set({
-      userId: user.toPointer(),
-scenarioId: new CloudObject("TrainingScenario").set({ objectId: scenarioId }),
-customerType,     
-      analysisResult: ""
-    });
-    
-    return await record.save();
+  getCustomerTypes(): Observable<CustomerType[]> {
+    return of(this.customerTypes).pipe(delay(300));
   }
 
-  async updateTrainingRecordAnalysis(recordId: string, analysis: string) {
-    const record = new CloudObject("TrainingRecord");
-    record.set({ objectId: recordId, analysisResult: analysis });
-    return await record.save();
+  getAllScenes(): Observable<Scene[]> {
+    return of(this.allScenes).pipe(delay(400));
   }
 
-  async getUserTrainingHistory(limit: number = 10, skip: number = 0) {
-    const user = await this.getCurrentUser();
-    if (!user || !user.id) throw new Error("用户未登录");
-    
-    const query = new CloudQuery("TrainingRecord");
-    query.equalTo("userId", user.toPointer());
-    query.orderBy("createdAt", "desc");
-    query.limit(limit);
-    query.skip(skip);
-    
-    return await query.find();
+  getGeneratedReports(): Observable<Report[]> {
+    return of(this.sampleReports).pipe(delay(500));
   }
 
-  async addCustomerType(name: string, difficulty: number, customerRole: string) {
-    const scenario = new CloudObject("TrainingScenario");
-    scenario.set({
-      name,
-      difficulty,
-      customerRole,
-      reactivity: 0,
-      technology: 0,
-      persuasiveness: 0,
-      professionalism: 0
-    });
+  generateResponse(sceneId: string, userInput: string): string {
+    const scene = this.allScenes.find(s => s.id === sceneId);
+    if (!scene) return '我不太明白您的意思';
+
+    // 根据场景和用户输入生成响应
+    if (sceneId === 'scene-1') {
+      if (userInput.includes('价格') || userInput.includes('多少钱')) {
+        return '我们的产品价格区间在X到Y之间,具体取决于配置。您对哪个型号感兴趣?';
+      }
+      return '这款产品的核心优势是A、B、C三个功能,能有效解决您面临的X问题。';
+    }
     
-    return await scenario.save();
-  }
+    if (sceneId === 'scene-4') {
+      if (userInput.includes('抱歉') || userInput.includes('对不起')) {
+        return '道歉是不够的!我需要你们立即解决问题!';
+      }
+      if (userInput.includes('方案') || userInput.includes('解决')) {
+        return '我们提供三种解决方案:1. 全额退款 2. 产品更换 3. 补偿优惠券,您倾向于哪种?';
+      }
+      return '我理解您的不满,请给我们一个改正的机会。';
+    }
 
-  async deleteCustomerType(scenarioId: string) {
-    const scenario = new CloudObject("TrainingScenario");
-    scenario.set({ objectId: scenarioId });
-    return await scenario.destroy();
+    // 默认回复逻辑
+    const responses = [
+      '这是一个很好的观点,我们可以详细讨论。',
+      '关于这个问题,我们的专业团队是这样建议的...',
+      '我理解您的担忧,实际上我们的产品是这样处理的...',
+      '根据大多数客户反馈,这个问题的解决方案是...'
+    ];
+    return responses[Math.floor(Math.random() * responses.length)];
   }
+}
+
+// 数据模型
+export interface SceneLevel {
+  value: string;
+  label: string;
+}
+
+export interface CustomerType {
+  value: string;
+  label: string;
+}
+
+export interface Scene {
+  id: string;
+  name: string;
+  description: string;
+  level: string;
+  customerType: string;
+  initialMessage: string;
+}
+
+export interface Report {
+  id: string;
+  sceneId: string;
+  sceneName: string;
+  date: string;
+  score: number;
+  evaluations: {
+    responsiveness: number;
+    话术: number;
+    persuasion: number;
+    professionalism: number;
+  };
+  summary: string;
 }

+ 6 - 0
package-lock.json

@@ -0,0 +1,6 @@
+{
+  "name": "AI-Salesperson Assistant",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {}
+}