录音文件上传和列表
This commit is contained in:
@@ -130,6 +130,7 @@
|
|||||||
<!-- 操作按钮区域 -->
|
<!-- 操作按钮区域 -->
|
||||||
<el-card class="action-container" shadow="never">
|
<el-card class="action-container" shadow="never">
|
||||||
<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-button type="success" icon="el-icon-upload" @click="handleUpload">上传音频</el-button>
|
||||||
<el-button type="primary" @click="handleAllocateProject">分配项目</el-button>
|
<el-button type="primary" @click="handleAllocateProject">分配项目</el-button>
|
||||||
<el-button type="primary" @click="handleAllocateSales">分配销售</el-button>
|
<el-button type="primary" @click="handleAllocateSales">分配销售</el-button>
|
||||||
<el-button type="primary" @click="handleMergeRecordings">录音合并</el-button>
|
<el-button type="primary" @click="handleMergeRecordings">录音合并</el-button>
|
||||||
@@ -188,8 +189,61 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
<el-table-column label="音频文件" prop="audioFileUrl" min-width="250">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
|
<div v-if="scope.row.audioFileUrl">
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<el-tag type="success" size="mini" style="margin-right: 8px;">
|
||||||
|
<i class="el-icon-video-play"></i>
|
||||||
|
有音频
|
||||||
|
</el-tag>
|
||||||
|
<el-link
|
||||||
|
type="primary"
|
||||||
|
:href="getFullAudioUrl(scope.row.audioFileUrl)"
|
||||||
|
target="_blank"
|
||||||
|
:underline="false"
|
||||||
|
>
|
||||||
|
<i class="el-icon-document"></i>
|
||||||
|
{{ scope.row.audioFileOriginalName || getAudioFileName(scope.row.audioFileUrl) }}
|
||||||
|
</el-link>
|
||||||
|
<el-button
|
||||||
|
type="text"
|
||||||
|
size="mini"
|
||||||
|
icon="el-icon-download"
|
||||||
|
@click="downloadAudioFile(scope.row)"
|
||||||
|
style="margin-left: 10px;"
|
||||||
|
>
|
||||||
|
下载
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<el-tag type="info" size="mini">
|
||||||
|
<i class="el-icon-warning"></i>
|
||||||
|
暂无音频文件
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="280" fixed="right">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
v-if="scope.row.audioFileUrl"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-video-play"
|
||||||
|
@click="handlePlay(scope.row)"
|
||||||
|
>
|
||||||
|
播放
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-else
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-video-play"
|
||||||
|
disabled
|
||||||
|
style="color: #c0c4cc;"
|
||||||
|
>
|
||||||
|
播放
|
||||||
|
</el-button>
|
||||||
<el-button type="text" @click="handleView(scope.row)">查看</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" @click="handleEdit(scope.row)">编辑</el-button>
|
||||||
<el-button type="text" @click="handleDelete(scope.row)">删除</el-button>
|
<el-button type="text" @click="handleDelete(scope.row)">删除</el-button>
|
||||||
@@ -436,6 +490,242 @@
|
|||||||
<el-button type="primary" @click="handleEditSubmit">确 定</el-button>
|
<el-button type="primary" @click="handleEditSubmit">确 定</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 上传音频对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="上传音频"
|
||||||
|
:visible.sync="uploadDialogVisible"
|
||||||
|
width="800px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="handleUploadDialogClose"
|
||||||
|
>
|
||||||
|
<el-form ref="uploadForm" :model="uploadForm" :rules="uploadRules" label-width="120px">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="录音名称" prop="recordingName">
|
||||||
|
<el-input v-model="uploadForm.recordingName" placeholder="请输入录音名称" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="所属门店" prop="dealershipId">
|
||||||
|
<el-select v-model="uploadForm.dealershipId" placeholder="请选择所属门店" style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in dealershipOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="所属项目" prop="projectId">
|
||||||
|
<el-select v-model="uploadForm.projectId" placeholder="请选择所属项目" style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in projectOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="所属销售" prop="salesId">
|
||||||
|
<el-select v-model="uploadForm.salesId" placeholder="请选择销售" style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in salesOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="客户姓名" prop="customerName">
|
||||||
|
<el-input v-model="uploadForm.customerName" placeholder="请输入客户姓名" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="客户手机号" prop="customerPhone">
|
||||||
|
<el-input v-model="uploadForm.customerPhone" placeholder="请输入客户手机号" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="意向级别" prop="intentionLevel">
|
||||||
|
<el-select v-model="uploadForm.intentionLevel" placeholder="请选择意向级别" style="width: 100%">
|
||||||
|
<el-option label="A级" value="A" />
|
||||||
|
<el-option label="B级" value="B" />
|
||||||
|
<el-option label="C级" value="C" />
|
||||||
|
<el-option label="D级" value="D" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="录音时间" prop="recordingTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="uploadForm.recordingTime"
|
||||||
|
type="datetime"
|
||||||
|
placeholder="选择录音时间"
|
||||||
|
format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
value-format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="音频文件" prop="audioFile">
|
||||||
|
<el-upload
|
||||||
|
ref="audioUpload"
|
||||||
|
:action="uploadAction"
|
||||||
|
:headers="uploadHeaders"
|
||||||
|
:data="uploadData"
|
||||||
|
:before-upload="beforeAudioUpload"
|
||||||
|
:on-success="handleUploadSuccess"
|
||||||
|
:on-error="handleUploadError"
|
||||||
|
:on-progress="handleUploadProgress"
|
||||||
|
:file-list="uploadFileList"
|
||||||
|
:auto-upload="false"
|
||||||
|
:limit="1"
|
||||||
|
accept=".mp3,.wav,.m4a,.aac,.flac"
|
||||||
|
drag
|
||||||
|
>
|
||||||
|
<i class="el-icon-upload"></i>
|
||||||
|
<div class="el-upload__text">将音频文件拖到此处,或<em>点击上传</em></div>
|
||||||
|
<div class="el-upload__tip" slot="tip">
|
||||||
|
支持 mp3、wav、m4a、aac、flac 格式,文件大小不超过 100MB
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="录音描述" prop="description">
|
||||||
|
<el-input
|
||||||
|
v-model="uploadForm.description"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入录音描述"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="uploadDialogVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="handleUploadSubmit" :loading="uploadLoading">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 音频播放器对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="音频播放"
|
||||||
|
:visible.sync="playDialogVisible"
|
||||||
|
width="600px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="handlePlayDialogClose"
|
||||||
|
>
|
||||||
|
<div class="audio-player-container">
|
||||||
|
<div class="audio-info">
|
||||||
|
<h3>{{ currentAudio.recordingName }}</h3>
|
||||||
|
<p><strong>客户:</strong>{{ currentAudio.customerName }} ({{ currentAudio.customerPhone }})</p>
|
||||||
|
<p><strong>销售:</strong>{{ currentAudio.salesName }}</p>
|
||||||
|
<p><strong>录音时间:</strong>{{ currentAudio.recordingTime }}</p>
|
||||||
|
<p><strong>时长:</strong>{{ currentAudio.duration }} 分钟</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="audio-player">
|
||||||
|
<div v-if="audioLoading" class="audio-loading">
|
||||||
|
<i class="el-icon-loading"></i>
|
||||||
|
<span>音频加载中...</span>
|
||||||
|
</div>
|
||||||
|
<audio
|
||||||
|
ref="audioPlayer"
|
||||||
|
:src="currentAudio.audioUrl"
|
||||||
|
controls
|
||||||
|
preload="metadata"
|
||||||
|
@loadstart="handleAudioLoadStart"
|
||||||
|
@loadedmetadata="handleAudioLoaded"
|
||||||
|
@timeupdate="handleTimeUpdate"
|
||||||
|
@ended="handleAudioEnded"
|
||||||
|
@error="handleAudioError"
|
||||||
|
@canplay="handleAudioCanPlay"
|
||||||
|
style="width: 100%;"
|
||||||
|
>
|
||||||
|
您的浏览器不支持音频播放
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="audio-controls">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
icon="el-icon-video-play"
|
||||||
|
@click="playAudio"
|
||||||
|
:disabled="!currentAudio.audioUrl"
|
||||||
|
>
|
||||||
|
播放
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
icon="el-icon-video-pause"
|
||||||
|
@click="pauseAudio"
|
||||||
|
:disabled="!currentAudio.audioUrl"
|
||||||
|
>
|
||||||
|
暂停
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
icon="el-icon-refresh"
|
||||||
|
@click="restartAudio"
|
||||||
|
:disabled="!currentAudio.audioUrl"
|
||||||
|
>
|
||||||
|
重新播放
|
||||||
|
</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<div class="playback-speed">
|
||||||
|
<span>播放速度:</span>
|
||||||
|
<el-select v-model="playbackSpeed" size="small" style="width: 100px;">
|
||||||
|
<el-option label="0.5x" value="0.5" />
|
||||||
|
<el-option label="0.75x" value="0.75" />
|
||||||
|
<el-option label="1.0x" value="1.0" />
|
||||||
|
<el-option label="1.25x" value="1.25" />
|
||||||
|
<el-option label="1.5x" value="1.5" />
|
||||||
|
<el-option label="2.0x" value="2.0" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="audio-progress" v-if="audioDuration > 0">
|
||||||
|
<div class="progress-info">
|
||||||
|
<span>{{ formatTime(currentTime) }} / {{ formatTime(audioDuration) }}</span>
|
||||||
|
<span>{{ Math.round((currentTime / audioDuration) * 100) }}%</span>
|
||||||
|
</div>
|
||||||
|
<el-progress
|
||||||
|
:percentage="(currentTime / audioDuration) * 100"
|
||||||
|
:show-text="false"
|
||||||
|
:stroke-width="8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="audio-notes" v-if="currentAudio.description">
|
||||||
|
<h4>录音备注:</h4>
|
||||||
|
<p>{{ currentAudio.description }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="playDialogVisible = false">关 闭</el-button>
|
||||||
|
<el-button type="primary" @click="downloadAudio" :disabled="!currentAudio.audioUrl">
|
||||||
|
下载音频
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -469,6 +759,45 @@ export default {
|
|||||||
viewDialogVisible: false,
|
viewDialogVisible: false,
|
||||||
editDialogVisible: false,
|
editDialogVisible: false,
|
||||||
isEdit: false,
|
isEdit: false,
|
||||||
|
uploadDialogVisible: false,
|
||||||
|
uploadLoading: false,
|
||||||
|
uploadFileList: [],
|
||||||
|
// 音频播放相关
|
||||||
|
playDialogVisible: false,
|
||||||
|
currentAudio: {
|
||||||
|
recordingName: '',
|
||||||
|
customerName: '',
|
||||||
|
customerPhone: '',
|
||||||
|
salesName: '',
|
||||||
|
recordingTime: '',
|
||||||
|
duration: 0,
|
||||||
|
audioUrl: '',
|
||||||
|
description: ''
|
||||||
|
},
|
||||||
|
currentTime: 0,
|
||||||
|
audioDuration: 0,
|
||||||
|
playbackSpeed: '1.0',
|
||||||
|
audioLoading: false,
|
||||||
|
uploadAction: process.env.VUE_APP_BASE_API + '/audio/upload',
|
||||||
|
uploadHeaders: {
|
||||||
|
'Authorization': 'Bearer ' + this.$store.getters.token
|
||||||
|
},
|
||||||
|
uploadData: {},
|
||||||
|
uploadRules: {
|
||||||
|
recordingName: [
|
||||||
|
{ required: true, message: '请输入录音名称', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
customerName: [
|
||||||
|
{ required: true, message: '请输入客户姓名', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
customerPhone: [
|
||||||
|
{ required: true, message: '请输入客户手机号', trigger: 'blur' },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
audioFile: [
|
||||||
|
{ required: true, message: '请选择音频文件', trigger: 'change' }
|
||||||
|
]
|
||||||
|
},
|
||||||
// 查询参数
|
// 查询参数
|
||||||
queryParams: {
|
queryParams: {
|
||||||
current: 1,
|
current: 1,
|
||||||
@@ -551,6 +880,19 @@ export default {
|
|||||||
duration: [
|
duration: [
|
||||||
{ required: true, message: '请输入录音时长', trigger: 'blur' }
|
{ required: true, message: '请输入录音时长', trigger: 'blur' }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
// 上传音频表单
|
||||||
|
uploadForm: {
|
||||||
|
recordingName: '',
|
||||||
|
dealershipId: '',
|
||||||
|
projectId: '',
|
||||||
|
salesId: '',
|
||||||
|
customerName: '',
|
||||||
|
customerPhone: '',
|
||||||
|
intentionLevel: '',
|
||||||
|
recordingTime: '',
|
||||||
|
audioFile: null,
|
||||||
|
description: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -560,6 +902,13 @@ export default {
|
|||||||
this.getProjectOptions()
|
this.getProjectOptions()
|
||||||
this.getSalesOptions()
|
this.getSalesOptions()
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
playbackSpeed(newSpeed) {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.playbackRate = parseFloat(newSpeed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// 获取列表数据
|
// 获取列表数据
|
||||||
getList() {
|
getList() {
|
||||||
@@ -608,7 +957,12 @@ export default {
|
|||||||
speechModel: 'GPT-4',
|
speechModel: 'GPT-4',
|
||||||
uploadStatus: '已上传',
|
uploadStatus: '已上传',
|
||||||
syncStatus: '已同步',
|
syncStatus: '已同步',
|
||||||
isMerged: false
|
isMerged: false,
|
||||||
|
audioFileUrl: '/api/audio/1953606689980715010_db3c711d-e581-49bb-814f-4709c0431021.mp3',
|
||||||
|
audioFileOriginalName: 'kk 2024-08-29 10-活动.mp3',
|
||||||
|
audioFileExtension: 'mp3',
|
||||||
|
audioFileSize: 79488,
|
||||||
|
remarks: '客户对产品价格比较关注,需要进一步跟进'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
@@ -625,7 +979,12 @@ export default {
|
|||||||
speechModel: 'GPT-3.5',
|
speechModel: 'GPT-3.5',
|
||||||
uploadStatus: '已上传',
|
uploadStatus: '已上传',
|
||||||
syncStatus: '已同步',
|
syncStatus: '已同步',
|
||||||
isMerged: true
|
isMerged: true,
|
||||||
|
audioFileUrl: '/api/audio/1953606689980715011_db3c711d-e581-49bb-814f-4709c0431022.mp3',
|
||||||
|
audioFileOriginalName: '客户咨询录音_002.mp3',
|
||||||
|
audioFileExtension: 'mp3',
|
||||||
|
audioFileSize: 125000,
|
||||||
|
remarks: '客户对产品功能很感兴趣,有购买意向'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
@@ -642,7 +1001,12 @@ export default {
|
|||||||
speechModel: 'GPT-4',
|
speechModel: 'GPT-4',
|
||||||
uploadStatus: '上传中',
|
uploadStatus: '上传中',
|
||||||
syncStatus: '未同步',
|
syncStatus: '未同步',
|
||||||
isMerged: false
|
isMerged: false,
|
||||||
|
audioFileUrl: '',
|
||||||
|
audioFileOriginalName: '',
|
||||||
|
audioFileExtension: '',
|
||||||
|
audioFileSize: 0,
|
||||||
|
remarks: '客户需要更多产品详细信息'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
this.total = this.audioList.length
|
this.total = this.audioList.length
|
||||||
@@ -1031,18 +1395,289 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取意向级别标签类型
|
// 上传音频
|
||||||
getIntentionLevelType(level) {
|
handleUpload() {
|
||||||
const typeMap = {
|
this.uploadForm = {
|
||||||
'A': 'success',
|
recordingName: '',
|
||||||
'B': 'warning',
|
dealershipId: '',
|
||||||
'C': 'info',
|
projectId: '',
|
||||||
'D': 'danger'
|
salesId: '',
|
||||||
|
customerName: '',
|
||||||
|
customerPhone: '',
|
||||||
|
intentionLevel: '',
|
||||||
|
recordingTime: '',
|
||||||
|
audioFile: null,
|
||||||
|
description: ''
|
||||||
|
}
|
||||||
|
this.uploadFileList = []
|
||||||
|
this.uploadDialogVisible = true
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传音频对话框关闭
|
||||||
|
handleUploadDialogClose() {
|
||||||
|
this.$refs.uploadForm?.resetFields()
|
||||||
|
this.uploadFileList = []
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传前检查文件大小和类型
|
||||||
|
beforeAudioUpload(file) {
|
||||||
|
const isLt100M = file.size / 1024 / 1024 < 100
|
||||||
|
if (!isLt100M) {
|
||||||
|
this.$message.error('上传文件大小不能超过 100MB!')
|
||||||
}
|
}
|
||||||
return typeMap[level] || 'info'
|
return isLt100M
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
// 上传成功
|
||||||
|
handleUploadSuccess(response, file, fileList) {
|
||||||
|
if (response.code === 20000) {
|
||||||
|
this.$message.success('音频上传成功')
|
||||||
|
this.uploadForm.audioFile = file
|
||||||
|
this.uploadLoading = false
|
||||||
|
} else {
|
||||||
|
this.$message.error(response.message || '音频上传失败')
|
||||||
|
this.uploadLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传失败
|
||||||
|
handleUploadError(err, file, fileList) {
|
||||||
|
console.error('上传失败:', err)
|
||||||
|
this.$message.error('音频上传失败')
|
||||||
|
this.uploadLoading = false
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传进度
|
||||||
|
handleUploadProgress(event, file, fileList) {
|
||||||
|
this.uploadLoading = true
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传提交
|
||||||
|
handleUploadSubmit() {
|
||||||
|
this.$refs.uploadForm.validate(valid => {
|
||||||
|
if (valid) {
|
||||||
|
const formData = new FormData()
|
||||||
|
Object.keys(this.uploadForm).forEach(key => {
|
||||||
|
if (key !== 'audioFile') {
|
||||||
|
formData.append(key, this.uploadForm[key])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (this.uploadForm.audioFile) {
|
||||||
|
formData.append('audioFile', this.uploadForm.audioFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
addAudio(formData).then(response => {
|
||||||
|
if (response.code === 20000) {
|
||||||
|
this.$message.success('音频添加成功')
|
||||||
|
this.uploadDialogVisible = false
|
||||||
|
this.getList()
|
||||||
|
} else {
|
||||||
|
this.$message.error(response.message || '音频添加失败')
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
this.$message.success('音频添加成功')
|
||||||
|
this.uploadDialogVisible = false
|
||||||
|
this.getList()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 播放音频
|
||||||
|
handlePlay(row) {
|
||||||
|
this.currentAudio = {
|
||||||
|
recordingName: row.recordingName,
|
||||||
|
customerName: row.customerName,
|
||||||
|
customerPhone: row.customerPhone,
|
||||||
|
salesName: row.salesName,
|
||||||
|
recordingTime: row.recordingTime,
|
||||||
|
duration: row.duration,
|
||||||
|
audioUrl: row.audioFileUrl ? this.getFullAudioUrl(row.audioFileUrl) : this.generateAudioUrl(row.id),
|
||||||
|
description: row.remarks || row.description
|
||||||
|
}
|
||||||
|
this.playDialogVisible = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.resetAudioPlayer()
|
||||||
|
// 验证音频URL是否可访问
|
||||||
|
this.validateAudioUrl()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 验证音频URL
|
||||||
|
validateAudioUrl() {
|
||||||
|
if (!this.currentAudio.audioUrl) {
|
||||||
|
this.$message.warning('该录音暂无音频文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查URL格式
|
||||||
|
try {
|
||||||
|
new URL(this.currentAudio.audioUrl)
|
||||||
|
} catch (e) {
|
||||||
|
this.$message.error('音频文件URL格式错误')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生成音频URL
|
||||||
|
generateAudioUrl(audioId) {
|
||||||
|
return process.env.VUE_APP_BASE_API + `/audio/stream/${audioId}`
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取完整音频URL
|
||||||
|
getFullAudioUrl(audioFileUrl) {
|
||||||
|
if (!audioFileUrl) return ''
|
||||||
|
// 如果已经是完整URL,直接返回
|
||||||
|
if (audioFileUrl.startsWith('http')) {
|
||||||
|
return audioFileUrl
|
||||||
|
}
|
||||||
|
// 如果是相对路径,拼接基础URL
|
||||||
|
return process.env.VUE_APP_BASE_API + audioFileUrl
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重置音频播放器
|
||||||
|
resetAudioPlayer() {
|
||||||
|
this.currentTime = 0
|
||||||
|
this.audioDuration = 0
|
||||||
|
this.playbackSpeed = '1.0'
|
||||||
|
this.audioLoading = false
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.currentTime = 0
|
||||||
|
this.$refs.audioPlayer.playbackRate = 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 音频开始加载
|
||||||
|
handleAudioLoadStart() {
|
||||||
|
this.audioLoading = true
|
||||||
|
},
|
||||||
|
|
||||||
|
// 音频可以播放
|
||||||
|
handleAudioCanPlay() {
|
||||||
|
this.audioLoading = false
|
||||||
|
},
|
||||||
|
|
||||||
|
// 播放音频
|
||||||
|
playAudio() {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.play()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 暂停音频
|
||||||
|
pauseAudio() {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.pause()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 重新播放
|
||||||
|
restartAudio() {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.currentTime = 0
|
||||||
|
this.$refs.audioPlayer.play()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 音频加载完成
|
||||||
|
handleAudioLoaded() {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.audioDuration = this.$refs.audioPlayer.duration
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 音频播放进度更新
|
||||||
|
handleTimeUpdate() {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.currentTime = this.$refs.audioPlayer.currentTime
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 音频播放结束
|
||||||
|
handleAudioEnded() {
|
||||||
|
this.currentTime = 0
|
||||||
|
this.$message.info('音频播放完成')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 音频播放错误
|
||||||
|
handleAudioError() {
|
||||||
|
console.error('音频加载失败:', this.currentAudio.audioUrl)
|
||||||
|
this.$message.error('音频加载失败,请检查文件是否存在或网络连接')
|
||||||
|
// 如果是模拟数据,提供提示
|
||||||
|
if (this.currentAudio.audioUrl && (this.currentAudio.audioUrl.includes('soundjay.com') || this.currentAudio.audioUrl.includes('learningcontainer.com'))) {
|
||||||
|
this.$message.warning('当前为演示数据,实际环境中请上传真实的音频文件')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 播放对话框关闭
|
||||||
|
handlePlayDialogClose() {
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
this.$refs.audioPlayer.pause()
|
||||||
|
}
|
||||||
|
this.resetAudioPlayer()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载音频
|
||||||
|
downloadAudio() {
|
||||||
|
if (this.currentAudio.audioUrl) {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = this.currentAudio.audioUrl
|
||||||
|
link.download = this.currentAudio.recordingName + '.mp3'
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
|
formatTime(seconds) {
|
||||||
|
if (!seconds || isNaN(seconds)) return '00:00'
|
||||||
|
const mins = Math.floor(seconds / 60)
|
||||||
|
const secs = Math.floor(seconds % 60)
|
||||||
|
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取音频文件名
|
||||||
|
getAudioFileName(url) {
|
||||||
|
if (!url) return '未知文件'
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url)
|
||||||
|
const pathname = urlObj.pathname
|
||||||
|
const fileName = pathname.split('/').pop()
|
||||||
|
return fileName || '音频文件'
|
||||||
|
} catch (e) {
|
||||||
|
return '音频文件'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载音频文件
|
||||||
|
downloadAudioFile(row) {
|
||||||
|
if (!row.audioFileUrl) {
|
||||||
|
this.$message.warning('该录音暂无音频文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = this.getFullAudioUrl(row.audioFileUrl)
|
||||||
|
link.download = row.audioFileOriginalName || this.getAudioFileName(row.audioFileUrl)
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
this.$message.success('开始下载音频文件')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取意向级别标签类型
|
||||||
|
getIntentionLevelType(level) {
|
||||||
|
const typeMap = {
|
||||||
|
'A': 'success',
|
||||||
|
'B': 'warning',
|
||||||
|
'C': 'info',
|
||||||
|
'D': 'danger'
|
||||||
|
}
|
||||||
|
return typeMap[level] || 'info'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -1072,4 +1707,95 @@ export default {
|
|||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
color: #606266;
|
color: #606266;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 音频播放器样式 */
|
||||||
|
.audio-player-container {
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-info {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-info h3 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-info p {
|
||||||
|
margin: 5px 0;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-player {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-loading {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-loading i {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #409eff;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-loading span {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-controls {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback-speed {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-progress {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-notes {
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f0f9ff;
|
||||||
|
border-radius: 4px;
|
||||||
|
border-left: 4px solid #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-notes h4 {
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-notes p {
|
||||||
|
margin: 0;
|
||||||
|
color: #606266;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user