音频生产展示

This commit is contained in:
ZLI263
2025-10-04 23:10:53 +08:00
parent 696bec5206
commit 69fd595ebb
7 changed files with 1540 additions and 187 deletions

View File

@@ -0,0 +1,381 @@
<template>
<div class="app-container">
<!-- 搜索区域 -->
<el-card style="margin-bottom: 20px;">
<el-form :model="searchForm" :inline="true" class="search-form">
<el-form-item label="状态">
<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 150px;">
<el-option label="全部" value="" />
<el-option label="成功" value="success" />
<el-option label="失败" value="failed" />
<el-option label="处理中" value="processing" />
</el-select>
</el-form-item>
<el-form-item label="模型">
<el-input v-model="searchForm.model" placeholder="请输入模型" clearable style="width: 150px;" />
</el-form-item>
<el-form-item label="创建人姓名">
<el-input v-model="searchForm.creatorName" placeholder="请输入创建人姓名" clearable style="width: 150px;" />
</el-form-item>
<el-form-item label="创建人电话">
<el-input v-model="searchForm.creatorPhone" placeholder="请输入创建人电话" clearable style="width: 150px;" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="searchForm.startTime"
type="datetime"
placeholder="开始时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 180px;"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="searchForm.endTime"
type="datetime"
placeholder="结束时间"
format="yyyy-MM-dd HH:mm:ss"
value-format="yyyy-MM-dd HH:mm:ss"
style="width: 180px;"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="loading" @click="handleSearch">
<i class="el-icon-search" />
查询
</el-button>
<el-button @click="handleReset">
<i class="el-icon-refresh" />
重置
</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 数据表格 -->
<el-card>
<div slot="header">
<span>TTS请求日志列表</span>
<div style="float: right;">
<el-button type="primary" size="small" @click="handleRefresh">
<i class="el-icon-refresh" />
刷新
</el-button>
</div>
</div>
<el-table
v-loading="loading"
:data="tableData"
border
stripe
style="width: 100%"
@sort-change="handleSortChange"
>
<el-table-column prop="id" label="ID" width="80" sortable="custom" />
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">
<el-tag
:type="getStatusType(scope.row.status)"
size="small"
>
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="model" label="模型" width="200" show-overflow-tooltip />
<el-table-column prop="voice" label="声音" width="150" show-overflow-tooltip />
<el-table-column prop="inputText" label="输入文本" min-width="200" show-overflow-tooltip />
<el-table-column prop="audioSizeBytes" label="文件大小" width="100">
<template slot-scope="scope">
{{ scope.row.audioSizeBytes ? formatFileSize(scope.row.audioSizeBytes) : '-' }}
</template>
</el-table-column>
<el-table-column prop="duration" label="时长(秒)" width="100">
<template slot-scope="scope">
{{ scope.row.duration ? scope.row.duration.toFixed(2) : '-' }}
</template>
</el-table-column>
<el-table-column prop="minioUrl" label="音频文件" width="120">
<template slot-scope="scope">
<el-button
v-if="scope.row.minioUrl"
type="text"
size="small"
@click="handlePlayAudio(scope.row.minioUrl)"
>
播放
</el-button>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="processingTimeMs" label="处理时间(ms)" width="120" />
<el-table-column prop="createTime" label="创建时间" width="160" sortable="custom" />
<el-table-column label="操作" width="120" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleViewDetail(scope.row)">
详情
</el-button>
<el-button
v-if="scope.row.status === 'FAILED'"
type="text"
size="small"
@click="handleRetry(scope.row)"
>
重试
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container" style="margin-top: 20px; text-align: right;">
<el-pagination
:current-page="pagination.pageNum"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.pageSize"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- 详情对话框 -->
<el-dialog
title="TTS请求详情"
:visible.sync="detailDialogVisible"
width="800px"
:close-on-click-modal="false"
>
<el-descriptions :column="2" border>
<el-descriptions-item label="ID">{{ currentRow.id }}</el-descriptions-item>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(currentRow.status)">
{{ getStatusText(currentRow.status) }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="模型">{{ currentRow.model }}</el-descriptions-item>
<el-descriptions-item label="声音">{{ currentRow.voice }}</el-descriptions-item>
<el-descriptions-item label="文件大小">{{ formatFileSize(currentRow.audioSizeBytes) }}</el-descriptions-item>
<el-descriptions-item label="时长">{{ currentRow.duration ? currentRow.duration.toFixed(2) + '秒' : '-' }}</el-descriptions-item>
<el-descriptions-item label="处理时间">{{ currentRow.processingTimeMs }}ms</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ currentRow.createTime }}</el-descriptions-item>
<el-descriptions-item :span="2" label="输入文本">
<div style="max-height: 100px; overflow-y: auto;">
{{ currentRow.inputText }}
</div>
</el-descriptions-item>
<el-descriptions-item v-if="currentRow.errorMessage" :span="2" label="错误信息">
<div style="color: #f56c6c;">{{ currentRow.errorMessage }}</div>
</el-descriptions-item>
</el-descriptions>
<div slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false">关闭</el-button>
<el-button
v-if="currentRow.minioUrl"
type="primary"
@click="handlePlayAudio(currentRow.minioUrl)"
>
播放音频
</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getTtsRequestLogs } from '@/api/audio-statistics'
export default {
name: 'AudioGeneration',
data() {
return {
loading: false,
tableData: [],
searchForm: {
status: '',
model: '',
creatorName: '',
creatorPhone: '',
startTime: '',
endTime: ''
},
pagination: {
pageNum: 1,
pageSize: 10,
total: 0
},
sortField: '',
sortOrder: '',
detailDialogVisible: false,
currentRow: {}
}
},
created() {
this.fetchData()
},
methods: {
// 获取数据
async fetchData() {
this.loading = true
try {
const params = {
pageNum: this.pagination.pageNum,
pageSize: this.pagination.pageSize,
...this.searchForm
}
if (this.sortField) {
params.sortField = this.sortField
params.sortOrder = this.sortOrder
}
const response = await getTtsRequestLogs(params)
// 后端直接返回数据,没有包装在success字段中
if (response && response.data) {
this.tableData = response.data || []
this.pagination.total = response.total || 0
} else {
this.$message.error('获取数据失败')
}
} catch (error) {
console.error('获取TTS请求日志失败:', error)
this.$message.error('获取数据失败')
} finally {
this.loading = false
}
},
// 搜索
handleSearch() {
this.pagination.pageNum = 1
this.fetchData()
},
// 重置
handleReset() {
this.searchForm = {
status: '',
model: '',
creatorName: '',
creatorPhone: '',
startTime: '',
endTime: ''
}
this.pagination.pageNum = 1
this.fetchData()
},
// 刷新
handleRefresh() {
this.fetchData()
},
// 分页大小改变
handleSizeChange(val) {
this.pagination.pageSize = val
this.pagination.pageNum = 1
this.fetchData()
},
// 当前页改变
handleCurrentChange(val) {
this.pagination.pageNum = val
this.fetchData()
},
// 排序改变
handleSortChange({ column, prop, order }) {
this.sortField = prop
this.sortOrder = order === 'ascending' ? 'asc' : 'desc'
this.fetchData()
},
// 查看详情
handleViewDetail(row) {
this.currentRow = row
this.detailDialogVisible = true
},
// 播放音频
handlePlayAudio(audioUrl) {
if (audioUrl) {
window.open(audioUrl, '_blank')
} else {
this.$message.warning('音频文件不存在')
}
},
// 重试
handleRetry(row) {
this.$confirm('确定要重试这个TTS请求吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// TODO: 实现重试逻辑
this.$message.success('重试请求已提交')
this.fetchData()
}).catch(() => {
this.$message.info('已取消重试')
})
},
// 获取状态类型
getStatusType(status) {
const statusMap = {
'SUCCESS': 'success',
'FAILED': 'danger',
'PROCESSING': 'warning'
}
return statusMap[status] || 'info'
},
// 获取状态文本
getStatusText(status) {
const statusMap = {
'SUCCESS': '成功',
'FAILED': '失败',
'PROCESSING': '处理中'
}
return statusMap[status] || status
},
// 格式化文件大小
formatFileSize(bytes) {
if (!bytes) return '-'
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(1024))
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]
}
}
}
</script>
<style scoped>
.search-form {
margin-bottom: 0;
}
.pagination-container {
margin-top: 20px;
text-align: right;
}
.dialog-footer {
text-align: right;
}
</style>

