feat(录音下载): 重构录音下载功能并添加模态提示
- 将录音下载方法改为异步并使用fetch API - 添加下载开始、成功和失败的模态提示 - 替换alert为统一模态框显示通话原文内容 - 在父组件中添加模态框组件及样式
This commit is contained in:
@@ -116,6 +116,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -143,11 +145,13 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Emits
|
// Emits
|
||||||
const emit = defineEmits(['analyze-sop'])
|
const emit = defineEmits(['analyze-sop', 'show-modal'])
|
||||||
|
|
||||||
// 当前激活的tab
|
// 当前激活的tab
|
||||||
const activeTab = ref('chat')
|
const activeTab = ref('chat')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 聊天消息列表
|
// 聊天消息列表
|
||||||
const chatMessages = computed(() => {
|
const chatMessages = computed(() => {
|
||||||
return props.chatInfo?.messages || []
|
return props.chatInfo?.messages || []
|
||||||
@@ -244,44 +248,69 @@ const callRecords = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 录音下载方法
|
// 录音下载方法
|
||||||
const downloadRecording = (call) => {
|
const downloadRecording = async (call) => {
|
||||||
console.log('下载录音:', call)
|
console.log('下载录音:', call)
|
||||||
|
|
||||||
// 检查是否有录音文件地址
|
// 检查是否有录音文件地址
|
||||||
if (call.record_file_addr) {
|
if (call.record_file_addr) {
|
||||||
|
try {
|
||||||
|
// 显示下载开始提示
|
||||||
|
emit('show-modal', '下载提示', '正在下载录音文件,请稍候...')
|
||||||
|
|
||||||
const recordingUrl = call.record_file_addr
|
const recordingUrl = call.record_file_addr
|
||||||
|
|
||||||
// 从URL中提取文件名
|
// 从URL中提取文件名
|
||||||
const urlParts = recordingUrl.split('/')
|
const urlParts = recordingUrl.split('/')
|
||||||
const fileName = urlParts[urlParts.length - 1]
|
const fileName = urlParts[urlParts.length - 1]
|
||||||
|
|
||||||
|
// 使用fetch获取文件
|
||||||
|
const response = await fetch(recordingUrl, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取文件blob
|
||||||
|
const blob = await response.blob()
|
||||||
|
|
||||||
// 创建下载链接
|
// 创建下载链接
|
||||||
|
const url = window.URL.createObjectURL(blob)
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
link.href = recordingUrl
|
link.href = url
|
||||||
link.download = fileName
|
link.download = fileName
|
||||||
link.target = '_blank'
|
link.style.display = 'none'
|
||||||
|
|
||||||
// 触发下载
|
// 触发下载
|
||||||
document.body.appendChild(link)
|
document.body.appendChild(link)
|
||||||
link.click()
|
link.click()
|
||||||
document.body.removeChild(link)
|
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
document.body.removeChild(link)
|
||||||
|
window.URL.revokeObjectURL(url)
|
||||||
|
|
||||||
|
// 下载成功提示
|
||||||
|
emit('show-modal', '下载成功', '录音文件下载完成!')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('下载录音文件失败:', error)
|
||||||
|
emit('show-modal', '下载失败', '下载录音文件失败,请检查网络连接或文件是否存在')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
alert('该通话记录暂无录音文件')
|
emit('show-modal', '提示', '该通话记录暂无录音文件')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查看原文方法
|
// 查看原文方法
|
||||||
const viewTranscript = async (call) => {
|
const viewTranscript = async (call) => {
|
||||||
// 触发SOP分析
|
const title = '通话原文内容'
|
||||||
alert(call.record_context)
|
const content = call.record_context || '该通话记录暂无原文内容'
|
||||||
|
|
||||||
// 显示通话记录内容
|
emit('show-modal', { title, content })
|
||||||
if (call.record_context) {
|
|
||||||
alert(call.record_context)
|
|
||||||
} else {
|
|
||||||
alert('该通话记录暂无原文内容')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 时间格式化方法
|
// 时间格式化方法
|
||||||
@@ -752,4 +781,7 @@ const formatDateTime = (dateTimeString) => {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 弹框样式
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -76,7 +76,8 @@
|
|||||||
@view-form-data="handleViewFormData"
|
@view-form-data="handleViewFormData"
|
||||||
@view-chat-data="handleViewChatData"
|
@view-chat-data="handleViewChatData"
|
||||||
@view-call-data="handleViewCallData"
|
@view-call-data="handleViewCallData"
|
||||||
@analyze-sop="handleAnalyzeSop" />
|
@analyze-sop="handleAnalyzeSop"
|
||||||
|
@show-modal="handleShowModal" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -126,6 +127,26 @@
|
|||||||
<WeekAnalize :week-data="weekAnalysisData" />
|
<WeekAnalize :week-data="weekAnalysisData" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- 自定义弹框 -->
|
||||||
|
<div v-if="showModal" class="modal-overlay" @click="closeModal">
|
||||||
|
<div class="modal-container" @click.stop>
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title">{{ modalTitle }}</h3>
|
||||||
|
<button class="modal-close-btn" @click="closeModal">
|
||||||
|
<i class="icon-close">×</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="modal-content">
|
||||||
|
{{ modalContent }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="modal-btn modal-btn-primary" @click="closeModal">确定</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -247,6 +268,11 @@ const statisticsData = reactive({
|
|||||||
// 客户迫切解决的问题数据
|
// 客户迫切解决的问题数据
|
||||||
const urgentProblemData = ref([]);
|
const urgentProblemData = ref([]);
|
||||||
|
|
||||||
|
// 弹框状态
|
||||||
|
const showModal = ref(false)
|
||||||
|
const modalContent = ref('')
|
||||||
|
const modalTitle = ref('')
|
||||||
|
|
||||||
// 时间线数据
|
// 时间线数据
|
||||||
const timelineData = ref({});
|
const timelineData = ref({});
|
||||||
|
|
||||||
@@ -810,6 +836,20 @@ const handleViewCallData = (contact) => {
|
|||||||
// TODO: 实现通话录音查看逻辑
|
// TODO: 实现通话录音查看逻辑
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理弹框显示事件
|
||||||
|
const handleShowModal = (title, content) => {
|
||||||
|
modalTitle.value = title
|
||||||
|
modalContent.value = content
|
||||||
|
showModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹框
|
||||||
|
const closeModal = () => {
|
||||||
|
showModal.value = false
|
||||||
|
modalContent.value = ''
|
||||||
|
modalTitle.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
// // 处理SOP分析事件
|
// // 处理SOP分析事件
|
||||||
// const handleAnalyzeSop = (analyzeData) => {
|
// const handleAnalyzeSop = (analyzeData) => {
|
||||||
// console.log('handleAnalyzeSop', analyzeData)
|
// console.log('handleAnalyzeSop', analyzeData)
|
||||||
@@ -1719,4 +1759,169 @@ $primary: #3b82f6;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 弹框样式
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
animation: fadeIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
max-width: 600px;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: slideIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
background: #f3f4f6;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #e5e7eb;
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-close {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #6b7280;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 5px;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #374151;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
padding: 16px;
|
||||||
|
margin-left: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
// border: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 24px;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
background: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&.modal-btn-primary {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: #ffffff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-20px) scale(0.95);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹框响应式样式
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.modal-container {
|
||||||
|
width: 95%;
|
||||||
|
max-height: 85vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
max-height: 55vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 12px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user