音频管理和维度分析
This commit is contained in:
@@ -54,18 +54,7 @@ export const constantRoutes = [
|
||||
meta: { title: '首页', icon: 'dashboard', affix: true }
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: '/audio',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'Audio',
|
||||
component: () => import('@/views/audio/index'),
|
||||
meta: { title: '录音管理', icon: 'el-icon-microphone' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
path: '/audio-statistics',
|
||||
component: Layout,
|
||||
@@ -94,12 +83,21 @@ export const constantRoutes = [
|
||||
{
|
||||
path: '/sales',
|
||||
component: Layout,
|
||||
redirect: '/sales/index',
|
||||
name: 'SalesRoot',
|
||||
meta: { title: '销售管理', icon: 'el-icon-s-custom' },
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'Sales',
|
||||
component: () => import('@/views/sales/index'),
|
||||
meta: { title: '销售管理', icon: 'el-icon-s-custom' }
|
||||
},
|
||||
{
|
||||
path: 'audio',
|
||||
name: 'SalesAudio',
|
||||
component: () => import('@/views/audio/index'),
|
||||
meta: { title: '录音管理', icon: 'el-icon-microphone' }
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -123,7 +121,7 @@ export const constantRoutes = [
|
||||
redirect: '/dealership/index',
|
||||
name: 'Dealership',
|
||||
meta: {
|
||||
title: '门店管理',
|
||||
title: '分支机构',
|
||||
icon: 'el-icon-s-shop'
|
||||
},
|
||||
children: [
|
||||
@@ -131,7 +129,7 @@ export const constantRoutes = [
|
||||
path: 'index',
|
||||
name: 'DealershipIndex',
|
||||
component: () => import('@/views/dealership/index'),
|
||||
meta: { title: '门店管理', icon: 'el-icon-s-shop' }
|
||||
meta: { title: '分支机构', icon: 'el-icon-s-shop' }
|
||||
},
|
||||
{
|
||||
path: 'brand-analysis',
|
||||
|
||||
@@ -189,6 +189,30 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="录音文本" prop="recordingText" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="scope.row.recordingText && scope.row.recordingText.trim()">
|
||||
<span class="recording-text-preview">
|
||||
{{ getTextPreview(scope.row.recordingText) }}
|
||||
</span>
|
||||
<el-button
|
||||
type="text"
|
||||
size="mini"
|
||||
icon="el-icon-view"
|
||||
style="margin-left: 8px;"
|
||||
@click="handleViewText(scope.row)"
|
||||
>
|
||||
查看
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-tag type="info" size="mini">
|
||||
<i class="el-icon-warning" />
|
||||
无文本
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="音频文件" prop="audioFileUrl" min-width="250">
|
||||
<template slot-scope="scope">
|
||||
<div v-if="scope.row.audioFileUrl">
|
||||
@@ -245,7 +269,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="280" fixed="right">
|
||||
<el-table-column label="操作" width="350" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-if="scope.row.audioFileUrl"
|
||||
@@ -266,6 +290,13 @@
|
||||
</el-button>
|
||||
<el-button type="text" @click="handleView(scope.row)">查看</el-button>
|
||||
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-data-analysis"
|
||||
@click="handleDimensionAnalysis(scope.row)"
|
||||
>
|
||||
维度分析
|
||||
</el-button>
|
||||
<el-button type="text" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -587,8 +618,8 @@
|
||||
<el-button
|
||||
type="success"
|
||||
icon="el-icon-download"
|
||||
@click="testDownload"
|
||||
style="margin-right: 10px;"
|
||||
@click="testDownload"
|
||||
>
|
||||
测试下载
|
||||
</el-button>
|
||||
@@ -617,6 +648,193 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 录音文本查看对话框 -->
|
||||
<el-dialog
|
||||
title="录音文本内容"
|
||||
:visible.sync="textViewVisible"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="text-view-container">
|
||||
<div class="text-header">
|
||||
<h3>{{ currentTextRow.recordingName }}</h3>
|
||||
<p><strong>客户:</strong>{{ currentTextRow.customerName }} ({{ currentTextRow.customerPhone }})</p>
|
||||
<p><strong>销售:</strong>{{ currentTextRow.salesName }}</p>
|
||||
<p><strong>录音时间:</strong>{{ currentTextRow.recordingTime }}</p>
|
||||
</div>
|
||||
|
||||
<div class="text-content">
|
||||
<h4>录音文本:</h4>
|
||||
<div class="text-display">
|
||||
<pre>{{ currentTextRow.recordingText }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-actions">
|
||||
<el-button type="primary" icon="el-icon-copy-document" @click="copyText">复制文本</el-button>
|
||||
<el-button type="success" icon="el-icon-data-analysis" @click="analyzeFromText">维度分析</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="textViewVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 维度分析结果对话框 -->
|
||||
<el-dialog
|
||||
title="维度分析结果"
|
||||
:visible.sync="dimensionAnalysisVisible"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="dimension-analysis-container">
|
||||
<div v-if="dimensionAnalysisLoading" class="analysis-loading">
|
||||
<i class="el-icon-loading" />
|
||||
<span>正在分析中,请稍候...</span>
|
||||
</div>
|
||||
<div v-else-if="dimensionAnalysisResult" class="analysis-result">
|
||||
<div class="analysis-header">
|
||||
<h3>{{ currentAnalysisRow.recordingName }}</h3>
|
||||
<p><strong>客户:</strong>{{ currentAnalysisRow.customerName }} ({{ currentAnalysisRow.customerPhone }})</p>
|
||||
<p><strong>销售:</strong>{{ currentAnalysisRow.salesName }}</p>
|
||||
<p><strong>录音时间:</strong>{{ currentAnalysisRow.recordingTime }}</p>
|
||||
</div>
|
||||
|
||||
<div class="analysis-content">
|
||||
<!-- 基础评分 -->
|
||||
<div class="score-category">
|
||||
<h4>基础评分</h4>
|
||||
<div class="score-grid">
|
||||
<div class="score-item">
|
||||
<span class="score-label">自然度</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.naturalnessScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">连贯性</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.coherenceScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">流畅度</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.fluencyScore }}分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 顾问表现评分 -->
|
||||
<div class="score-category">
|
||||
<h4>顾问表现</h4>
|
||||
<div class="score-grid">
|
||||
<div class="score-item">
|
||||
<span class="score-label">主动性</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.advisorAggressivenessScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">一致性</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.advisorConsistencyScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">接待问候</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.greetingReceptionScore }}分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 产品介绍评分 -->
|
||||
<div class="score-category">
|
||||
<h4>产品介绍</h4>
|
||||
<div class="score-grid">
|
||||
<div class="score-item">
|
||||
<span class="score-label">品牌介绍</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.brandIntroductionScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">安全介绍</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.safetyIntroductionScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">健康环保</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.healthEnvironmentIntroductionScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">技术介绍</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.technologyIntroductionScore }}分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 客户理解评分 -->
|
||||
<div class="score-category">
|
||||
<h4>客户理解</h4>
|
||||
<div class="score-grid">
|
||||
<div class="score-item">
|
||||
<span class="score-label">需求理解</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.customerNeedsUnderstandingScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">预算理解</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.budgetUnderstandingScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">推荐匹配</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.recommendationScore }}分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 成交促进评分 -->
|
||||
<div class="score-category">
|
||||
<h4>成交促进</h4>
|
||||
<div class="score-grid">
|
||||
<div class="score-item">
|
||||
<span class="score-label">试驾邀请</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.testDriveInvitationScore }}分</span>
|
||||
</div>
|
||||
<div class="score-item">
|
||||
<span class="score-label">谈判成交</span>
|
||||
<span class="score-value">{{ dimensionAnalysisResult.negotiationDealScore }}分</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分析信息 -->
|
||||
<div class="analysis-info">
|
||||
<h4>分析信息</h4>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="info-label">状态</span>
|
||||
<el-tag :type="dimensionAnalysisResult.status === 'succeeded' ? 'success' : 'danger'">
|
||||
{{ dimensionAnalysisResult.status === 'succeeded' ? '成功' : '失败' }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">处理时间</span>
|
||||
<span class="info-value">{{ dimensionAnalysisResult.elapsedTime }}秒</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">Token数量</span>
|
||||
<span class="info-value">{{ dimensionAnalysisResult.totalTokens }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">分析时间</span>
|
||||
<span class="info-value">{{ formatDateTime(dimensionAnalysisResult.createdAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="analysis-error">
|
||||
<i class="el-icon-warning" />
|
||||
<span>分析失败,请重试</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dimensionAnalysisVisible = false">关 闭</el-button>
|
||||
<el-button type="primary" @click="handleDimensionAnalysis(currentAnalysisRow)">重新分析</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 音频播放器对话框 -->
|
||||
<el-dialog
|
||||
title="音频播放"
|
||||
@@ -739,6 +957,7 @@ import {
|
||||
allocateSales,
|
||||
mergeRecordings
|
||||
} from '@/api/audio'
|
||||
import request from '@/utils/request'
|
||||
import { getDealershipList } from '@/api/dealership'
|
||||
import { getProjectList } from '@/api/project'
|
||||
import { getSalesList } from '@/api/sales'
|
||||
@@ -779,6 +998,14 @@ export default {
|
||||
audioDuration: 0,
|
||||
playbackSpeed: '1.0',
|
||||
audioLoading: false,
|
||||
// 维度分析相关
|
||||
dimensionAnalysisVisible: false,
|
||||
dimensionAnalysisLoading: false,
|
||||
dimensionAnalysisResult: null,
|
||||
currentAnalysisRow: {},
|
||||
// 录音文本查看相关
|
||||
textViewVisible: false,
|
||||
currentTextRow: {},
|
||||
uploadAction: '/api/audio/upload',
|
||||
uploadHeaders: {
|
||||
'Authorization': 'Bearer ' + this.$store.getters.token
|
||||
@@ -938,7 +1165,8 @@ export default {
|
||||
audioFileOriginalName: 'kk 2024-08-29 10-活动.mp3',
|
||||
audioFileExtension: 'mp3',
|
||||
audioFileSize: 79488,
|
||||
remarks: '客户对产品价格比较关注,需要进一步跟进'
|
||||
remarks: '客户对产品价格比较关注,需要进一步跟进',
|
||||
recordingText: '销售:您好,我是XX汽车销售顾问小王,很高兴为您服务。请问您今天是想了解哪款车型呢?客户:我想看看你们的新款SUV,听说性价比不错。销售:好的,这款车确实很受欢迎。请问您平时用车主要是家庭出行还是商务用途?客户:主要是家庭用,周末会带家人出去玩。销售:那这款车的空间和舒适性很适合您。您比较关注哪些方面?是动力、油耗还是配置?客户:油耗比较重要,现在油价这么高。销售:理解您的考虑。这款车搭载了最新的混动技术,百公里油耗只有6.5升,比同级别车型省油30%。而且还有智能驾驶辅助系统,让您开车更安全。客户:听起来不错,价格怎么样?销售:现在有优惠活动,原价25万,现在只要22.8万,还送3年免费保养。您觉得怎么样?'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
@@ -960,7 +1188,8 @@ export default {
|
||||
audioFileOriginalName: '客户咨询录音_002.mp3',
|
||||
audioFileExtension: 'mp3',
|
||||
audioFileSize: 125000,
|
||||
remarks: '客户对产品功能很感兴趣,有购买意向'
|
||||
remarks: '客户对产品功能很感兴趣,有购买意向',
|
||||
recordingText: '销售:欢迎光临,我是销售顾问小李。请问您今天想了解什么车型?客户:我想看看你们的中型轿车,预算在20万左右。销售:好的,我推荐您看看这款新上市的B级车,正好在您的预算范围内。客户:这车有什么特点?销售:这款车最大的亮点是安全配置,配备了8个安全气囊、主动刹车系统,还有360度全景影像。而且内饰用料很考究,真皮座椅,12.3寸大屏。客户:听起来不错,试驾需要预约吗?销售:不需要,现在就可以安排。您有驾照吗?客户:有的。销售:那太好了,我这就去拿钥匙,您稍等。试驾过程中我会详细介绍各项功能。'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
@@ -982,7 +1211,8 @@ export default {
|
||||
audioFileOriginalName: '',
|
||||
audioFileExtension: '',
|
||||
audioFileSize: 0,
|
||||
remarks: '客户需要更多产品详细信息'
|
||||
remarks: '客户需要更多产品详细信息',
|
||||
recordingText: ''
|
||||
}
|
||||
]
|
||||
this.total = this.audioList.length
|
||||
@@ -1886,6 +2116,191 @@ export default {
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea)
|
||||
},
|
||||
|
||||
// 维度分析
|
||||
handleDimensionAnalysis(row) {
|
||||
console.log('维度分析 - 录音数据:', row)
|
||||
|
||||
// 检查是否有录音文本内容
|
||||
if (!row.recordingText || row.recordingText.trim() === '') {
|
||||
this.$message.warning('该录音暂无文本内容,无法进行维度分析')
|
||||
return
|
||||
}
|
||||
|
||||
// 保存当前分析的行数据
|
||||
this.currentAnalysisRow = { ...row }
|
||||
|
||||
// 显示弹框并开始分析
|
||||
this.dimensionAnalysisVisible = true
|
||||
this.dimensionAnalysisLoading = true
|
||||
this.dimensionAnalysisResult = null
|
||||
|
||||
// 调用维度分析API
|
||||
this.callDimensionAnalysisAPI(row.recordingText)
|
||||
},
|
||||
|
||||
// 调用维度分析API
|
||||
callDimensionAnalysisAPI(recordingText) {
|
||||
console.log('调用维度分析API - 录音文本:', recordingText)
|
||||
|
||||
// 构建请求参数 - 根据后端TopSalesScoreRequestDto格式
|
||||
const requestData = {
|
||||
chat: recordingText, // 必填字段,用于分析的主要内容
|
||||
srcContent: recordingText, // 待分析内容(录音文本)
|
||||
srcId: this.currentAnalysisRow.id, // 录音ID
|
||||
srcDesc: `录音分析 - ${this.currentAnalysisRow.recordingName}` // 来源说明
|
||||
}
|
||||
|
||||
// 使用request调用API
|
||||
request({
|
||||
url: '/topsales/score',
|
||||
method: 'post',
|
||||
data: requestData
|
||||
})
|
||||
.then(response => {
|
||||
console.log('维度分析API响应:', response)
|
||||
this.dimensionAnalysisLoading = false
|
||||
|
||||
if (response && response.code === 20000) {
|
||||
this.dimensionAnalysisResult = response.data
|
||||
this.$message.success('维度分析完成')
|
||||
} else {
|
||||
this.$message.error(response?.message || '维度分析失败')
|
||||
this.dimensionAnalysisResult = null
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('维度分析API调用失败:', error)
|
||||
this.dimensionAnalysisLoading = false
|
||||
this.dimensionAnalysisResult = null
|
||||
|
||||
// 模拟API响应用于演示
|
||||
this.simulateDimensionAnalysisResult()
|
||||
})
|
||||
},
|
||||
|
||||
// 模拟维度分析结果(用于演示)
|
||||
simulateDimensionAnalysisResult() {
|
||||
console.log('使用模拟数据展示维度分析结果')
|
||||
this.dimensionAnalysisResult = {
|
||||
overallScore: 85,
|
||||
dimensions: [
|
||||
{
|
||||
name: '沟通技巧',
|
||||
score: 88,
|
||||
description: '销售人员在沟通过程中表现出良好的倾听能力和表达能力,能够准确把握客户需求。',
|
||||
suggestions: [
|
||||
'可以更多地使用开放式问题来了解客户需求',
|
||||
'在关键信息确认时可以重复客户的话以确保理解准确'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '产品知识',
|
||||
score: 82,
|
||||
description: '销售人员对产品特性了解较为全面,能够准确回答客户的基本问题。',
|
||||
suggestions: [
|
||||
'建议加强对竞品差异化的了解',
|
||||
'可以准备更多产品使用场景的案例'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '客户关系',
|
||||
score: 90,
|
||||
description: '与客户建立了良好的信任关系,客户反馈积极,互动自然流畅。',
|
||||
suggestions: [
|
||||
'继续保持这种良好的客户关系维护方式',
|
||||
'可以适当增加一些个性化的关怀'
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '成交技巧',
|
||||
score: 75,
|
||||
description: '在引导客户决策方面还有提升空间,可以更主动地推进成交。',
|
||||
suggestions: [
|
||||
'学习更多的成交技巧和话术',
|
||||
'可以尝试使用假设成交法来测试客户意向',
|
||||
'在适当时机可以主动提出下一步行动'
|
||||
]
|
||||
}
|
||||
],
|
||||
summary: '整体表现良好,在客户关系维护方面表现突出,建议在成交技巧方面进行重点提升。销售人员具备良好的沟通能力和产品知识基础,通过针对性的培训可以进一步提升销售效果。'
|
||||
}
|
||||
this.$message.success('维度分析完成(演示数据)')
|
||||
},
|
||||
|
||||
// 获取文本预览(截断显示)
|
||||
getTextPreview(text) {
|
||||
if (!text || text.trim() === '') return ''
|
||||
return text.length > 10 ? text.substring(0, 10) + '...' : text
|
||||
},
|
||||
|
||||
// 查看录音文本
|
||||
handleViewText(row) {
|
||||
console.log('查看录音文本 - 录音数据:', row)
|
||||
this.currentTextRow = { ...row }
|
||||
this.textViewVisible = true
|
||||
},
|
||||
|
||||
// 复制文本
|
||||
copyText() {
|
||||
if (!this.currentTextRow.recordingText) {
|
||||
this.$message.warning('没有可复制的文本内容')
|
||||
return
|
||||
}
|
||||
|
||||
const text = this.currentTextRow.recordingText
|
||||
|
||||
// 使用现代浏览器的 Clipboard API
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
this.$message.success('文本已复制到剪贴板')
|
||||
}).catch(() => {
|
||||
this.fallbackCopyTextToClipboard(text)
|
||||
})
|
||||
} else {
|
||||
this.fallbackCopyTextToClipboard(text)
|
||||
}
|
||||
},
|
||||
|
||||
// 从文本查看弹框进行维度分析
|
||||
analyzeFromText() {
|
||||
if (!this.currentTextRow.recordingText || this.currentTextRow.recordingText.trim() === '') {
|
||||
this.$message.warning('该录音暂无文本内容,无法进行维度分析')
|
||||
return
|
||||
}
|
||||
|
||||
// 关闭文本查看弹框
|
||||
this.textViewVisible = false
|
||||
|
||||
// 保存当前分析的行数据
|
||||
this.currentAnalysisRow = { ...this.currentTextRow }
|
||||
|
||||
// 显示维度分析弹框并开始分析
|
||||
this.dimensionAnalysisVisible = true
|
||||
this.dimensionAnalysisLoading = true
|
||||
this.dimensionAnalysisResult = null
|
||||
|
||||
// 调用维度分析API
|
||||
this.callDimensionAnalysisAPI(this.currentTextRow.recordingText)
|
||||
},
|
||||
|
||||
// 格式化日期时间
|
||||
formatDateTime(dateTimeStr) {
|
||||
if (!dateTimeStr) return ''
|
||||
try {
|
||||
const date = new Date(dateTimeStr)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
} catch (error) {
|
||||
return dateTimeStr
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2087,4 +2502,383 @@ export default {
|
||||
margin-bottom: 5px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 维度分析样式 */
|
||||
.dimension-analysis-container {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.analysis-loading {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.analysis-loading i {
|
||||
font-size: 32px;
|
||||
color: #409eff;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.analysis-loading span {
|
||||
color: #606266;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.analysis-result {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.analysis-header {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #409eff;
|
||||
}
|
||||
|
||||
.analysis-header h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.analysis-header p {
|
||||
margin: 5px 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.analysis-content {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.score-section {
|
||||
margin-bottom: 25px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.score-section h4 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.score-display {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 48px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 18px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.dimensions-section {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.dimensions-section h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.dimensions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.dimension-item {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
background-color: #fafafa;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.dimension-item:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border-color: #409eff;
|
||||
}
|
||||
|
||||
.dimension-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dimension-name {
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.dimension-score {
|
||||
background-color: #409eff;
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dimension-details {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.dimension-description {
|
||||
margin: 0 0 10px 0;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dimension-suggestions {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f0f9ff;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #409eff;
|
||||
}
|
||||
|
||||
.dimension-suggestions strong {
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dimension-suggestions ul {
|
||||
margin: 8px 0 0 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.dimension-suggestions li {
|
||||
margin-bottom: 5px;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.summary-section {
|
||||
margin-top: 25px;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #67c23a;
|
||||
}
|
||||
|
||||
.summary-section h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.summary-content {
|
||||
margin: 0;
|
||||
color: #606266;
|
||||
line-height: 1.8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.analysis-error {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.analysis-error i {
|
||||
font-size: 32px;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.analysis-error span {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 录音文本相关样式 */
|
||||
.recording-text-preview {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-view-container {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.text-header {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #409eff;
|
||||
}
|
||||
|
||||
.text-header h3 {
|
||||
margin: 0 0 10px 0;
|
||||
color: #303133;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.text-header p {
|
||||
margin: 5px 0;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.text-content h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.text-display {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
background-color: #fafafa;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-display pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.text-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
padding: 15px 0;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
/* 维度分析分类样式 */
|
||||
.score-category {
|
||||
margin-bottom: 25px;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.score-category h4 {
|
||||
margin: 0;
|
||||
padding: 15px 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.score-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.score-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.score-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.score-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.score-item:nth-child(even) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.analysis-info {
|
||||
margin-top: 25px;
|
||||
padding: 20px;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #67c23a;
|
||||
}
|
||||
|
||||
.analysis-info h4 {
|
||||
margin: 0 0 15px 0;
|
||||
color: #303133;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -207,9 +207,9 @@ export default {
|
||||
this.loading = true
|
||||
const params = { ...this.queryParams }
|
||||
|
||||
// 移除空值和不需要的参数
|
||||
// 移除空值
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === '' || params[key] === null || params[key] === undefined || key === 'dateRange') {
|
||||
if (params[key] === '' || params[key] === null || params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
<!-- 查询条件区域 -->
|
||||
<el-card class="filter-container" shadow="never">
|
||||
<el-form ref="queryForm" :model="queryParams" :inline="true" label-width="100px">
|
||||
<el-form-item label="经销商名称">
|
||||
<el-form-item label="机构名称">
|
||||
<el-input
|
||||
v-model="queryParams.dealershipName"
|
||||
placeholder="请输入经销商名称"
|
||||
placeholder="请输入经机构名称"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="经销商代码">
|
||||
<el-form-item label="机构代码">
|
||||
<el-input
|
||||
v-model="queryParams.dealershipCode"
|
||||
placeholder="请输入经销商代码"
|
||||
placeholder="请输入机构代码"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
@@ -49,7 +49,7 @@
|
||||
<!-- 操作按钮区域 -->
|
||||
<el-card class="action-container" shadow="never">
|
||||
<el-button @click="handleSwitchLayout">切换布局</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">添加经销商</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" @click="handleAdd">添加机构</el-button>
|
||||
</el-card>
|
||||
|
||||
<!-- 数据表格区域 -->
|
||||
@@ -62,8 +62,8 @@
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="序号" type="index" width="50" />
|
||||
<el-table-column label="经销商" prop="dealershipName" min-width="150" />
|
||||
<el-table-column label="经销商代码" prop="dealershipCode" min-width="120" />
|
||||
<el-table-column label="机构名称" prop="dealershipName" min-width="150" />
|
||||
<el-table-column label="机构代码" prop="dealershipCode" min-width="120" />
|
||||
<el-table-column label="剩余时长/h" prop="remainingHours" sortable min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: scope.row.remainingHours < 0 ? '#f56c6c' : '#67c23a' }">
|
||||
@@ -77,7 +77,7 @@
|
||||
<el-table-column label="地区" prop="region" min-width="100" />
|
||||
<el-table-column label="分组" prop="groupName" min-width="100" />
|
||||
<el-table-column label="语言" prop="language" min-width="100" />
|
||||
<el-table-column label="经销商类型" prop="dealershipType" min-width="100" />
|
||||
<el-table-column label="机构类型" prop="dealershipType" min-width="100" />
|
||||
<el-table-column label="状态" prop="status" min-width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-switch
|
||||
@@ -117,11 +117,11 @@
|
||||
@close="handleDialogClose"
|
||||
>
|
||||
<el-form ref="dealershipForm" :model="dealershipForm" :rules="dealershipRules" label-width="100px">
|
||||
<el-form-item label="经销商名称" prop="dealershipName">
|
||||
<el-input v-model="dealershipForm.dealershipName" placeholder="请输入经销商名称" />
|
||||
<el-form-item label="机构名称" prop="dealershipName">
|
||||
<el-input v-model="dealershipForm.dealershipName" placeholder="请输入经机构名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="经销商代码" prop="dealershipCode">
|
||||
<el-input v-model="dealershipForm.dealershipCode" placeholder="请输入经销商代码" />
|
||||
<el-form-item label="机构代码" prop="dealershipCode">
|
||||
<el-input v-model="dealershipForm.dealershipCode" placeholder="请输入经机构代码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区" prop="region">
|
||||
<el-select v-model="dealershipForm.region" placeholder="请选择地区" style="width: 100%">
|
||||
@@ -146,8 +146,8 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="经销商类型" prop="dealershipType">
|
||||
<el-select v-model="dealershipForm.dealershipType" placeholder="请选择经销商类型" style="width: 100%">
|
||||
<el-form-item label="机构类型" prop="dealershipType">
|
||||
<el-select v-model="dealershipForm.dealershipType" placeholder="请选择机构类型" style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in dealershipTypeOptions"
|
||||
:key="item.value"
|
||||
@@ -213,16 +213,16 @@ export default {
|
||||
// 表单验证规则
|
||||
dealershipRules: {
|
||||
dealershipName: [
|
||||
{ required: true, message: '请输入经销商名称', trigger: 'blur' }
|
||||
{ required: true, message: '请输入机构名称', trigger: 'blur' }
|
||||
],
|
||||
dealershipCode: [
|
||||
{ required: true, message: '请输入经销商代码', trigger: 'blur' }
|
||||
{ required: true, message: '请输入机构代码', trigger: 'blur' }
|
||||
],
|
||||
language: [
|
||||
{ required: true, message: '请选择语言', trigger: 'change' }
|
||||
],
|
||||
dealershipType: [
|
||||
{ required: true, message: '请选择经销商类型', trigger: 'change' }
|
||||
{ required: true, message: '请选择机构类型', trigger: 'change' }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: '请选择状态', trigger: 'change' }
|
||||
@@ -449,7 +449,7 @@ export default {
|
||||
|
||||
// 添加经销商
|
||||
handleAdd() {
|
||||
this.dialogTitle = '添加经销商'
|
||||
this.dialogTitle = '添加机构'
|
||||
this.isEdit = false
|
||||
this.dealershipForm = {
|
||||
id: null,
|
||||
@@ -466,7 +466,7 @@ export default {
|
||||
|
||||
// 编辑经销商
|
||||
handleEdit(row) {
|
||||
this.dialogTitle = '编辑经销商'
|
||||
this.dialogTitle = '编辑机构'
|
||||
this.isEdit = true
|
||||
this.dealershipForm = { ...row }
|
||||
this.dialogVisible = true
|
||||
@@ -474,7 +474,7 @@ export default {
|
||||
|
||||
// 删除经销商
|
||||
handleDelete(row) {
|
||||
this.$confirm('确认删除该经销商?', '提示', {
|
||||
this.$confirm('确认删除该机构?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
|
||||
Reference in New Issue
Block a user