View File

@@ -228,116 +228,52 @@
title="TopSales评分详情"
:visible.sync="detailDialogVisible"
width="800px"
:close-on-click-modal="false"
@close="handleDetailDialogClose"
>
<div v-if="currentRecord">
<el-descriptions :column="2" border>
<el-descriptions-item label="状态">
<el-tag :type="getStatusType(currentRecord.status)">{{ currentRecord.status }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="销售人员">{{ currentRecord.salespersonName || currentRecord.salesName || '-' }}</el-descriptions-item>
<el-descriptions-item label="销售电话">{{ currentRecord.salespersonPhone || currentRecord.salesPhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="经销商">{{ currentRecord.dealerName || currentRecord.dealershipName || '-' }}</el-descriptions-item>
<el-descriptions-item label="经销商电话">{{ currentRecord.dealerPhone || currentRecord.dealershipPhone || '-' }}</el-descriptions-item>
<!-- 基础沟通能力 (3个维度) -->
<el-descriptions-item label="自然度评分">
<el-tag :type="getScoreType(currentRecord.naturalnessScore)" size="small">
{{ currentRecord.naturalnessScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="连贯性评分">
<el-tag :type="getScoreType(currentRecord.coherenceScore)" size="small">
{{ currentRecord.coherenceScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="流畅度评分">
<el-tag :type="getScoreType(currentRecord.fluencyScore)" size="small">
{{ currentRecord.fluencyScore || 0 }}分
</el-tag>
</el-descriptions-item>
<div v-if="currentRecord" class="detail-view-container">
<div class="detail-header">
<h3>{{ currentRecord.salespersonName || currentRecord.salesName || '未知销售' }}</h3>
<p><strong>状态:</strong><el-tag :type="getStatusType(currentRecord.status)">{{ currentRecord.status }}</el-tag></p>
<p><strong>销售电话:</strong>{{ currentRecord.salespersonPhone || currentRecord.salesPhone || '-' }}</p>
<p><strong>经销商:</strong>{{ currentRecord.dealerName || currentRecord.dealershipName || '-' }}</p>
<p><strong>创建时间:</strong>{{ currentRecord.createdAt }}</p>
</div>
<!-- 顾问表现 (3个维度) -->
<el-descriptions-item label="顾问积极性评分">
<el-tag :type="getScoreType(currentRecord.advisorAggressivenessScore)" size="small">
{{ currentRecord.advisorAggressivenessScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="顾问一致性评分">
<el-tag :type="getScoreType(currentRecord.advisorConsistencyScore)" size="small">
{{ currentRecord.advisorConsistencyScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="问候接待评分">
<el-tag :type="getScoreType(currentRecord.greetingReceptionScore)" size="small">
{{ currentRecord.greetingReceptionScore || 0 }}分
</el-tag>
</el-descriptions-item>
<div class="basic-info">
<h4>基本信息</h4>
<div class="info-grid">
<div class="info-item">
<span class="info-label">总用时:</span>
<span class="info-value">{{ currentRecord.elapsedTime }}秒</span>
</div>
<div class="info-item">
<span class="info-label">综合评分:</span>
<el-tag :type="getOverallScoreType(currentRecord)" size="small">
{{ getOverallScore(currentRecord) }}
</el-tag>
</div>
</div>
</div>
<!-- 产品介绍 (4个维度) -->
<el-descriptions-item label="品牌介绍评分">
<el-tag :type="getScoreType(currentRecord.brandIntroductionScore)" size="small">
{{ currentRecord.brandIntroductionScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="安全介绍评分">
<el-tag :type="getScoreType(currentRecord.safetyIntroductionScore)" size="small">
{{ currentRecord.safetyIntroductionScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="健康环境介绍评分">
<el-tag :type="getScoreType(currentRecord.healthEnvironmentIntroductionScore)" size="small">
{{ currentRecord.healthEnvironmentIntroductionScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="技术介绍评分">
<el-tag :type="getScoreType(currentRecord.technologyIntroductionScore)" size="small">
{{ currentRecord.technologyIntroductionScore || 0 }}分
</el-tag>
</el-descriptions-item>
<div class="original-content">
<h4>对话内容</h4>
<div class="content-display">
<div class="formatted-content">
<div v-for="(line, index) in getFormattedLines(currentRecord.srcContent)" :key="index" :class="getLineClass(line)">
<span v-if="getSpeakerLabel(line)" class="speaker-label">{{ getSpeakerLabel(line) }}</span>
<span class="speaker-content">{{ getSpeakerContent(line) }}</span>
</div>
</div>
</div>
</div>
<!-- 客户理解 (3个维度) -->
<el-descriptions-item label="客户需求理解评分">
<el-tag :type="getScoreType(currentRecord.customerNeedsUnderstandingScore)" size="small">
{{ currentRecord.customerNeedsUnderstandingScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="预算理解评分">
<el-tag :type="getScoreType(currentRecord.budgetUnderstandingScore)" size="small">
{{ currentRecord.budgetUnderstandingScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="推荐评分">
<el-tag :type="getScoreType(currentRecord.recommendationScore)" size="small">
{{ currentRecord.recommendationScore || 0 }}分
</el-tag>
</el-descriptions-item>
<!-- 成交促进 (2个维度) -->
<el-descriptions-item label="试驾邀请评分">
<el-tag :type="getScoreType(currentRecord.testDriveInvitationScore)" size="small">
{{ currentRecord.testDriveInvitationScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="谈判成交评分">
<el-tag :type="getScoreType(currentRecord.negotiationDealScore)" size="small">
{{ currentRecord.negotiationDealScore || 0 }}分
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="总用时(秒)">{{ currentRecord.elapsedTime }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ currentRecord.createdAt }}</el-descriptions-item>
</el-descriptions>
<!-- 显示原始内容 -->
<div style="margin-top: 20px;">
<h4>原始内容</h4>
<el-input
type="textarea"
:rows="6"
:value="currentRecord.srcContent"
readonly
/>
<div class="detail-actions">
<el-button type="primary" icon="el-icon-copy-document" @click="copyContent">复制内容</el-button>
<el-button type="success" icon="el-icon-download" @click="handleExport(currentRecord)">导出详情</el-button>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false">关 闭</el-button>
</div>
@@ -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;
}
}
</style>

View File

@@ -0,0 +1,725 @@
<template>
<div class="app-container">
<!-- 页面标题 -->
<div class="page-header">
<h2>智能捡漏</h2>
<p class="page-description">基于咨询场景分析的智能捡漏机会识别</p>
</div>
<!-- 查询条件区域 -->
<el-card class="filter-container" shadow="never">
<el-form ref="queryForm" :model="queryParams" :inline="true" label-width="100px">
<el-form-item label="主分类">
<el-select v-model="queryParams.mainCategory" placeholder="请选择主分类" clearable>
<el-option label="全部" value="" />
<el-option label="购车相关" value="购车相关" />
<el-option label="售后服务" value="售后服务" />
</el-select>
</el-form-item>
<el-form-item label="子分类">
<el-select v-model="queryParams.subCategory" placeholder="请选择子分类" clearable>
<el-option label="全部" value="" />
<el-option label="销售流程" value="销售流程" />
<el-option label="数字化服务" value="数字化服务" />
<el-option label="数字化服务 (APP使用)" value="数字化服务 (APP使用)" />
</el-select>
</el-form-item>
<el-form-item label="置信度">
<el-select v-model="queryParams.confidence" placeholder="请选择置信度" clearable>
<el-option label="全部" value="" />
<el-option label="高" value="高" />
<el-option label="中" value="中" />
<el-option label="低" value="低" />
</el-select>
</el-form-item>
<el-form-item label="优先级">
<el-select v-model="queryParams.priority" placeholder="请选择优先级" clearable>
<el-option label="全部" value="" />
<el-option label="高" value="高" />
<el-option label="中" value="中" />
<el-option label="低" value="低" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button>
<el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
<el-button type="success" icon="el-icon-refresh" @click="refreshData">刷新数据</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 统计卡片区域 -->
<div class="stats-container">
<el-row :gutter="20">
<el-col :span="6">
<el-card class="stats-card">
<div class="stats-item">
<div class="stats-value">{{ totalCount }}</div>
<div class="stats-label">总记录数</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stats-card">
<div class="stats-item">
<div class="stats-value high-confidence">{{ highConfidenceCount }}</div>
<div class="stats-label">高置信度</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stats-card">
<div class="stats-item">
<div class="stats-value high-priority">{{ highPriorityCount }}</div>
<div class="stats-label">高优先级</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="stats-card">
<div class="stats-item">
<div class="stats-value sales-related">{{ salesRelatedCount }}</div>
<div class="stats-label">购车相关</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
<!-- 数据表格区域 -->
<el-card class="table-container" shadow="never">
<div class="table-header">
<h3>咨询场景列表</h3>
<div class="table-actions">
<el-button type="primary" icon="el-icon-cpu" @click="showAiAnalysisDialog">AI分析</el-button>
</div>
</div>
<el-table
v-loading="loading"
:data="tableData"
border
stripe
style="width: 100%"
:default-sort="{ prop: 'createdAt', order: 'descending' }"
>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="mainCategory" label="主分类" width="120" align="center">
<template slot-scope="scope">
<el-tag :type="getCategoryType(scope.row.mainCategory)">
{{ scope.row.mainCategory }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="subCategory" label="子分类" width="150" align="center">
<template slot-scope="scope">
<el-tag type="info" size="small">
{{ scope.row.subCategory }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="confidence" label="置信度" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="getConfidenceType(scope.row.confidence)" size="small">
{{ scope.row.confidence }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="priority" label="优先级" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="getPriorityType(scope.row.priority)" size="small">
{{ scope.row.priority }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="userDemand" label="用户需求" min-width="200" show-overflow-tooltip />
<el-table-column prop="involvedVehicleModels" label="涉及车型" width="120" align="center">
<template slot-scope="scope">
<span v-if="scope.row.involvedVehicleModels">
<el-tag type="warning" size="mini">{{ scope.row.involvedVehicleModels }}</el-tag>
</span>
<span v-else class="text-muted">-</span>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="创建时间" width="160" align="center" sortable>
<template slot-scope="scope">
{{ formatDate(scope.row.createdAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="120" align="center" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small" @click="viewDetail(scope.row)">
<i class="el-icon-view" /> 详情
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
:current-page="pagination.current"
:page-sizes="[10, 20, 50, 100]"
:page-size="pagination.size"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
<!-- AI分析弹窗 -->
<el-dialog
title="AI智能分析"
:visible.sync="aiAnalysisDialogVisible"
width="60%"
:close-on-click-modal="false"
>
<div class="ai-analysis-content">
<div class="input-section">
<label class="input-label">请输入需要分析的文本内容:</label>
<el-input
v-model="analysisText"
type="textarea"
:rows="8"
placeholder="请输入咨询对话内容或相关文本,AI将为您分析并提取关键信息..."
maxlength="5000"
show-word-limit
/>
</div>
<div v-if="analysisResult" class="result-section">
<h4>分析结果:</h4>
<div class="analysis-result">
<pre>{{ analysisResult }}</pre>
</div>
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="aiAnalysisDialogVisible = false">取消</el-button>
<el-button
type="primary"
:loading="analysisLoading"
:disabled="!analysisText.trim()"
@click="performAiAnalysis"
>
{{ analysisLoading ? '分析中...' : '开始分析' }}
</el-button>
</div>
</el-dialog>
<!-- 详情弹窗 -->
<el-dialog
title="咨询场景详情"
:visible.sync="detailDialogVisible"
width="80%"
:close-on-click-modal="false"
>
<div v-if="currentRecord" class="detail-content">
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
<label>主分类:</label>
<el-tag :type="getCategoryType(currentRecord.mainCategory)">
{{ currentRecord.mainCategory }}
</el-tag>
</div>
</el-col>
<el-col :span="12">
<div class="detail-item">
<label>子分类:</label>
<el-tag type="info">{{ currentRecord.subCategory }}</el-tag>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
<label>置信度:</label>
<el-tag :type="getConfidenceType(currentRecord.confidence)">
{{ currentRecord.confidence }}
</el-tag>
</div>
</el-col>
<el-col :span="12">
<div class="detail-item">
<label>优先级:</label>
<el-tag :type="getPriorityType(currentRecord.priority)">
{{ currentRecord.priority }}
</el-tag>
</div>
</el-col>
</el-row>
<div class="detail-item">
<label>用户需求:</label>
<div class="detail-text">{{ currentRecord.userDemand }}</div>
</div>
<div class="detail-item">
<label>咨询内容:</label>
<div class="detail-text">{{ currentRecord.consultingContent }}</div>
</div>
<div class="detail-item">
<label>涉及车型:</label>
<div class="detail-text">
<span v-if="currentRecord.involvedVehicleModels">
<el-tag type="warning">{{ currentRecord.involvedVehicleModels }}</el-tag>
</span>
<span v-else class="text-muted">无</span>
</div>
</div>
<div class="detail-item">
<label>知识缺口:</label>
<div class="detail-text">{{ currentRecord.knowledgeGap }}</div>
</div>
<div class="detail-item">
<label>备注:</label>
<div class="detail-text">{{ currentRecord.remarks }}</div>
</div>
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
<label>创建时间:</label>
<span>{{ formatDate(currentRecord.createdAt) }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="detail-item">
<label>更新时间:</label>
<span>{{ formatDate(currentRecord.updatedAt) }}</span>
</div>
</el-col>
</el-row>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getConsultingScenarioList, processConsultingScenario } from '@/api/sales'
export default {
name: 'IntelligentPickup',
data() {
return {
loading: false,
tableData: [],
detailDialogVisible: false,
currentRecord: null,
aiAnalysisDialogVisible: false,
analysisText: '',
analysisResult: '',
analysisLoading: false,
queryParams: {
mainCategory: '',
subCategory: '',
confidence: '',
priority: ''
},
pagination: {
current: 1,
size: 10,
total: 0,
pages: 1
}
}
},
computed: {
totalCount() {
return this.pagination.total
},
highConfidenceCount() {
return this.tableData.filter(item => item.confidence === '高').length
},
highPriorityCount() {
return this.tableData.filter(item => item.priority === '高').length
},
salesRelatedCount() {
return this.tableData.filter(item => item.mainCategory === '购车相关').length
}
},
created() {
this.fetchData()
},
methods: {
// 获取数据
async fetchData() {
this.loading = true
try {
const params = {
current: this.pagination.current,
size: this.pagination.size,
...this.queryParams
}
// 过滤空值参数
Object.keys(params).forEach(key => {
if (params[key] === '') {
delete params[key]
}
})
const response = await getConsultingScenarioList(params)
// 添加调试信息
console.log('API响应:', response)
if (response.code === 20000) {
this.tableData = response.data || []
this.pagination = {
current: response.current || 1,
size: response.size || 10,
total: response.total || 0,
pages: response.pages || 1
}
console.log('设置的tableData:', this.tableData)
console.log('设置的pagination:', this.pagination)
} else {
console.log('API调用失败:', response)
this.$message.error(response.message || '获取数据失败')
}
} catch (error) {
console.error('获取数据失败:', error)
this.$message.error('获取数据失败,请稍后重试')
} finally {
this.loading = false
}
},
// 查询
handleQuery() {
this.pagination.current = 1
this.fetchData()
},
// 重置查询条件
resetQuery() {
this.queryParams = {
mainCategory: '',
subCategory: '',
confidence: '',
priority: ''
}
this.pagination.current = 1
this.fetchData()
},
// 刷新数据
refreshData() {
this.fetchData()
},
// 分页大小改变
handleSizeChange(val) {
this.pagination.size = val
this.pagination.current = 1
this.fetchData()
},
// 当前页改变
handleCurrentChange(val) {
this.pagination.current = val
this.fetchData()
},
// 查看详情
viewDetail(row) {
this.currentRecord = row
this.detailDialogVisible = true
},
// 显示AI分析对话框
showAiAnalysisDialog() {
this.aiAnalysisDialogVisible = true
this.analysisText = ''
this.analysisResult = ''
},
// 执行AI分析
async performAiAnalysis() {
if (!this.analysisText.trim()) {
this.$message.warning('请输入需要分析的文本内容')
return
}
// 关闭弹窗并开始后台分析
this.aiAnalysisDialogVisible = false
this.$message.info('AI分析已开始,请稍后查看结果')
this.analysisLoading = true
try {
const response = await processConsultingScenario({
chat: this.analysisText.trim(),
businessType: 'Channel_4in1',
sourceCorpusId: '2180459',
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, '-')
})
if (response.code === 20000) {
this.$message.success('AI分析完成')
// 刷新列表数据以显示新的分析结果
this.fetchData()
} else {
this.$message.error(response.message || 'AI分析失败')
}
} catch (error) {
console.error('AI分析失败:', error)
this.$message.error('AI分析失败,请稍后重试')
} finally {
this.analysisLoading = false
}
},
// 格式化日期
formatDate(dateString) {
if (!dateString) return '-'
const date = new Date(dateString)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
},
// 获取分类标签类型
getCategoryType(category) {
const typeMap = {
'购车相关': 'success',
'售后服务': 'warning'
}
return typeMap[category] || 'info'
},
// 获取置信度标签类型
getConfidenceType(confidence) {
const typeMap = {
'高': 'success',
'中': 'warning',
'低': 'danger'
}
return typeMap[confidence] || 'info'
},
// 获取优先级标签类型
getPriorityType(priority) {
const typeMap = {
'高': 'danger',
'中': 'warning',
'低': 'success'
}
return typeMap[priority] || 'info'
}
}
}
</script>
<style lang="scss" scoped>
.app-container {
padding: 20px;
}
.page-header {
margin-bottom: 20px;
h2 {
margin: 0 0 8px 0;
color: #303133;
font-size: 24px;
font-weight: 600;
}
.page-description {
margin: 0;
color: #909399;
font-size: 14px;
}
}
.filter-container {
margin-bottom: 20px;
.el-form {
margin-bottom: 0;
}
}
.stats-container {
margin-bottom: 20px;
.stats-card {
text-align: center;
.stats-item {
.stats-value {
font-size: 32px;
font-weight: bold;
color: #409EFF;
margin-bottom: 8px;
&.high-confidence {
color: #67C23A;
}
&.high-priority {
color: #F56C6C;
}
&.sales-related {
color: #E6A23C;
}
}
.stats-label {
font-size: 14px;
color: #909399;
}
}
}
}
.table-container {
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
h3 {
margin: 0;
color: #303133;
font-size: 18px;
font-weight: 600;
}
}
}
.pagination-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
.detail-content {
.detail-item {
margin-bottom: 16px;
label {
display: inline-block;
width: 100px;
font-weight: bold;
color: #606266;
margin-right: 8px;
}
.detail-text {
margin-top: 4px;
line-height: 1.6;
color: #303133;
white-space: pre-wrap;
}
}
}
.text-muted {
color: #C0C4CC;
}
.dialog-footer {
text-align: center;
}
// AI分析对话框样式
.ai-analysis-content {
.input-section {
margin-bottom: 20px;
.input-label {
display: block;
margin-bottom: 8px;
font-weight: bold;
color: #606266;
}
}
.result-section {
margin-top: 20px;
h4 {
margin: 0 0 12px 0;
color: #303133;
font-size: 16px;
font-weight: 600;
}
.analysis-result {
background: #f5f7fa;
border: 1px solid #e4e7ed;
border-radius: 4px;
padding: 16px;
max-height: 400px;
overflow-y: auto;
pre {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.6;
color: #303133;
white-space: pre-wrap;
word-wrap: break-word;
}
}
}
}
// 响应式设计
@media (max-width: 768px) {
.stats-container {
.el-col {
margin-bottom: 10px;
}
}
.table-header {
flex-direction: column;
align-items: flex-start;
.table-actions {
margin-top: 10px;
}
}
}
</style>