diff --git a/src/api/audio-statistics.js b/src/api/audio-statistics.js index 4d63ed5..82d06c7 100644 --- a/src/api/audio-statistics.js +++ b/src/api/audio-statistics.js @@ -55,8 +55,11 @@ export function getStatisticsOverview() { }) } - - - - - +// TTS请求日志相关接口 +export function getTtsRequestLogs(params) { + return request({ + url: '/tts/log/list', + method: 'get', + params + }) +} diff --git a/src/api/sales.js b/src/api/sales.js index 16f21e2..db7c184 100644 --- a/src/api/sales.js +++ b/src/api/sales.js @@ -93,3 +93,41 @@ export function getTopSalesPage(params) { } }) } + +// 获取咨询场景列表(智能捡漏) +export function getConsultingScenarioList(params) { + return request({ + url: '/consultingScenario/list', + method: 'get', + params: { + current: params.current || 1, + size: params.size || 10, + mainCategory: params.mainCategory, + subCategory: params.subCategory, + confidence: params.confidence, + priority: params.priority + } + }) +} + +// AI分析咨询场景 +export function processConsultingScenario(data) { + return request({ + url: '/consultingScenario/process', + method: 'post', + timeout: 80000, // 设置超时时间为60秒(1分钟) + data: { + chat: data.chat, + businessType: data.businessType || 'Channel_4in1', + sourceCorpusId: data.sourceCorpusId || '2180459', + sourceCorpusTime: data.sourceCorpusTime || new Date().toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + }).replace(/\//g, '-') + } + }) +} diff --git a/src/router/index.js b/src/router/index.js index 7bd782c..35b4bc8 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -55,31 +55,6 @@ export const constantRoutes = [ }] }, - { - path: '/audio-statistics', - component: Layout, - children: [ - { - path: 'index', - name: 'AudioStatistics', - component: () => import('@/views/audio-statistics/index'), - meta: { title: '录音统计', icon: 'el-icon-data-analysis' } - } - ] - }, - { - path: '/video', - component: Layout, - children: [ - { - path: 'index', - name: 'Video', - component: () => import('@/views/video/index'), - meta: { title: '视频管理', icon: 'el-icon-video-camera' } - } - ] - }, - { path: '/sales', component: Layout, @@ -104,6 +79,12 @@ export const constantRoutes = [ name: 'DimensionAnalysis', component: () => import('@/views/sales/dimension-analysis'), meta: { title: '维度分析', icon: 'el-icon-data-analysis' } + }, + { + path: 'intelligent-pickup', + name: 'IntelligentPickup', + component: () => import('@/views/sales/intelligent-pickup'), + meta: { title: '智能捡漏', icon: 'el-icon-magic-stick' } } ] }, @@ -215,71 +196,36 @@ export const constantRoutes = [ }, { - path: '/nested', + path: '/audio-statistics', component: Layout, - redirect: '/nested/menu1', - name: 'Nested', - meta: { - title: 'Nested', - icon: 'nested' - }, + redirect: '/audio-statistics/index', + name: 'AudioStatisticsRoot', + meta: { title: '音频管理', icon: 'el-icon-data-analysis' }, children: [ { - path: 'menu1', - component: () => import('@/views/nested/menu1/index'), // Parent router-view - name: 'Menu1', - meta: { title: 'Menu1' }, - children: [ - { - path: 'menu1-1', - component: () => import('@/views/nested/menu1/menu1-1'), - name: 'Menu1-1', - meta: { title: 'Menu1-1' } - }, - { - path: 'menu1-2', - component: () => import('@/views/nested/menu1/menu1-2'), - name: 'Menu1-2', - meta: { title: 'Menu1-2' }, - children: [ - { - path: 'menu1-2-1', - component: () => import('@/views/nested/menu1/menu1-2/menu1-2-1'), - name: 'Menu1-2-1', - meta: { title: 'Menu1-2-1' } - }, - { - path: 'menu1-2-2', - component: () => import('@/views/nested/menu1/menu1-2/menu1-2-2'), - name: 'Menu1-2-2', - meta: { title: 'Menu1-2-2' } - } - ] - }, - { - path: 'menu1-3', - component: () => import('@/views/nested/menu1/menu1-3'), - name: 'Menu1-3', - meta: { title: 'Menu1-3' } - } - ] + path: 'index', + name: 'AudioStatistics', + component: () => import('@/views/audio-statistics/index'), + meta: { title: '音频统计', icon: 'el-icon-data-analysis' } }, { - path: 'menu2', - component: () => import('@/views/nested/menu2/index'), - name: 'Menu2', - meta: { title: 'menu2' } + path: 'audio-generation', + name: 'AudioGeneration', + component: () => import('@/views/audio-statistics/audio-generation'), + meta: { title: '音频生成', icon: 'el-icon-microphone' } } ] }, { - path: 'external-link', + path: '/video', component: Layout, children: [ { - path: 'https://panjiachen.github.io/vue-element-admin-site/#/', - meta: { title: 'External Link', icon: 'link' } + path: 'index', + name: 'Video', + component: () => import('@/views/video/index'), + meta: { title: '视频管理', icon: 'el-icon-video-camera' } } ] }, diff --git a/src/utils/request.js b/src/utils/request.js index 5a4b4c7..248043b 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -39,11 +39,15 @@ service.interceptors.response.use( // 前端期望格式: { code: 20000, data: {...}, message: "..." } if (res.success === true) { - // 后端成功响应,转换为前端期望格式 + // 后端成功响应,转换为前端期望格式,保留分页信息 return { code: 20000, data: res.data, - message: res.message || 'Success' + message: res.message || 'Success', + total: res.total, + current: res.current, + pages: res.pages, + size: res.size } } else if (res.success === false) { // 后端失败响应 diff --git a/src/views/audio-statistics/audio-generation.vue b/src/views/audio-statistics/audio-generation.vue new file mode 100644 index 0000000..8b9bbdf --- /dev/null +++ b/src/views/audio-statistics/audio-generation.vue @@ -0,0 +1,381 @@ + + + + + diff --git a/src/views/sales/dimension-analysis.vue b/src/views/sales/dimension-analysis.vue index 8a65142..e6956c9 100644 --- a/src/views/sales/dimension-analysis.vue +++ b/src/views/sales/dimension-analysis.vue @@ -228,116 +228,52 @@ title="TopSales评分详情" :visible.sync="detailDialogVisible" width="800px" + :close-on-click-modal="false" @close="handleDetailDialogClose" > -
- - - {{ currentRecord.status }} - - {{ currentRecord.salespersonName || currentRecord.salesName || '-' }} - {{ currentRecord.salespersonPhone || currentRecord.salesPhone || '-' }} - {{ currentRecord.dealerName || currentRecord.dealershipName || '-' }} - {{ currentRecord.dealerPhone || currentRecord.dealershipPhone || '-' }} - - - - {{ currentRecord.naturalnessScore || 0 }}分 - - - - - {{ currentRecord.coherenceScore || 0 }}分 - - - - - {{ currentRecord.fluencyScore || 0 }}分 - - +
+
+

