feat(录音下载): 重构录音下载功能并添加模态提示

- 将录音下载方法改为异步并使用fetch API
- 添加下载开始、成功和失败的模态提示
- 替换alert为统一模态框显示通话原文内容
- 在父组件中添加模态框组件及样式
This commit is contained in:
2025-09-15 11:25:03 +08:00
parent 1d63829ed6
commit 62a4eb0319
2 changed files with 266 additions and 29 deletions

View File

@@ -116,6 +116,8 @@
</div>
</div>
</div>
</template>
<script setup>
@@ -143,11 +145,13 @@ const props = defineProps({
})
// Emits
const emit = defineEmits(['analyze-sop'])
const emit = defineEmits(['analyze-sop', 'show-modal'])
// 当前激活的tab
const activeTab = ref('chat')
// 聊天消息列表
const chatMessages = computed(() => {
return props.chatInfo?.messages || []
@@ -244,44 +248,69 @@ const callRecords = computed(() => {
})
// 录音下载方法
const downloadRecording = (call) => {
const downloadRecording = async (call) => {
console.log('下载录音:', call)
// 检查是否有录音文件地址
if (call.record_file_addr) {
const recordingUrl = call.record_file_addr
// 从URL中提取文件名
const urlParts = recordingUrl.split('/')
const fileName = urlParts[urlParts.length - 1]
// 创建下载链接
const link = document.createElement('a')
link.href = recordingUrl
link.download = fileName
link.target = '_blank'
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
try {
// 显示下载开始提示
emit('show-modal', '下载提示', '正在下载录音文件,请稍候...')
const recordingUrl = call.record_file_addr
// 从URL中提取文件名
const urlParts = recordingUrl.split('/')
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')
link.href = url
link.download = fileName
link.style.display = 'none'
// 触发下载
document.body.appendChild(link)
link.click()
// 清理
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// 下载成功提示
emit('show-modal', '下载成功', '录音文件下载完成!')
} catch (error) {
console.error('下载录音文件失败:', error)
emit('show-modal', '下载失败', '下载录音文件失败,请检查网络连接或文件是否存在')
}
} else {
alert('该通话记录暂无录音文件')
emit('show-modal', '提示', '该通话记录暂无录音文件')
}
}
// 查看原文方法
const viewTranscript = async (call) => {
// 触发SOP分析
alert(call.record_context)
const title = '通话原文内容'
const content = call.record_context || '该通话记录暂无原文内容'
// 显示通话记录内容
if (call.record_context) {
alert(call.record_context)
} else {
alert('该通话记录暂无原文内容')
}
emit('show-modal', { title, content })
}
// 时间格式化方法
@@ -752,4 +781,7 @@ const formatDateTime = (dateTimeString) => {
font-size: 13px;
}
}
// 弹框样式
</style>

View File

@@ -76,7 +76,8 @@
@view-form-data="handleViewFormData"
@view-chat-data="handleViewChatData"
@view-call-data="handleViewCallData"
@analyze-sop="handleAnalyzeSop" />
@analyze-sop="handleAnalyzeSop"
@show-modal="handleShowModal" />
</div>
</section>
@@ -126,6 +127,26 @@
<WeekAnalize :week-data="weekAnalysisData" />
</div>
</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>
</template>
@@ -247,6 +268,11 @@ const statisticsData = reactive({
// 客户迫切解决的问题数据
const urgentProblemData = ref([]);
// 弹框状态
const showModal = ref(false)
const modalContent = ref('')
const modalTitle = ref('')
// 时间线数据
const timelineData = ref({});
@@ -810,6 +836,20 @@ const handleViewCallData = (contact) => {
// TODO: 实现通话录音查看逻辑
};
// 处理弹框显示事件
const handleShowModal = (title, content) => {
modalTitle.value = title
modalContent.value = content
showModal.value = true
}
// 关闭弹框
const closeModal = () => {
showModal.value = false
modalContent.value = ''
modalTitle.value = ''
}
// // 处理SOP分析事件
// const 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>