客户管理功能调整, 音频模型调整

This commit is contained in:
ZLI263
2025-10-21 17:47:31 +08:00
parent 0d21742e12
commit ea21831191
13 changed files with 646 additions and 143 deletions

View File

@@ -26,6 +26,10 @@
<el-input v-model="searchForm.creatorPhone" placeholder="请输入创建人电话" clearable style="width: 150px;" />
</el-form-item>
<el-form-item label="音频名称">
<el-input v-model="searchForm.audioName" placeholder="请输入音频名称" clearable style="width: 150px;" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="searchForm.startTime"
@@ -89,7 +93,7 @@
style="width: 100%"
@sort-change="handleSortChange"
>
<el-table-column prop="id" label="ID" width="80" sortable="custom" />
<el-table-column prop="id" label="ID" width="160" sortable="custom" />
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">
<el-tag
@@ -100,12 +104,16 @@
</el-tag>
</template>
</el-table-column>
<el-table-column prop="audioName" label="音频名称" width="150" show-overflow-tooltip />
<el-table-column prop="creatorName" label="创建人姓名" width="120" show-overflow-tooltip />
<el-table-column prop="creatorPhone" label="创建人电话" width="130" show-overflow-tooltip />
<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="audioName" label="音频名称" width="150" />
<el-table-column prop="creatorName" label="创建人姓名" width="120" />
<el-table-column prop="creatorPhone" label="创建人电话" width="130" />
<el-table-column prop="model" label="模型" width="200" />
<el-table-column prop="voice" label="声音" width="150" />
<el-table-column prop="inputText" label="输入文本" min-width="200">
<template slot-scope="scope">
<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: inline-block; max-width: 200px; word-break: keep-all; word-wrap: normal;">{{ scope.row.inputText }}</span>
</template>
</el-table-column>
<el-table-column prop="audioSizeBytes" label="文件大小" width="100">
<template slot-scope="scope">
{{ scope.row.audioSizeBytes ? formatFileSize(scope.row.audioSizeBytes) : '-' }}
@@ -118,7 +126,7 @@
</el-table-column>
<el-table-column prop="processingTimeMs" label="处理时间(ms)" width="120" />
<el-table-column prop="updateTime" label="修改时间" width="160" sortable="custom" />
<el-table-column label="操作" width="200" fixed="right">
<el-table-column label="操作" width="280" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleViewDetail(scope.row)">
详情
@@ -140,6 +148,15 @@
>
生成播放
</el-button>
<el-button
v-if="scope.row.audioName"
type="text"
size="small"
:loading="scope.row.asrLoading"
@click="handleSpeechToText(scope.row)"
>
音频转文字
</el-button>
<el-button
v-if="scope.row.status === 'FAILED'"
type="text"
@@ -168,46 +185,104 @@
<!-- 详情对话框 -->
<el-dialog
title="TTS请求详情"
title="音频详情"
:visible.sync="detailDialogVisible"
width="800px"
width="900px"
:close-on-click-modal="false"
class="detail-dialog"
>
<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.creatorName || '-' }}</el-descriptions-item>
<el-descriptions-item label="创建人电话">{{ currentRow.creatorPhone || '-' }}</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="音频名称">{{ currentRow.audioName || '-' }}</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.updateTime }}</el-descriptions-item>
<el-descriptions-item :span="2" label="输入文本">
<div style="max-height: 100px; overflow-y: auto;">
{{ currentRow.inputText }}
<div class="detail-content">
<!-- 基本信息卡片 -->
<el-card class="detail-card" shadow="never">
<div slot="header" class="card-header">
<i class="el-icon-document" />
<span>基本信息</span>
</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 class="detail-grid">
<div class="detail-item">
<label>ID</label>
<span class="detail-value">{{ currentRow.id || '-' }}</span>
</div>
<div class="detail-item">
<label>状态</label>
<el-tag :type="getStatusType(currentRow.status)" size="small">
{{ getStatusText(currentRow.status) }}
</el-tag>
</div>
<div class="detail-item">
<label>创建人</label>
<span class="detail-value">{{ currentRow.creatorName || '-' }}</span>
</div>
<div class="detail-item">
<label>联系电话</label>
<span class="detail-value">{{ currentRow.creatorPhone || '-' }}</span>
</div>
<div class="detail-item">
<label>模型</label>
<span class="detail-value">{{ currentRow.model || '-' }}</span>
</div>
<div class="detail-item">
<label>音色</label>
<span class="detail-value">{{ currentRow.voice || '-' }}</span>
</div>
</div>
</el-card>
<!-- 音频信息卡片 -->
<el-card class="detail-card" shadow="never">
<div slot="header" class="card-header">
<i class="el-icon-headset" />
<span>音频信息</span>
</div>
<div class="detail-grid">
<div class="detail-item">
<label>音频名称</label>
<span class="detail-value">{{ currentRow.audioName || '-' }}</span>
</div>
<div class="detail-item">
<label>文件大小</label>
<span class="detail-value">{{ formatFileSize(currentRow.audioSizeBytes) }}</span>
</div>
<div class="detail-item">
<label>音频时长</label>
<span class="detail-value">{{ currentRow.duration ? currentRow.duration.toFixed(2) + '秒' : '-' }}</span>
</div>
<div class="detail-item">
<label>处理时间</label>
<span class="detail-value">{{ currentRow.processingTimeMs ? currentRow.processingTimeMs + 'ms' : '-' }}</span>
</div>
<div class="detail-item">
<label>修改时间</label>
<span class="detail-value">{{ currentRow.updateTime || '-' }}</span>
</div>
</div>
</el-card>
<!-- 文本内容卡片 -->
<el-card class="detail-card" shadow="never">
<div slot="header" class="card-header">
<i class="el-icon-edit-outline" />
<span>输入文本</span>
</div>
<div class="text-content">
{{ currentRow.inputText || '无' }}
</div>
</el-card>
<!-- 错误信息卡片仅在有错误时显示 -->
<el-card v-if="currentRow.errorMessage" class="detail-card error-card" shadow="never">
<div slot="header" class="card-header error-header">
<i class="el-icon-warning" />
<span>错误信息</span>
</div>
<div class="error-content">
{{ currentRow.errorMessage }}
</div>
</el-card>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="detailDialogVisible = false">关闭</el-button>
<el-button
v-if="currentRow.shortUrl"
type="primary"
@click="handlePlayAudio(currentRow.shortUrl)"
>
播放音频
</el-button>
</div>
</el-dialog>
@@ -273,6 +348,41 @@
</div>
</el-dialog>
<!-- 音频播放器弹窗 -->
<el-dialog
title="音频播放"
:visible.sync="audioPlayerVisible"
width="500px"
:close-on-click-modal="false"
@close="handleCloseAudioPlayer"
>
<div class="audio-player-container">
<audio
ref="audioPlayer"
:src="currentAudioUrl"
controls
preload="metadata"
style="width: 100%;"
@loadstart="handleAudioLoadStart"
@canplay="handleAudioCanPlay"
@error="handleAudioError"
>
您的浏览器不支持音频播放
</audio>
<div v-if="audioLoading" class="audio-loading">
<i class="el-icon-loading" />
音频加载中...
</div>
<div v-if="audioError" class="audio-error">
<i class="el-icon-warning" />
{{ audioError }}
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCloseAudioPlayer">关闭</el-button>
</div>
</el-dialog>
<!-- 音频生成对话框 -->
<el-dialog
title="音频生成"
@@ -348,9 +458,11 @@
v-model="generateForm.inputText"
type="textarea"
:rows="8"
placeholder="请输入要转换为语音的文本"
placeholder="请输入要转换为语音的文本..."
maxlength="1000"
show-word-limit
resize="vertical"
style="font-size: 14px; line-height: 1.5;"
/>
</el-form-item>
@@ -448,7 +560,7 @@
</template>
<script>
import { getTtsRequestLogs, generateTtsAudio, generateTempUrl } from '@/api/audio-statistics'
import { getTtsRequestLogs, generateTtsAudio, generateTempUrl, speechToText } from '@/api/audio-statistics'
import { mapGetters } from 'vuex'
export default {
@@ -462,6 +574,7 @@ export default {
model: '',
creatorName: '',
creatorPhone: '',
audioName: '',
startTime: '',
endTime: ''
},
@@ -474,6 +587,11 @@ export default {
sortOrder: '',
detailDialogVisible: false,
currentRow: {},
// 音频播放器相关
audioPlayerVisible: false,
currentAudioUrl: '',
audioLoading: false,
audioError: '',
// 上传音频对话框相关
uploadDialogVisible: false,
uploadLoading: false,
@@ -612,6 +730,7 @@ export default {
model: '',
creatorName: '',
creatorPhone: '',
audioName: '',
startTime: '',
endTime: ''
}
@@ -653,7 +772,10 @@ export default {
// 播放音频
handlePlayAudio(audioUrl) {
if (audioUrl) {
window.open(audioUrl, '_blank')
this.currentAudioUrl = audioUrl
this.audioPlayerVisible = true
this.audioLoading = true
this.audioError = ''
} else {
this.$message.warning('音频文件不存在')
}
@@ -674,6 +796,40 @@ export default {
})
},
// 音频开始加载
handleAudioLoadStart() {
this.audioLoading = true
this.audioError = ''
},
// 音频可以播放
handleAudioCanPlay() {
this.audioLoading = false
this.audioError = ''
},
// 音频加载错误
handleAudioError(event) {
this.audioLoading = false
this.audioError = '音频加载失败,请检查网络连接或音频文件是否有效'
console.error('音频加载错误:', event)
},
// 关闭音频播放器
handleCloseAudioPlayer() {
// 停止音频播放
if (this.$refs.audioPlayer) {
this.$refs.audioPlayer.pause()
this.$refs.audioPlayer.currentTime = 0
}
// 关闭弹窗
this.audioPlayerVisible = false
// 重置状态
this.currentAudioUrl = ''
this.audioLoading = false
this.audioError = ''
},
// 获取状态类型
getStatusType(status) {
const statusMap = {
@@ -988,6 +1144,64 @@ export default {
}
},
// 音频转文字
async handleSpeechToText(row) {
if (!row || !row.audioName) {
this.$message.error('缺少音频文件名,无法转文字')
return
}
if (row.asrLoading) return
this.$set(row, 'asrLoading', true)
try {
let audioUrl = row.shortUrl
// 若还没有可访问的短链先生成临时URL
if (!audioUrl) {
const expiresInSeconds = 604800
const tempRes = await generateTempUrl(row.audioName, expiresInSeconds)
if (tempRes && (tempRes.success || tempRes.code === 20000) && (tempRes.tempUrl || tempRes.data?.tempUrl)) {
audioUrl = tempRes.tempUrl || tempRes.data.tempUrl
this.$set(row, 'shortUrl', audioUrl)
} else {
this.$message.error(tempRes?.message || '无法生成音频访问链接')
return
}
}
// 组装与后端 AsrRequest 匹配的请求体
const payload = {
id: row.id, // 添加ID字段传递给后端
audioUrl,
model: 'paraformer-v2',
format: 'wav',
sampleRate: 16000,
enablePunctuation: true,
enableNumberConversion: true,
enableSpeakerDiarization: false,
speakerCount: 1,
creatorName: row.creatorName || this.name || '',
creatorPhone: row.creatorPhone || this.phone || '',
audioName: row.audioName
}
const res = await speechToText(payload, row.id)
// 兼容 axios 响应包装
const ok = res && (res.success === true || res.code === 20000 || res.code === 200)
const text = (res && (res.data?.text || res.text || res.result || res.data)) || ''
if (ok) {
this.$alert(text || '未识别到有效文本', '转写结果', {
confirmButtonText: '确定'
})
} else {
this.$message.error(res?.message || '语音转文本失败')
}
} catch (e) {
console.error('ASR失败:', e)
this.$message.error('语音转文本失败: ' + (e.message || '未知错误'))
} finally {
this.$set(row, 'asrLoading', false)
}
},
// 确认生成音频
async handleConfirmGenerate() {
this.$refs.generateForm.validate(async(valid) => {
@@ -1052,4 +1266,229 @@ export default {
.dialog-footer {
text-align: right;
}
/* 优化文本输入框样式 */
.el-textarea__inner {
font-size: 14px !important;
line-height: 1.6 !important;
padding: 12px 15px !important;
border-radius: 6px !important;
border: 1px solid #dcdfe6 !important;
transition: all 0.3s ease !important;
resize: vertical !important;
min-height: 120px !important;
}
.el-textarea__inner:focus {
border-color: #409eff !important;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1) !important;
}
.el-textarea__inner::placeholder {
color: #c0c4cc !important;
font-size: 13px !important;
line-height: 1.5 !important;
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
}
/* 优化字数统计显示 */
.el-input__count {
background: rgba(255, 255, 255, 0.9) !important;
color: #909399 !important;
font-size: 12px !important;
padding: 2px 6px !important;
border-radius: 4px !important;
}
/* 优化表格单元格样式,防止换行 */
.el-table .cell {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
word-break: keep-all !important;
word-wrap: normal !important;
}
/* 特别针对输入文本列,强制不换行 */
.el-table tbody tr td:nth-child(8) .cell {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
max-width: 200px !important;
word-break: keep-all !important;
word-wrap: normal !important;
}
/* 确保所有表格单元格都不换行 */
.el-table td .cell {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
word-break: keep-all !important;
word-wrap: normal !important;
}
/* 针对输入文本列的具体样式优化 */
.el-table-column[prop="inputText"] .cell {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
word-break: keep-all !important;
word-wrap: normal !important;
display: inline-block !important;
max-width: 200px !important;
}
/* 音频播放器样式 */
.audio-player-container {
text-align: center;
padding: 20px 0;
}
.audio-loading {
margin-top: 10px;
color: #409eff;
font-size: 14px;
}
.audio-loading i {
margin-right: 5px;
animation: rotating 2s linear infinite;
}
.audio-error {
margin-top: 10px;
color: #f56c6c;
font-size: 14px;
}
.audio-error i {
margin-right: 5px;
}
@keyframes rotating {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 详情对话框样式优化 */
.detail-dialog .el-dialog__body {
padding: 20px;
max-height: 70vh;
overflow-y: auto;
}
.detail-content {
max-height: 60vh;
overflow-y: auto;
}
.detail-card {
margin-bottom: 20px;
border: 1px solid #e4e7ed;
border-radius: 8px;
}
.detail-card:last-child {
margin-bottom: 0;
}
.card-header {
display: flex;
align-items: center;
font-weight: 600;
color: #303133;
font-size: 16px;
}
.card-header i {
margin-right: 8px;
color: #409eff;
font-size: 18px;
}
.error-header {
color: #f56c6c;
}
.error-header i {
color: #f56c6c;
}
.detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.detail-item {
display: flex;
align-items: center;
min-height: 36px;
padding: 8px 12px;
background: #fafafa;
border-radius: 6px;
border: 1px solid #e4e7ed;
}
.detail-item label {
font-weight: 600;
color: #606266;
min-width: 80px;
margin-right: 12px;
flex-shrink: 0;
font-size: 14px;
}
.detail-value {
color: #303133;
word-break: break-all;
flex: 1;
font-size: 14px;
}
.text-content {
background: #f8f9fa;
border: 1px solid #e4e7ed;
border-radius: 6px;
padding: 16px;
line-height: 1.6;
color: #303133;
white-space: pre-wrap;
word-break: break-word;
min-height: 80px;
max-height: 200px;
overflow-y: auto;
}
.error-content {
background: #fef0f0;
border: 1px solid #fbc4c4;
border-radius: 6px;
padding: 16px;
color: #f56c6c;
line-height: 1.6;
word-break: break-word;
}
.error-card {
border-color: #fbc4c4;
}
.dialog-footer {
text-align: right;
padding-top: 20px;
border-top: 1px solid #e4e7ed;
}
.dialog-footer .el-button {
margin-left: 10px;
}
</style>

View File

@@ -1,6 +1,6 @@
<template>
<div class="app-container">
<h2>门店录音统计管理</h2>
<h2>机构录音统计管理</h2>
<!-- 操作区域 -->
<el-card style="margin-bottom: 20px;">
@@ -102,13 +102,13 @@
stripe
style="width: 100%"
>
<el-table-column prop="dealershipName" label="门店名称" width="150" />
<el-table-column prop="dealershipTotalDuration" label="门店录音总时长" width="150">
<el-table-column prop="dealershipName" label="机构名称" width="150" />
<el-table-column prop="dealershipTotalDuration" label="机构录音总时长" width="150">
<template slot-scope="scope">
{{ formatDuration(scope.row.dealershipTotalDuration) }}
</template>
</el-table-column>
<el-table-column prop="dealershipAvgDuration" label="门店录音平均时长" width="150">
<el-table-column prop="dealershipAvgDuration" label="机构录音平均时长" width="150">
<template slot-scope="scope">
{{ scope.row.dealershipAvgDuration }}
</template>

View File

@@ -4,8 +4,8 @@
<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.dealershipId" placeholder="请选择所属门店" clearable>
<el-form-item label="选择机构">
<el-select v-model="queryParams.dealershipId" placeholder="请选择所属机构" clearable>
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -167,7 +167,7 @@
</el-tag>
</template>
</el-table-column>
<el-table-column label="所属门店" prop="dealershipName" min-width="150" />
<el-table-column label="所属机构" prop="dealershipName" min-width="150" />
<el-table-column label="所属项目" prop="projectName" min-width="150" />
<el-table-column label="话术模型" prop="speechModel" min-width="120" />
<el-table-column label="上传状态" prop="uploadStatus" min-width="100">
@@ -377,7 +377,7 @@
{{ audioForm.intentionLevel }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="所属门店">{{ audioForm.dealershipName }}</el-descriptions-item>
<el-descriptions-item label="所属机构">{{ audioForm.dealershipName }}</el-descriptions-item>
<el-descriptions-item label="所属项目">{{ audioForm.projectName }}</el-descriptions-item>
<el-descriptions-item label="话术模型">{{ audioForm.speechModel }}</el-descriptions-item>
<el-descriptions-item label="上传状态">
@@ -419,8 +419,8 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属门店" prop="dealershipId">
<el-select v-model="audioForm.dealershipId" placeholder="请选择所属门店" style="width: 100%">
<el-form-item label="所属机构" prop="dealershipId">
<el-select v-model="audioForm.dealershipId" placeholder="请选择所属机构" style="width: 100%">
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -1039,7 +1039,7 @@ export default {
{ required: true, message: '请选择销售', trigger: 'change' }
]
},
// 门店选项
// 机构选项
dealershipOptions: [],
// 项目选项
projectOptions: [],
@@ -1109,10 +1109,10 @@ export default {
// 直接设置销售电话查询条件
this.queryParams.salesPhone = salesPhone
// 如果有分支机构名称,设置到门店选择中
// 如果有分支机构名称,设置到机构选择中
if (dealershipName) {
// 这里需要根据分支机构名称找到对应的ID
// 暂时先设置一个标识,等门店选项加载完成后再处理
// 暂时先设置一个标识,等机构选项加载完成后再处理
this.pendingDealershipName = dealershipName
}
@@ -1188,13 +1188,13 @@ export default {
this.pendingDealershipName = ''
},
// 获取门店选项
// 获取机构选项
getDealershipOptions() {
getDealershipList({ size: 1000 }).then(response => {
console.log('门店选项 1111111111111111111:', response)
console.log('机构选项 1111111111111111111:', response)
if (response.code === 20000) {
const data = response.data.records || response.data.data || response.data || []
console.log('门店选项 22222222222:', data)
console.log('机构选项 22222222222:', data)
this.dealershipOptions = data.map(item => ({
value: item.id,
label: item.dealershipName || item.name
@@ -1212,10 +1212,10 @@ export default {
this.getList()
}
} else {
console.error('获取门店选项失败:', response.message)
console.error('获取机构选项失败:', response.message)
}
}).catch(error => {
console.error('获取门店选项错误:', error)
console.error('获取机构选项错误:', error)
// 开发环境使用模拟数据
this.dealershipOptions = [
{ value: '1', label: '销售演示-上汽' },

View File

@@ -3,8 +3,8 @@
<!-- 查询条件区域 -->
<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.dealershipId" placeholder="请选择所属门店" clearable>
<el-form-item label="选择机构">
<el-select v-model="queryParams.dealershipId" placeholder="请选择所属机构" clearable>
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -15,14 +15,12 @@
</el-form-item>
<el-form-item label="所属销售">
<el-select v-model="queryParams.salesId" placeholder="请选择销售" clearable>
<el-option
v-for="item in salesOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-model="queryParams.salesName"
placeholder="请输入销售姓名(默认当前用户)"
clearable
style="width: 200px"
/>
</el-form-item>
<el-form-item label="客户姓名">
@@ -105,10 +103,15 @@
<el-table-column label="序号" type="index" width="50" />
<el-table-column label="客户姓名" prop="customerName" min-width="120" />
<el-table-column label="联系方式" prop="contact" min-width="150" />
<el-table-column label="所属门店" prop="dealershipName" min-width="150" />
<el-table-column label="所属机构" prop="dealershipName" min-width="150" />
<el-table-column label="所属销售" prop="salesName" min-width="150" />
<el-table-column label="录音条数" prop="recordingCount" sortable min-width="100" />
<el-table-column label="意向车型" prop="intendedModel" min-width="120" />
<el-table-column label="修改时间" prop="updateTime" min-width="150">
<template slot-scope="scope">
{{ formatTime(scope.row.updateTime) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template slot-scope="scope">
<el-button type="text" size="small" @click="handleViewDetails(scope.row)">查看详情</el-button>
@@ -145,7 +148,7 @@
</div>
<div class="card-content">
<div class="info-item">
<label>所属门店:</label>
<label>所属机构:</label>
<span>{{ customer.dealershipName }}</span>
</div>
<div class="info-item">
@@ -160,6 +163,10 @@
<label>意向车型:</label>
<span>{{ customer.intendedModel || '-' }}</span>
</div>
<div class="info-item">
<label>修改时间:</label>
<span>{{ formatTime(customer.updateTime) || '-' }}</span>
</div>
</div>
<div class="card-footer">
<el-button type="text" size="small" @click="handleViewDetails(customer)">查看详情</el-button>
@@ -196,8 +203,8 @@
<el-form-item label="联系方式" prop="contact">
<el-input v-model="customerForm.contact" placeholder="请输入联系方式" />
</el-form-item>
<el-form-item label="所属门店" prop="dealershipId">
<el-select v-model="customerForm.dealershipId" placeholder="请选择所属门店" style="width: 100%">
<el-form-item label="所属机构" prop="dealershipId">
<el-select v-model="customerForm.dealershipId" placeholder="请选择所属机构" style="width: 100%">
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -258,7 +265,7 @@
<el-row :gutter="20">
<el-col :span="12">
<div class="detail-item">
<label>所属门店:</label>
<label>所属机构:</label>
<span>{{ currentCustomer.dealershipName }}</span>
</div>
</el-col>
@@ -287,13 +294,13 @@
<el-col :span="12">
<div class="detail-item">
<label>创建时间:</label>
<span>{{ currentCustomer.createTime }}</span>
<span>{{ formatTime(currentCustomer.createTime) }}</span>
</div>
</el-col>
<el-col :span="12">
<div class="detail-item">
<label>更新时间:</label>
<span>{{ currentCustomer.updateTime }}</span>
<span>{{ formatTime(currentCustomer.updateTime) }}</span>
</div>
</el-col>
</el-row>
@@ -354,7 +361,7 @@ export default {
customerRules: {
customerName: [{ required: true, message: '请输入客户姓名', trigger: 'blur' }],
contact: [{ required: true, message: '请输入联系方式', trigger: 'blur' }],
dealershipId: [{ required: true, message: '请选择所属门店', trigger: 'change' }],
dealershipId: [{ required: true, message: '请选择所属机构', trigger: 'change' }],
salesId: [{ required: true, message: '请选择所属销售', trigger: 'change' }]
},
// 查询参数
@@ -364,20 +371,25 @@ export default {
customerName: '',
contact: '',
dealershipId: '',
projectId: '',
salesId: '',
salesName: '',
createTimeRange: [],
infoCard: ''
},
// 门店选项
// 机构选项
dealershipOptions: [],
// 项目选项
projectOptions: [],
// 销售选项
salesOptions: []
salesOptions: [],
// 当前用户信息
currentUser: {
name: '',
phone: ''
}
}
},
created() {
this.getCurrentUser()
this.getList()
this.getDealershipOptions()
this.getProjectOptions()
@@ -403,6 +415,9 @@ export default {
}
})
console.log('发送给后端的查询参数:', params)
console.log('原始queryParams:', this.queryParams)
getCustomerList(params).then(response => {
console.log('API响应:', response)
if (response.code === 20000) {
@@ -421,7 +436,7 @@ export default {
})
},
// 获取门店选项
// 获取机构选项
getDealershipOptions() {
getDealershipList({ size: 1000 }).then(response => {
if (response && response.code === 20000) {
@@ -430,10 +445,10 @@ export default {
label: item.dealershipName
}))
} else {
this.$message.error(response?.message || '获取门店列表失败')
this.$message.error(response?.message || '获取机构列表失败')
}
}).catch(error => {
console.error('获取门店列表失败:', error)
console.error('获取机构列表失败:', error)
// 开发环境使用模拟数据
this.dealershipOptions = [
{ value: '1', label: '销售演示-上汽' },
@@ -504,8 +519,7 @@ export default {
customerName: '',
contact: '',
dealershipId: '',
projectId: '',
salesId: '',
salesName: this.currentUser.name,
createTimeRange: [],
infoCard: ''
}
@@ -682,6 +696,8 @@ export default {
}
})
console.log('导出时发送给后端的查询参数:', params)
exportCustomerFlow(params).then(response => {
// 创建下载链接
const blob = new Blob([response], { type: 'application/vnd.ms-excel' })
@@ -701,6 +717,39 @@ export default {
handleViewDetails(row) {
this.currentCustomer = row
this.detailVisible = true
},
// 获取当前用户信息
getCurrentUser() {
// 从Vuex store中获取当前用户信息
const userName = this.$store.getters.name || ''
this.currentUser.name = userName || '当前用户'
// 设置默认查询条件
this.queryParams.salesName = this.currentUser.name
},
// 格式化时间显示
formatTime(timeStr) {
if (!timeStr) return '-'
try {
const date = new Date(timeStr)
if (isNaN(date.getTime())) return timeStr
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
} catch (error) {
console.error('时间格式化错误:', error)
return timeStr
}
}
}
}

View File

@@ -88,7 +88,7 @@
<el-col :span="12">
<el-card class="chart-card" shadow="never">
<div slot="header">
<span>门店录音数量</span>
<span>机构录音数量</span>
</div>
<div class="chart-container">
<div ref="dealershipChart" style="height: 300px;" />
@@ -312,7 +312,7 @@ export default {
},
xAxis: {
type: 'category',
data: ['上汽门店', '奔驰门店', '比亚迪门店', 'DCC门店']
data: ['上汽机构', '奔驰机构', '比亚迪机构', 'DCC机构']
},
yAxis: {
type: 'value'

View File

@@ -3,8 +3,8 @@
<!-- 查询条件区域 -->
<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.dealershipId" placeholder="请选择所属门店" clearable>
<el-form-item label="选择机构">
<el-select v-model="queryParams.dealershipId" placeholder="请选择所属机构" clearable>
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -50,7 +50,7 @@
<!-- 操作按钮区域 -->
<el-card class="action-container" shadow="never">
<el-button type="primary" @click="handleBatchAssign">批量分配设备到门店</el-button>
<el-button type="primary" @click="handleBatchAssign">批量分配设备到机构</el-button>
</el-card>
<!-- 数据表格区域 -->
@@ -64,7 +64,7 @@
<el-table-column type="selection" width="55" />
<el-table-column label="序号" type="index" width="50" />
<el-table-column label="设备编号" prop="deviceCode" sortable min-width="180" />
<el-table-column label="所属门店" prop="dealershipName" sortable min-width="120" />
<el-table-column label="所属机构" prop="dealershipName" sortable min-width="120" />
<el-table-column label="设备信息(最近一次)" min-width="300">
<template slot-scope="scope">
<div class="device-info">
@@ -120,8 +120,8 @@
<el-form-item label="设备编号" prop="deviceCode">
<el-input v-model="deviceForm.deviceCode" placeholder="请输入设备编号" />
</el-form-item>
<el-form-item label="所属门店" prop="dealershipId">
<el-select v-model="deviceForm.dealershipId" placeholder="请选择所属门店" style="width: 100%">
<el-form-item label="所属机构" prop="dealershipId">
<el-select v-model="deviceForm.dealershipId" placeholder="请选择所属机构" style="width: 100%">
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -163,15 +163,15 @@
</div>
</el-dialog>
<!-- 批量分配设备到门店对话框 -->
<!-- 批量分配设备到机构对话框 -->
<el-dialog
title="批量分配设备到门店"
title="批量分配设备到机构"
:visible.sync="batchAssignVisible"
width="500px"
>
<el-form ref="batchAssignForm" :model="batchAssignForm" :rules="batchAssignRules" label-width="100px">
<el-form-item label="选择门店" prop="dealershipId">
<el-select v-model="batchAssignForm.dealershipId" placeholder="请选择门店" style="width: 100%">
<el-form-item label="选择机构" prop="dealershipId">
<el-select v-model="batchAssignForm.dealershipId" placeholder="请选择机构" style="width: 100%">
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -252,7 +252,7 @@ export default {
{ required: true, message: '请输入设备编号', trigger: 'blur' }
],
dealershipId: [
{ required: true, message: '请选择所属门店', trigger: 'change' }
{ required: true, message: '请选择所属机构', trigger: 'change' }
],
bindStatus: [
{ required: true, message: '请选择绑定状态', trigger: 'change' }
@@ -260,10 +260,10 @@ export default {
},
batchAssignRules: {
dealershipId: [
{ required: true, message: '请选择门店', trigger: 'change' }
{ required: true, message: '请选择机构', trigger: 'change' }
]
},
// 门店选项
// 机构选项
dealershipOptions: [],
// 用户选项
userOptions: [],
@@ -278,7 +278,7 @@ export default {
this.getProjectOptions()
},
methods: {
// 获取门店选项
// 获取机构选项
getDealershipOptions() {
getDealershipList({ size: 1000 }).then(response => {
if (response && response.code === 20000) {
@@ -287,10 +287,10 @@ export default {
label: item.dealershipName
}))
} else {
this.$message.error(response?.message || '获取门店列表失败')
this.$message.error(response?.message || '获取机构列表失败')
}
}).catch(error => {
console.error('获取门店列表失败:', error)
console.error('获取机构列表失败:', error)
// 开发环境使用模拟数据
this.dealershipOptions = [
{ value: '1', label: 'DCC演示' },
@@ -435,7 +435,7 @@ export default {
this.getList()
},
// 批量分配设备到门店
// 批量分配设备到机构
handleBatchAssign() {
if (this.selectedIds.length === 0) {
this.$message.warning('请先选择要分配的设备')

View File

@@ -3,8 +3,8 @@
<!-- 查询条件区域 -->
<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.dealershipId" placeholder="请选择所属门店" clearable>
<el-form-item label="选择机构">
<el-select v-model="queryParams.dealershipId" placeholder="请选择所属机构" clearable>
<el-option
v-for="item in storeOptions"
:key="item.value"
@@ -62,7 +62,7 @@
>
<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="dealershipName" min-width="150" />
<el-table-column label="项目名称" prop="projectName" min-width="150" />
<el-table-column label="话术版本" prop="scriptVersion" min-width="100" />
<el-table-column label="话术发布时间" prop="scriptPublishTime" sortable min-width="150">
@@ -118,8 +118,8 @@
@close="handleDialogClose"
>
<el-form ref="projectForm" :model="projectForm" :rules="projectRules" label-width="100px">
<el-form-item label="所属门店" prop="dealershipId">
<el-select v-model="projectForm.dealershipId" placeholder="请选择所属门店" style="width: 100%">
<el-form-item label="所属机构" prop="dealershipId">
<el-select v-model="projectForm.dealershipId" placeholder="请选择所属机构" style="width: 100%">
<el-option
v-for="item in storeOptions"
:key="item.value"
@@ -191,7 +191,7 @@ export default {
// 表单验证规则
projectRules: {
dealershipId: [
{ required: true, message: '请选择所属门店', trigger: 'change' }
{ required: true, message: '请选择所属机构', trigger: 'change' }
],
projectName: [
{ required: true, message: '请输入项目名称', trigger: 'blur' }
@@ -200,7 +200,7 @@ export default {
{ required: true, message: '请选择项目状态', trigger: 'change' }
]
},
// 门店选项
// 机构选项
storeOptions: []
}
},
@@ -215,7 +215,7 @@ export default {
},
created() {
this.getList()
this.getDealershipList() // 获取门店列表
this.getDealershipList() // 获取机构列表
},
methods: {
// 获取列表数据
@@ -485,29 +485,29 @@ export default {
this.$refs.projectForm.resetFields()
},
// 获取门店列表
// 获取机构列表
getDealershipList() {
getDealershipList().then(response => {
console.log('门店列表响应:', response) // 调试日志
console.log('机构列表响应:', response) // 调试日志
if (response && response.code === 20000) {
console.log('门店列表响应 response.data :', response.data) // 调试日志
console.log('机构列表响应 response.data :', response.data) // 调试日志
this.storeOptions = response.data.map(item => ({
value: item.id,
label: item.dealershipName
}))
console.log('门店选项(按修改时间倒序):', this.storeOptions)
console.log('机构选项(按修改时间倒序):', this.storeOptions)
} else {
this.$message.error(response?.message || '获取门店列表失败')
this.$message.error(response?.message || '获取机构列表失败')
}
}).catch(error => {
console.error('获取门店列表失败:', error)
console.error('获取机构列表失败:', error)
// 开发环境使用模拟数据
this.storeOptions = [
{ value: 'DEAL001', label: '销售演示-奔驰' },
{ value: 'DEAL002', label: '销售演示-上汽' },
{ value: 'DEAL003', label: '销售演示-比亚迪' }
]
console.log('使用模拟门店数据:', this.storeOptions)
console.log('使用模拟机构数据:', this.storeOptions)
})
},

View File

@@ -13,8 +13,8 @@
/>
</el-form-item>
<el-form-item label="所属门店">
<el-select v-model="queryParams.dealershipId" placeholder="请选择门店" clearable>
<el-form-item label="所属机构">
<el-select v-model="queryParams.dealershipId" placeholder="请选择机构" clearable>
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -103,7 +103,7 @@
>
<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="dealershipName" min-width="150" />
<el-table-column label="视频名称" prop="videoName" min-width="200" />
<el-table-column label="所属销售" prop="salesName" min-width="150" />
<el-table-column label="客户姓名" prop="customerName" min-width="120" />
@@ -141,8 +141,8 @@
<el-form-item label="视频名称" prop="videoName">
<el-input v-model="videoForm.videoName" placeholder="请输入视频名称" />
</el-form-item>
<el-form-item label="所属门店" prop="dealershipId">
<el-select v-model="videoForm.dealershipId" placeholder="请选择所属门店" style="width: 100%">
<el-form-item label="所属机构" prop="dealershipId">
<el-select v-model="videoForm.dealershipId" placeholder="请选择所属机构" style="width: 100%">
<el-option
v-for="item in dealershipOptions"
:key="item.value"
@@ -211,7 +211,7 @@
</el-col>
<el-col :span="12">
<div class="detail-item">
<label>所属门店:</label>
<label>所属机构:</label>
<span>{{ currentVideo.dealershipName }}</span>
</div>
</el-col>
@@ -328,7 +328,7 @@ export default {
{ required: true, message: '请输入视频名称', trigger: 'blur' }
],
dealershipId: [
{ required: true, message: '请选择所属门店', trigger: 'change' }
{ required: true, message: '请选择所属机构', trigger: 'change' }
],
salesId: [
{ required: true, message: '请选择所属销售', trigger: 'change' }
@@ -343,7 +343,7 @@ export default {
{ required: true, message: '请输入视频时长', trigger: 'blur' }
]
},
// 门店选项
// 机构选项
dealershipOptions: [],
// 销售选项
salesOptions: []
@@ -429,7 +429,7 @@ export default {
})
},
// 获取门店选项
// 获取机构选项
getDealershipOptions() {
getDealershipList({ size: 1000 }).then(response => {
if (response.code === 20000) {
@@ -439,10 +439,10 @@ export default {
label: item.dealershipName || item.name
}))
} else {
console.error('获取门店选项失败:', response.message)
console.error('获取机构选项失败:', response.message)
}
}).catch(error => {
console.error('获取门店选项错误:', error)
console.error('获取机构选项错误:', error)
// 开发环境使用模拟数据
this.dealershipOptions = [
{ value: '1', label: '销售演示-上汽' },