{{ currentRecord.salespersonName || currentRecord.salesName || '未知销售' }}

+

状态:{{ currentRecord.status }}

+

销售电话:{{ currentRecord.salespersonPhone || currentRecord.salesPhone || '-' }}

+

经销商:{{ currentRecord.dealerName || currentRecord.dealershipName || '-' }}

+

创建时间:{{ currentRecord.createdAt }}

+
- - - - {{ currentRecord.advisorAggressivenessScore || 0 }}分 - - - - - {{ currentRecord.advisorConsistencyScore || 0 }}分 - - - - - {{ currentRecord.greetingReceptionScore || 0 }}分 - - +
+

基本信息

+
+
+ 总用时: + {{ currentRecord.elapsedTime }}秒 +
+
+ 综合评分: + + {{ getOverallScore(currentRecord) }} + +
+
+
- - - - {{ currentRecord.brandIntroductionScore || 0 }}分 - - - - - {{ currentRecord.safetyIntroductionScore || 0 }}分 - - - - - {{ currentRecord.healthEnvironmentIntroductionScore || 0 }}分 - - - - - {{ currentRecord.technologyIntroductionScore || 0 }}分 - - +
+

对话内容

+
+
+
+ {{ getSpeakerLabel(line) }} + {{ getSpeakerContent(line) }} +
+
+
+
- - - - {{ currentRecord.customerNeedsUnderstandingScore || 0 }}分 - - - - - {{ currentRecord.budgetUnderstandingScore || 0 }}分 - - - - - {{ currentRecord.recommendationScore || 0 }}分 - - - - - - - {{ currentRecord.testDriveInvitationScore || 0 }}分 - - - - - {{ currentRecord.negotiationDealScore || 0 }}分 - - - {{ currentRecord.elapsedTime }} - {{ currentRecord.createdAt }} - - - -
-

