|
@@ -1,159 +1,560 @@
|
|
|
-import { Component } from '@angular/core';
|
|
|
+import { Component, ElementRef, ViewChild, AfterViewInit, inject } from '@angular/core';
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
-import { MatIconModule } from '@angular/material/icon';
|
|
|
-import { MatButtonModule } from '@angular/material/button';
|
|
|
-import { MatCardModule } from '@angular/material/card';
|
|
|
-import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|
|
-import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
|
|
-import { faMicrophone, faSquare, faArrowLeft, faLayerGroup, faEdit, faPlus, faTimes, faChartLine, faFileAlt, faHistory, faArrowUp, faComments, faStar, faLightbulb } from '@fortawesome/free-solid-svg-icons';
|
|
|
+import { TrainingService } from './training.service';
|
|
|
+import { CloudObject, CloudQuery } from '../../../../lib/ncloud';
|
|
|
|
|
|
@Component({
|
|
|
selector: 'app-page-crm-training',
|
|
|
standalone: true,
|
|
|
- imports: [
|
|
|
- CommonModule,
|
|
|
- FormsModule,
|
|
|
- MatIconModule,
|
|
|
- MatButtonModule,
|
|
|
- MatCardModule,
|
|
|
- MatProgressSpinnerModule,
|
|
|
- FontAwesomeModule,
|
|
|
-
|
|
|
- ],
|
|
|
- templateUrl:'./page-crm-training.html',
|
|
|
+ imports: [CommonModule, FormsModule],
|
|
|
+ templateUrl: './page-crm-training.html',
|
|
|
styleUrls: ['./page-crm-training.scss']
|
|
|
})
|
|
|
-export class PageCrmTraining {
|
|
|
- // Font Awesome 图标
|
|
|
- faMicrophone = faMicrophone;
|
|
|
- faSquare = faSquare;
|
|
|
- faArrowLeft = faArrowLeft;
|
|
|
- faLayerGroup = faLayerGroup;
|
|
|
- faEdit = faEdit;
|
|
|
- faPlus = faPlus;
|
|
|
- faTimes = faTimes;
|
|
|
- faChartLine = faChartLine;
|
|
|
- faFileAlt = faFileAlt;
|
|
|
- faHistory = faHistory;
|
|
|
- faArrowUp = faArrowUp;
|
|
|
- faComments = faComments;
|
|
|
- faStar = faStar;
|
|
|
- faLightbulb = faLightbulb;
|
|
|
-
|
|
|
- // 组件状态和逻辑
|
|
|
- isVoiceActive = false;
|
|
|
- selectedDifficulty = '初级';
|
|
|
- difficulties = [
|
|
|
- { label: '⭐ 初级', value: '初级' },
|
|
|
- { label: '⭐⭐ 中级', value: '中级' },
|
|
|
- { label: '⭐⭐⭐ 高级', value: '高级' },
|
|
|
- { label: '专家挑战', value: '专家' }
|
|
|
- ];
|
|
|
-
|
|
|
- customerTypes = [
|
|
|
- { name: '商务客户', active: true },
|
|
|
- { name: '家庭客户', active: false },
|
|
|
- { name: 'VIP客户', active: false },
|
|
|
- { name: '投诉客户', active: false },
|
|
|
- { name: '团体客户', active: false }
|
|
|
- ];
|
|
|
- isEditingTypes = false;
|
|
|
- newCustomerType = '';
|
|
|
-
|
|
|
- messages = [
|
|
|
- { text: '您好,我想预订下周五的商务套房,你们有什么优惠吗?', isCustomer: true },
|
|
|
- { text: '感谢您的咨询!我们目前有商务套餐优惠,包含早餐和会议室使用,您需要了解详情吗?', isCustomer: false },
|
|
|
- { text: '会议室可以容纳多少人?另外我需要延迟退房到下午4点。', isCustomer: true },
|
|
|
- { text: '我们的商务套房会议室最多可容纳20人,延迟退房到下午4点需要额外支付50%的房费。', isCustomer: false },
|
|
|
- { text: '50%的费用太高了,我是贵酒店的黄金会员,能否提供免费延迟退房?', isCustomer: true },
|
|
|
- { text: '感谢您的会员支持!根据黄金会员权益,我们可以提供免费延迟退房到下午2点,或者您可以选择支付额外费用延长到4点。', isCustomer: false },
|
|
|
- { text: '下午2点可能不太够,我下午3点有个重要会议。有没有折中方案?', isCustomer: true },
|
|
|
- { text: '我理解您的情况。我们可以为您提供下午3点的延迟退房,只需额外支付25%的房费,这样您看可以接受吗?', isCustomer: false }
|
|
|
- ];
|
|
|
- newMessage = '';
|
|
|
-
|
|
|
- skills = [
|
|
|
- { name: '反应力', value: 90 },
|
|
|
- { name: '话术', value: 85 },
|
|
|
- { name: '说服力', value: 75 },
|
|
|
- { name: '专业度', value: 92 }
|
|
|
- ];
|
|
|
-
|
|
|
+export class PageCrmTraining implements AfterViewInit {
|
|
|
+ @ViewChild('conversationContainer') conversationContainer!: ElementRef;
|
|
|
+ private trainingService = inject(TrainingService);
|
|
|
+
|
|
|
+ // 状态控制
|
|
|
+ isEditing = false;
|
|
|
+ isRecording = false;
|
|
|
showAddCustomerModal = false;
|
|
|
showFullReportModal = false;
|
|
|
showHistoryReportModal = false;
|
|
|
-
|
|
|
- historyReports = [
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- date: '2023年10月15日 14:30',
|
|
|
- type: '商务客户',
|
|
|
- summary: '本次与商务客户的对话中,您展现了优秀的专业知识和反应能力。建议在价格谈判环节采用更积极的策略。'
|
|
|
+
|
|
|
+ // 表单数据
|
|
|
+ userInput = '';
|
|
|
+ newCustomerTypeName = '';
|
|
|
+ newCustomerDifficulty = 1;
|
|
|
+
|
|
|
+ // 评分数据
|
|
|
+ currentScore = 0;
|
|
|
+ scoreChange = 0;
|
|
|
+
|
|
|
+ // 对话数据
|
|
|
+ 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: []
|
|
|
},
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- date: '2023年10月12日 10:15',
|
|
|
- type: '投诉客户',
|
|
|
- summary: '处理客户投诉时展现了良好的同理心,但解决方案不够全面,建议加强问题解决能力训练。'
|
|
|
+ summaryMethods: {
|
|
|
+ intro: '',
|
|
|
+ methods: [],
|
|
|
+ tags: []
|
|
|
},
|
|
|
- {
|
|
|
- id: 3,
|
|
|
- date: '2023年10月10日 16:45',
|
|
|
- type: 'VIP客户',
|
|
|
- summary: '为VIP客户提供了个性化服务方案,展现了出色的服务意识,但在附加服务推荐上略显保守。'
|
|
|
+ score: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ // 历史报告数据
|
|
|
+ historyReports: Report[] = [];
|
|
|
+
|
|
|
+ // 解决模板中 Math 报错
|
|
|
+ Math = Math;
|
|
|
+
|
|
|
+ async ngAfterViewInit() {
|
|
|
+ await this.loadTrainingScenarios();
|
|
|
+ await this.loadTrainingHistory();
|
|
|
+ this.scrollToBottom();
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 核心功能方法 */
|
|
|
+ scrollToBottom(): void {
|
|
|
+ try {
|
|
|
+ setTimeout(() => {
|
|
|
+ this.conversationContainer.nativeElement.scrollTop =
|
|
|
+ this.conversationContainer.nativeElement.scrollHeight;
|
|
|
+ }, 100);
|
|
|
+ } catch(err) { }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 智能返回方法
|
|
|
+ goBack(): void {
|
|
|
+ if (this.showFullReportModal) {
|
|
|
+ this.closeFullReportModal();
|
|
|
+ } else if (this.showHistoryReportModal) {
|
|
|
+ this.closeHistoryReportModal();
|
|
|
+ } else if (this.showAddCustomerModal) {
|
|
|
+ this.closeAddCustomerModal();
|
|
|
+ } else {
|
|
|
+ console.log('返回上一页');
|
|
|
}
|
|
|
- ];
|
|
|
+ }
|
|
|
|
|
|
- toggleVoice() {
|
|
|
- this.isVoiceActive = !this.isVoiceActive;
|
|
|
+ canGoBack(): boolean {
|
|
|
+ return this.showFullReportModal ||
|
|
|
+ this.showHistoryReportModal ||
|
|
|
+ this.showAddCustomerModal;
|
|
|
}
|
|
|
-
|
|
|
- selectDifficulty(difficulty: string) {
|
|
|
- this.selectedDifficulty = difficulty;
|
|
|
+
|
|
|
+ /* 数据加载方法 */
|
|
|
+ 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);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- selectCustomerType(index: number) {
|
|
|
- if (!this.isEditingTypes) {
|
|
|
- this.customerTypes.forEach(type => type.active = false);
|
|
|
- this.customerTypes[index].active = true;
|
|
|
+
|
|
|
+ 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);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- deleteCustomerType(index: number) {
|
|
|
- if (this.customerTypes.length > 1) {
|
|
|
- this.customerTypes.splice(index, 1);
|
|
|
- } else {
|
|
|
+
|
|
|
+ /* 训练场景相关方法 */
|
|
|
+ async selectTab(tab: DifficultyTab) {
|
|
|
+ this.difficultyTabs.forEach(t => t.active = false);
|
|
|
+ tab.active = true;
|
|
|
+ await this.loadTrainingScenarios();
|
|
|
+ }
|
|
|
+
|
|
|
+ selectCustomerType(type: CustomerType) {
|
|
|
+ if (this.isEditing) return;
|
|
|
+
|
|
|
+ this.customerTypes.forEach(t => t.active = false);
|
|
|
+ type.active = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ toggleEditMode() {
|
|
|
+ this.isEditing = !this.isEditing;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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('删除失败,请稍后重试');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 客户类型管理 */
|
|
|
+ openAddCustomerModal() {
|
|
|
+ this.showAddCustomerModal = true;
|
|
|
}
|
|
|
-
|
|
|
- addCustomerType() {
|
|
|
- if (this.newCustomerType.trim()) {
|
|
|
- this.customerTypes.push({ name: this.newCustomerType.trim(), active: false });
|
|
|
- this.newCustomerType = '';
|
|
|
- this.showAddCustomerModal = false;
|
|
|
+
|
|
|
+ closeAddCustomerModal() {
|
|
|
+ this.showAddCustomerModal = false;
|
|
|
+ this.newCustomerTypeName = '';
|
|
|
+ this.newCustomerDifficulty = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ async saveCustomerType() {
|
|
|
+ if (!this.newCustomerTypeName.trim()) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const newType = await this.trainingService.addCustomerType(
|
|
|
+ this.newCustomerTypeName,
|
|
|
+ this.newCustomerDifficulty,
|
|
|
+ '自定义角色'
|
|
|
+ );
|
|
|
+
|
|
|
+ this.customerTypes.push({
|
|
|
+ id: newType.id|| '',
|
|
|
+ name: this.newCustomerTypeName,
|
|
|
+ active: false,
|
|
|
+ icon: this.getRandomIcon(),
|
|
|
+ difficulty: this.newCustomerDifficulty,
|
|
|
+ customerRole: '自定义角色'
|
|
|
+ });
|
|
|
+
|
|
|
+ this.closeAddCustomerModal();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('添加客户类型失败:', error);
|
|
|
+ alert('添加失败,请稍后重试');
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- sendMessage() {
|
|
|
- if (this.newMessage.trim()) {
|
|
|
- this.messages.push({ text: this.newMessage, isCustomer: false });
|
|
|
- this.newMessage = '';
|
|
|
+
|
|
|
+ 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)];
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 训练相关方法 */
|
|
|
+ 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;
|
|
|
|
|
|
- // 模拟AI回复
|
|
|
- setTimeout(() => {
|
|
|
- this.messages.push({
|
|
|
- text: "听起来不错,我会考虑这个方案。能提供一份详细报价吗?",
|
|
|
- isCustomer: true
|
|
|
- });
|
|
|
- }, 1000);
|
|
|
+ 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);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- getCircleDashOffset() {
|
|
|
- const circumference = 2 * Math.PI * 80;
|
|
|
- const progress = 86; // 86%
|
|
|
- return circumference - (progress / 100) * circumference;
|
|
|
+
|
|
|
+ generateCustomerOpening(type: string): string {
|
|
|
+ const openings: Record<string, string[]> = {
|
|
|
+ '商务客户': [
|
|
|
+ "我想咨询一下贵公司的企业服务方案。",
|
|
|
+ "我们需要预订一批商务客房,能提供优惠吗?",
|
|
|
+ "关于贵公司的VIP服务,我有几个问题想了解。"
|
|
|
+ ],
|
|
|
+ '家庭客户': [
|
|
|
+ "我们全家计划下周出游,能推荐适合的套餐吗?",
|
|
|
+ "带孩子入住有什么需要注意的事项吗?",
|
|
|
+ "请问有家庭优惠活动吗?"
|
|
|
+ ],
|
|
|
+ 'VIP客户': [
|
|
|
+ "作为VIP会员,我应该享受哪些专属权益?",
|
|
|
+ "这次入住希望能升级房型,可以安排吗?",
|
|
|
+ "我的积分可以兑换什么服务?"
|
|
|
+ ]
|
|
|
+ };
|
|
|
+
|
|
|
+ const defaultOpenings = [
|
|
|
+ "我想咨询一些服务细节。",
|
|
|
+ "能介绍一下你们的主要产品吗?",
|
|
|
+ "我对你们的服务很感兴趣。"
|
|
|
+ ];
|
|
|
+
|
|
|
+ return openings[type]
|
|
|
+ ? openings[type][Math.floor(Math.random() * openings[type].length)]
|
|
|
+ : defaultOpenings[Math.floor(Math.random() * defaultOpenings.length)];
|
|
|
}
|
|
|
+
|
|
|
+ /* 对话功能 */
|
|
|
+ toggleRecording() {
|
|
|
+ this.isRecording = !this.isRecording;
|
|
|
+ if (this.isRecording) {
|
|
|
+ this.startVoiceRecording();
|
|
|
+ } else {
|
|
|
+ this.stopVoiceRecording();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ startVoiceRecording() {
|
|
|
+ console.log('开始录音...');
|
|
|
+ }
|
|
|
+
|
|
|
+ stopVoiceRecording() {
|
|
|
+ console.log('停止录音...');
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ generateAIResponse(): string {
|
|
|
+ const responses = [
|
|
|
+ "听起来不错,我会考虑这个方案。能提供一份详细报价吗?",
|
|
|
+ "这个方案我需要再考虑一下,能否提供其他选择?",
|
|
|
+ "感谢您的建议,我会和团队讨论后给您回复。",
|
|
|
+ "这个价格可以接受,请问包含哪些服务?",
|
|
|
+ "作为VIP会员,我希望能有更多优惠,可以吗?"
|
|
|
+ ];
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 : ['各方面表现良好,继续保持!'];
|
|
|
+ }
|
|
|
+
|
|
|
+ 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 '表现有待提高。建议从基础场景开始,逐步提升各项能力。';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ openFullReportModal() {
|
|
|
+ this.showFullReportModal = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ closeFullReportModal() {
|
|
|
+ this.showFullReportModal = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ openHistoryReportModal() {
|
|
|
+ this.showHistoryReportModal = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ closeHistoryReportModal() {
|
|
|
+ this.showHistoryReportModal = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ async viewHistoryReport(report: Report) {
|
|
|
+ this.currentReport = report;
|
|
|
+ this.currentScore = report.score;
|
|
|
+ this.closeHistoryReportModal();
|
|
|
+ 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';
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 类型定义 */
|
|
|
+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;
|
|
|
}
|