原始内容

- +
+ 复制内容 + 导出详情
+ @@ -686,6 +622,122 @@ export default { // 详情对话框关闭 handleDetailDialogClose() { this.currentRecord = null + }, + + // 复制内容 + copyContent() { + if (!this.currentRecord || !this.currentRecord.srcContent) { + this.$message.warning('暂无内容可复制') + return + } + + // 创建临时文本区域 + const textArea = document.createElement('textarea') + textArea.value = this.currentRecord.srcContent + document.body.appendChild(textArea) + textArea.select() + + try { + document.execCommand('copy') + this.$message.success('内容已复制到剪贴板') + } catch (err) { + this.$message.error('复制失败,请手动复制') + } finally { + document.body.removeChild(textArea) + } + }, + + // 获取格式化的文本行数组 + getFormattedLines(text) { + if (!text || text.trim() === '') { + return [{ type: 'empty', content: '暂无对话内容' }] + } + + // 先尝试按换行符分割 + let lines = text.split('\n').filter(line => line.trim() !== '') + + // 如果没有换行符,按说话人模式分割 + if (lines.length === 1) { + const textContent = lines[0] + // 尝试识别说话人模式:销售:xxx 客户:xxx + const speakerPattern = /(销售|客户|顾问)[::]/g + const matches = [] + let match + while ((match = speakerPattern.exec(textContent)) !== null) { + matches.push({ + speaker: match[1], + index: match.index + }) + } + + if (matches.length > 0) { + lines = [] + for (let i = 0; i < matches.length; i++) { + const currentMatch = matches[i] + const nextMatch = matches[i + 1] + const startIndex = currentMatch.index + const endIndex = nextMatch ? nextMatch.index : textContent.length + const content = textContent.substring(startIndex, endIndex).trim() + if (content) { + lines.push(content) + } + } + } + } + + return lines.map(line => { + const trimmedLine = line.trim() + if (trimmedLine === '') { + return { type: 'empty', content: '' } + } + + // 检查是否是说话人格式 + if (trimmedLine.match(/^(销售|客户|顾问)[::]/)) { + const speakerMatch = trimmedLine.match(/^(销售|客户|顾问)[::](.*)/) + if (speakerMatch) { + return { + type: 'speaker', + speaker: speakerMatch[1], + content: speakerMatch[2].trim() + } + } + } + + return { + type: 'text', + content: trimmedLine + } + }) + }, + + // 获取行的CSS类 + getLineClass(line) { + if (line.type === 'speaker') { + return `speaker ${line.speaker === '销售' || line.speaker === '顾问' ? 'sales' : 'customer'}` + } + if (line.type === 'empty') { + return 'no-text' + } + return 'text-line' + }, + + // 获取说话人标签 + getSpeakerLabel(line) { + if (line.type === 'speaker') { + return line.speaker + ':' + } + return null + }, + + // 获取说话人内容 + getSpeakerContent(line) { + if (line.type === 'speaker') { + return line.content || '' + } + if (line.type === 'empty') { + return line.content + } + return line.content || '' } } } @@ -724,4 +776,208 @@ export default { .el-table .el-button--text:hover { color: #409EFF; } + +/* 详情弹窗样式 - 参考录音管理页面 */ +.detail-view-container { + padding: 20px 0; +} + +.detail-header { + margin-bottom: 20px; + padding: 15px; + background-color: #f5f7fa; + border-radius: 4px; + border-left: 4px solid #409eff; +} + +.detail-header h3 { + margin: 0 0 10px 0; + color: #303133; + font-size: 18px; +} + +.detail-header p { + margin: 5px 0; + color: #606266; + font-size: 14px; +} + +.basic-info { + margin-bottom: 20px; +} + +.basic-info h4 { + margin: 0 0 15px 0; + color: #303133; + font-size: 16px; + border-bottom: 2px solid #409eff; + padding-bottom: 8px; +} + +.info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 12px; +} + +.info-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background-color: #f8f9fa; + border-radius: 6px; + border: 1px solid #e4e7ed; +} + +.info-label { + font-weight: 500; + color: #606266; + margin-right: 8px; +} + +.info-value { + font-weight: 600; + color: #303133; +} + +.original-content { + margin-bottom: 20px; +} + +.original-content h4 { + margin: 0 0 15px 0; + color: #303133; + font-size: 16px; + border-bottom: 2px solid #409eff; + padding-bottom: 8px; +} + +.content-display { + border: 1px solid #e4e7ed; + border-radius: 8px; + padding: 20px; + background-color: #fff; + max-height: 500px; + overflow-y: auto; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.formatted-content { + line-height: 1.8; + font-size: 15px; + color: #333; +} + +.formatted-content .speaker { + margin-bottom: 16px; + padding: 12px 16px; + border-radius: 8px; + border-left: 4px solid; + background-color: #f8f9fa; + transition: all 0.2s ease; +} + +.formatted-content .speaker:hover { + transform: translateX(2px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.formatted-content .speaker.sales { + background-color: #f0f9ff; + border-left-color: #409eff; +} + +.formatted-content .speaker.customer { + background-color: #f0f9f0; + border-left-color: #67c23a; +} + +.formatted-content .speaker.other { + background-color: #f5f5f5; + border-left-color: #909399; +} + +.formatted-content .speaker-label { + font-weight: 600; + color: #333; + margin-right: 8px; + font-size: 14px; + display: inline-block; + min-width: 50px; +} + +.formatted-content .speaker.sales .speaker-label { + color: #409eff; +} + +.formatted-content .speaker.customer .speaker-label { + color: #67c23a; +} + +.formatted-content .speaker-content { + color: #555; + line-height: 1.6; + word-wrap: break-word; +} + +.formatted-content .text-line { + margin-bottom: 12px; + padding: 8px 12px; + color: #666; + background-color: #f8f9fa; + border-radius: 6px; + border-left: 3px solid #e4e7ed; +} + +.formatted-content .no-text { + text-align: center; + color: #999; + font-style: italic; + padding: 40px 20px; + background-color: #f8f9fa; + border-radius: 8px; + border: 2px dashed #ddd; +} + +.detail-actions { + text-align: center; + padding-top: 15px; + border-top: 1px solid #e4e7ed; +} + +.detail-actions .el-button { + margin: 0 8px; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .info-grid { + grid-template-columns: 1fr; + } + + .info-item { + flex-direction: column; + align-items: flex-start; + } + + .info-label { + margin-bottom: 4px; + } + + .content-display { + padding: 15px; + max-height: 400px; + } + + .formatted-content .speaker { + padding: 10px 12px; + margin-bottom: 12px; + } + + .formatted-content .speaker-label { + display: block; + margin-bottom: 4px; + } +} diff --git a/src/views/sales/intelligent-pickup.vue b/src/views/sales/intelligent-pickup.vue new file mode 100644 index 0000000..59eefdd --- /dev/null +++ b/src/views/sales/intelligent-pickup.vue @@ -0,0 +1,725 @@ + + + + +