Files
DJKB/my-vue-app/src/views/secondTop/components/GoodMusic.vue
lbw_9527443 288a525537 refactor(ui): 调整多个组件的内边距和样式一致性
fix(GoodMusic): 修复录音列表数据结构和显示问题
style: 统一多个组件的头部内边距为10px 20px 10px
chore: 切换API基础路径为生产环境
2025-10-22 18:02:55 +08:00

1145 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- 录音管理区域 -->
<div class="charts-section">
<div class="chart-container">
<div class="chart-header">
<h3>优秀录音</h3>
</div>
<div class="chart-content">
<div class="recording-section">
<!-- 录音文件列表 -->
<div v-if="!showTranscriptView">
<div class="recording-list" v-if="recordings.length > 0">
<div
v-for="(recording, index) in recordings"
:key="index"
class="recording-item"
:class="{ active: selectedRecording === index }"
@click="selectRecording(index)"
>
<span class="recording-index">{{ recording.score}}</span>
<div class="recording-info">
<div class="recording-name" :title="recording.name">{{ recording.name.length > 10 ? recording.name.substring(0, 10) + '...' : recording.name }}</div>
<div class="recording-meta">
<span class="upload-time">{{ recording.uploadTime }}</span>
</div>
</div>
<div class="recording-actions">
<button
class="action-btn download-btn"
@click.stop="downloadRecording(index)"
:disabled="!recording.url"
>
下载录音
</button>
<button
class="action-btn convert-btn"
@click.stop="convertToText(index)"
:disabled="recording.isConverting"
>
<span v-if="!recording.isConverting">转文字</span>
<span v-else class="converting-text">
<span class="dot-animation">转换中</span>
</span>
</button>
</div>
</div>
</div>
<!-- 空状态 -->
<div class="empty-state" v-if="recordings.length === 0">
<i class="el-icon-microphone"></i>
<p>暂无录音文件请上传录音文件</p>
</div>
</div>
<!-- 转换结果 -->
<div class="conversion-result full-page" v-if="showTranscriptView">
<div class="result-header">
<button class="back-btn" @click="backToRecordings">
<i class="el-icon-arrow-left"></i>
返回
</button>
<div class="header-actions">
<!-- 视图切换按钮 -->
<div class="view-toggle" v-if="currentTranscript && !isConverting">
<button
class="toggle-btn"
:class="{ active: currentViewType === 'transcript' }"
@click="switchViewType('transcript')"
>
查看原文
</button>
<button
class="toggle-btn"
:class="{ active: currentViewType === 'analysis' }"
@click="switchViewType('analysis')"
>
录音分析
</button>
</div>
<button class="expand-btn" @click="showExpandDialog" v-if="(currentTranscript && currentViewType === 'transcript') || (analysisResult && currentViewType === 'analysis')">
<i class="el-icon-full-screen"></i>
展开
</button>
<button class="copy-btn" @click="copyText" v-if="currentTranscript && currentViewType === 'transcript'">
<i class="el-icon-document-copy"></i>
复制
</button>
<button class="copy-btn" @click="copyAnalysisText" v-if="analysisResult && currentViewType === 'analysis'">
<i class="el-icon-document-copy"></i>
复制
</button>
</div>
</div>
<!-- 原文显示 -->
<div class="transcript-text" v-if="currentTranscript && currentViewType === 'transcript'">
{{ currentTranscript }}
</div>
<!-- 分析结果显示 -->
<div class="analysis-result" v-if="currentViewType === 'analysis'">
<div v-if="isAnalyzing" class="analysis-loading">
<div class="loading-animation">
<span></span>
<span></span>
<span></span>
</div>
<p>正在进行录音分析请稍候...</p>
</div>
<div v-else-if="analysisResult" class="analysis-content" v-html="formattedAnalysisResult"></div>
<div v-else class="no-analysis">
<p>暂无分析结果</p>
</div>
</div>
<!-- 转换加载状态 -->
<div class="transcript-loading" v-if="isConverting">
<div class="loading-animation">
<span></span>
<span></span>
<span></span>
</div>
<p>正在将录音转换为文字请稍候...</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 展开查看弹框 -->
<div class="dialog-overlay" v-if="showDialog" @click="closeDialog">
<div class="dialog-content" @click.stop>
<div class="dialog-header">
<h3>{{ currentViewType === 'transcript' ? '转换文本内容' : '录音分析结果' }}</h3>
<button class="close-btn" @click="closeDialog">
<i class="el-icon-close"></i>
</button>
</div>
<div class="dialog-body">
<!-- 转换文本内容 -->
<div class="expanded-transcript" v-if="currentViewType === 'transcript'">
{{ currentTranscript }}
</div>
<!-- 分析结果内容 -->
<div class="expanded-analysis" v-if="currentViewType === 'analysis'">
<div v-if="analysisResult" class="analysis-content" v-html="formattedAnalysisResult"></div>
<div v-else class="no-analysis">
<p>暂无分析结果</p>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="dialog-copy-btn" @click="currentViewType === 'transcript' ? copyText() : copyAnalysisText()">
<i class="el-icon-document-copy"></i>
复制全部内容
</button>
<button class="dialog-close-btn" @click="closeDialog">
关闭
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
import { SimpleChatService } from '@/utils/ChatService.js'
import MarkdownIt from 'markdown-it'
// Props定义
const props = defineProps({
qualityCalls: {
type: Array,
default: () => []
}
})
// 响应式数据
const staticRecordings = ref([
{
id: 1,
name: '常家硕-张三丰-亮剑二部-20分钟通话-25-07-16_18-23-04-44196-215.mp3',
size: 2048576, // 2MB
duration: '00:03:45',
date: '2024-01-15',
url: '/recordings/sample_call_1.mp3',
transcription: null
}
])
const selectedRecording = ref(null)
const currentAudio = ref(null)
const showTranscriptView = ref(false)
const isConverting = ref(false)
const currentTranscript = ref(null)
const showDialog = ref(false)
// 录音分析相关
const showAnalysisView = ref(false)
const isAnalyzing = ref(false)
const analysisResult = ref('')
const currentViewType = ref('transcript') // 'transcript' 或 'analysis'
// Dify API配置
const DIFY_API_KEY_02 = 'app-h4uBo5kOGoiYhjuBF1AHZi8b' // 通话录音分析
const chatService_02 = ref(null)
const md = ref(null)
// 初始化服务
onMounted(() => {
chatService_02.value = new SimpleChatService(DIFY_API_KEY_02)
md.value = new MarkdownIt({
html: true,
linkify: true,
typographer: true
})
})
// 计算属性
// 处理传入的录音数据
const recordings = computed(() => {
if (!props.qualityCalls ) {
return staticRecordings.value;
}
console.log(66666,props.qualityCalls);
const recordingsList = [];
props.qualityCalls.forEach((record, index) => {
recordingsList.push({
id: recordingsList.length + 1,
name: record.record_name ? record.record_name : `${record.sale_name}-录音-${index + 1}`,
date: new Date().toISOString().split('T')[0],
url: record.record_file_addr,
transcription: record.record_context || null,
score: record.record_score,
sop: record.record_report,
sale_name: record.record_name,
size: 2048576, // 默认文件大小 2MB
uploadTime: record.created_at,
});
});
return recordingsList;
})
// 格式化分析结果
const formattedAnalysisResult = computed(() => {
if (!analysisResult.value) return ''
return md.value.render(analysisResult.value)
})
// 生命周期钩子
onBeforeUnmount(() => {
if (currentAudio.value) {
currentAudio.value.pause()
currentAudio.value = null
}
})
// 方法定义
// 录音文件选择
const handleFileSelect = (event) => {
const file = event.target.files[0]
if (file) {
const recording = {
name: file.name,
size: file.size,
uploadTime: new Date().toLocaleString(),
url: URL.createObjectURL(file),
isPlaying: false,
isConverting: false,
transcript: null
}
staticRecordings.value.push(recording)
// 清空input以便重复选择同一文件
event.target.value = ''
}
}
// 选择录音
const selectRecording = (index) => {
selectedRecording.value = index
}
// 播放/暂停录音
const togglePlay = (index) => {
const recording = recordings.value[index]
// 停止当前播放的音频
if (currentAudio.value) {
currentAudio.value.pause()
recordings.value.forEach(r => r.isPlaying = false)
}
if (!recording.isPlaying) {
currentAudio.value = new Audio(recording.url)
currentAudio.value.play()
recording.isPlaying = true
currentAudio.value.onended = () => {
recording.isPlaying = false
currentAudio.value = null
}
}
}
// 转换为文字
const convertToText = async (index) => {
const recording = recordings.value[index]
selectedRecording.value = index
showTranscriptView.value = true
isConverting.value = true
currentTranscript.value = null
currentViewType.value = 'transcript'
try {
// 模拟转换过程
await new Promise(resolve => setTimeout(resolve, 1000))
// 使用从API获取的transcription数据
if (recording.transcription) {
recording.transcript = recording.transcription
currentTranscript.value = recording.transcription
} else {
// 如果没有transcription数据显示提示信息
recording.transcript = '暂无转换文本数据'
currentTranscript.value = '暂无转换文本数据'
}
// 添加转换完成的动画效果
const resultElement = document.querySelector('.conversion-result')
if (resultElement) {
resultElement.classList.add('show-result')
setTimeout(() => {
resultElement.classList.remove('show-result')
}, 1000)
}
} catch (error) {
console.error('转换失败:', error)
alert('转换失败,请重试')
showTranscriptView.value = false
} finally {
isConverting.value = false
}
}
// 开始通话录音分析
const startRecordingAnalysis = async (recording) => {
isAnalyzing.value = true
try {
// 使用从API获取的sop数据作为录音分析结果
if (recording.sop) {
analysisResult.value = recording.sop
} else {
analysisResult.value = '暂无录音分析数据'
}
// 模拟分析过程
await new Promise(resolve => setTimeout(resolve, 500))
console.log('录音分析完成')
} catch (error) {
console.error('录音分析失败:', error)
analysisResult.value = '录音分析失败,请重试。'
} finally {
isAnalyzing.value = false
}
}
// 切换视图类型
const switchViewType = (type) => {
currentViewType.value = type
// 如果切换到录音分析视图,且还没有分析结果,则开始分析
if (type === 'analysis' && !analysisResult.value && selectedRecording.value !== null) {
const recording = recordings.value[selectedRecording.value]
startRecordingAnalysis(recording)
}
}
// 返回录音列表
const backToRecordings = () => {
showTranscriptView.value = false
currentTranscript.value = null
analysisResult.value = ''
currentViewType.value = 'transcript'
isAnalyzing.value = false
}
// 复制文本
const copyText = async () => {
if (currentTranscript.value) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(currentTranscript.value)
alert('文本已复制到剪贴板')
} else {
// 降级方案:使用传统的复制方法
const textArea = document.createElement('textarea')
textArea.value = currentTranscript.value
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
alert('文本已复制到剪贴板')
}
} catch (error) {
console.error('复制失败:', error)
alert('复制失败,请手动复制')
}
}
}
// 复制分析结果
const copyAnalysisText = async () => {
if (analysisResult.value) {
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(analysisResult.value)
alert('分析结果已复制到剪贴板')
} else {
// 降级方案:使用传统的复制方法
const textArea = document.createElement('textarea')
textArea.value = analysisResult.value
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
alert('分析结果已复制到剪贴板')
}
} catch (error) {
console.error('复制失败:', error)
alert('复制失败,请手动复制')
}
}
}
// 显示展开弹框
const showExpandDialog = () => {
showDialog.value = true
}
// 关闭弹框
const closeDialog = () => {
showDialog.value = false
}
// 格式化文件大小
const formatFileSize = (bytes) => {
if (bytes === 0) return '0 Bytes'
const k = 1024
const sizes = ['Bytes', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
const triggerFileUpload = () => {
const fileInput = document.createElement('input')
fileInput.type = 'file'
fileInput.accept = 'audio/*'
fileInput.style.display = 'none'
fileInput.addEventListener('change', handleFileSelect)
document.body.appendChild(fileInput)
fileInput.click()
document.body.removeChild(fileInput)
}
const downloadRecording = (index) => {
const recording = recordings.value[index]
if (recording && recording.url) {
const link = document.createElement('a')
link.href = recording.url
link.download = recording.name
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
}
</script>
<style scoped>
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.dashboard-header h2 {
margin: 0;
color: #303133;
font-size: 24px;
font-weight: 600;
}
.charts-section {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
.chart-container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
height: 400px;
}
.chart-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px 0;
border-bottom: 1px solid #ebeef5;
}
.chart-header h3 {
margin: 0 0 16px 0;
color: #303133;
font-size: 18px;
font-weight: 600;
}
.upload-icon-btn {
background: #409eff;
border: none;
border-radius: 6px;
padding: 8px;
cursor: pointer;
color: white;
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
.upload-icon-btn:hover {
background: #337ecc;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
}
.upload-icon-btn:active {
transform: translateY(0);
}
.chart-content {
padding: 10px;
}
.recording-section {
width: 100%;
min-height: 200px;
max-height: 300px;
overflow-y: auto;
}
.recording-list {
margin-bottom: 20px;
max-height: 400px;
}
.recording-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
border: 1px solid #ebeef5;
border-radius: 6px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s ease;
}
.recording-item:hover {
border-color: #c6e2ff;
background: #f5f7fa;
}
.recording-item.active {
border-color: #409eff;
background: #ecf5ff;
}
.recording-info {
flex: 1;
}
.recording-name {
font-weight: 500;
color: #303133;
margin-bottom: 4px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
display: inline-block;
}
.recording-index {
/* 基础分数样式 */
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
background-color: #e9ecef;
color: #495057;
margin-right: 10px;
}
/* 第一名样式 */
.recording-item:first-child .recording-index {
background: linear-gradient(135deg, #FFD700, #FFA500);
color: #fff;
box-shadow: 0 2px 4px rgba(255, 215, 0, 0.3);
}
/* 第二名样式 */
.recording-item:nth-child(2) .recording-index {
background: linear-gradient(135deg, #C0C0C0, #A9A9A9);
color: #fff;
box-shadow: 0 2px 4px rgba(192, 192, 192, 0.3);
}
/* 第三名样式 */
.recording-item:nth-child(3) .recording-index {
background: linear-gradient(135deg, #CD7F32, #A0522D);
color: #fff;
box-shadow: 0 2px 4px rgba(205, 127, 50, 0.3);
}
.recording-meta {
display: flex;
gap: 12px;
font-size: 12px;
color: #909399;
}
.recording-actions {
display: flex;
gap: 12px;
align-items: center;
}
.action-btn {
padding: 8px 16px;
height: 36px;
border: none;
border-radius: 6px;
background: #f8f9fa;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
font-size: 13px;
font-weight: 500;
color: #495057;
min-width: 80px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.action-btn:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.action-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.download-btn {
background: linear-gradient(135deg, #28a745, #20c997);
color: white;
}
.download-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #218838, #1ea085);
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(40, 167, 69, 0.3);
}
.convert-btn {
background: linear-gradient(135deg, #007bff, #6610f2);
color: white;
}
.convert-btn:hover:not(:disabled) {
background: linear-gradient(135deg, #0056b3, #520dc2);
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(0, 123, 255, 0.3);
}
.converting-text {
display: inline-flex;
align-items: center;
justify-content: center;
}
.dot-animation {
position: relative;
}
.dot-animation::after {
content: "...";
position: absolute;
overflow: hidden;
display: inline-block;
vertical-align: bottom;
animation: dots 2s infinite;
width: 0;
margin-left: 2px;
}
@keyframes dots {
0% { width: 0; }
33% { width: 0.5em; }
66% { width: 1em; }
100% { width: 1.5em; }
}
.conversion-result {
border: 1px solid #ebeef5;
border-radius: 6px;
overflow: hidden;
transition: all 0.5s ease;
}
.conversion-result.full-page {
height: 100%;
display: flex;
flex-direction: column;
animation: fadeIn 0.3s ease;
}
.show-result {
animation: pulse 1s ease;
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 0 rgba(0, 123, 255, 0); }
50% { transform: scale(1.03); box-shadow: 0 0 10px rgba(0, 123, 255, 0.3); }
100% { transform: scale(1); box-shadow: 0 0 0 rgba(0, 123, 255, 0); }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
background: #f8f9fa;
border-bottom: 1px solid #ebeef5;
flex-shrink: 0;
}
.result-header h4 {
margin: 0;
color: #303133;
font-size: 16px;
font-weight: 600;
}
.header-actions {
display: flex;
gap: 12px;
align-items: center;
}
.view-toggle {
display: flex;
background: #e9ecef;
border-radius: 6px;
overflow: hidden;
margin-right: 12px;
}
.toggle-btn {
padding: 8px 16px;
border: none;
background: transparent;
cursor: pointer;
font-size: 13px;
font-weight: 500;
color: #6c757d;
transition: all 0.2s ease;
}
.toggle-btn.active {
background: #007bff;
color: white;
}
.toggle-btn:hover:not(.active) {
background: #dee2e6;
}
.back-btn, .expand-btn, .copy-btn {
padding: 8px 12px;
border: none;
border-radius: 6px;
background: #6c757d;
color: white;
cursor: pointer;
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s ease;
}
.back-btn:hover, .expand-btn:hover, .copy-btn:hover {
background: #5a6268;
transform: translateY(-1px);
}
.transcript-text {
padding: 20px;
line-height: 1.6;
color: #495057;
background: white;
flex: 1;
overflow-y: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
.analysis-result {
padding: 20px;
flex: 1;
overflow-y: auto;
background: white;
}
.analysis-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
color: #6c757d;
}
.loading-animation {
display: flex;
gap: 4px;
margin-bottom: 16px;
}
.loading-animation span {
width: 8px;
height: 8px;
border-radius: 50%;
background: #007bff;
animation: bounce 1.4s ease-in-out infinite both;
}
.loading-animation span:nth-child(1) { animation-delay: -0.32s; }
.loading-animation span:nth-child(2) { animation-delay: -0.16s; }
@keyframes bounce {
0%, 80%, 100% { transform: scale(0); }
40% { transform: scale(1); }
}
.analysis-content {
line-height: 1.6;
color: #495057;
}
.analysis-content h1, .analysis-content h2, .analysis-content h3 {
color: #343a40;
margin-top: 24px;
margin-bottom: 12px;
}
.analysis-content h1 { font-size: 24px; }
.analysis-content h2 { font-size: 20px; }
.analysis-content h3 { font-size: 18px; }
.analysis-content p {
margin-bottom: 12px;
}
.analysis-content ul, .analysis-content ol {
margin-bottom: 12px;
padding-left: 24px;
}
.analysis-content li {
margin-bottom: 6px;
}
.analysis-content strong {
color: #343a40;
font-weight: 600;
}
.analysis-content code {
background: #f8f9fa;
padding: 2px 6px;
border-radius: 4px;
font-family: 'Courier New', monospace;
font-size: 14px;
}
.no-analysis {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: #6c757d;
font-style: italic;
}
.transcript-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 200px;
color: #6c757d;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 300px;
color: #909399;
font-size: 16px;
}
.empty-state i {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.dialog-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;
animation: fadeIn 0.3s ease;
}
.dialog-content {
background: white;
border-radius: 8px;
width: 90%;
max-width: 800px;
max-height: 80vh;
display: flex;
flex-direction: column;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #ebeef5;
}
.dialog-header h3 {
margin: 0;
color: #303133;
font-size: 18px;
font-weight: 600;
}
.close-btn {
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #909399;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
}
.close-btn:hover {
background: #f5f7fa;
color: #606266;
}
.dialog-body {
padding: 20px;
flex: 1;
overflow-y: auto;
}
.expanded-transcript, .expanded-analysis {
line-height: 1.6;
color: #495057;
white-space: pre-wrap;
word-wrap: break-word;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 20px;
border-top: 1px solid #ebeef5;
}
.dialog-copy-btn, .dialog-close-btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.dialog-copy-btn {
background: #007bff;
color: white;
display: flex;
align-items: center;
gap: 6px;
}
.dialog-copy-btn:hover {
background: #0056b3;
}
.dialog-close-btn {
background: #6c757d;
color: white;
}
.dialog-close-btn:hover {
background: #5a6268;
}
/* 响应式设计 */
@media (max-width: 768px) {
.recording-item {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.recording-actions {
width: 100%;
justify-content: flex-end;
}
.result-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.header-actions {
width: 100%;
justify-content: space-between;
}
.view-toggle {
margin-right: 0;
}
.dialog-content {
width: 95%;
max-height: 90vh;
}
.dialog-footer {
flex-direction: column;
}
.dialog-copy-btn, .dialog-close-btn {
width: 100%;
justify-content: center;
}
}
/* 滚动条样式 */
.recording-list::-webkit-scrollbar,
.recording-section::-webkit-scrollbar,
.transcript-text::-webkit-scrollbar,
.analysis-result::-webkit-scrollbar,
.dialog-body::-webkit-scrollbar {
width: 6px;
}
.recording-list::-webkit-scrollbar-track,
.recording-section::-webkit-scrollbar-track,
.transcript-text::-webkit-scrollbar-track,
.analysis-result::-webkit-scrollbar-track,
.dialog-body::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.recording-list::-webkit-scrollbar-thumb,
.recording-section::-webkit-scrollbar-thumb,
.transcript-text::-webkit-scrollbar-thumb,
.analysis-result::-webkit-scrollbar-thumb,
.dialog-body::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.recording-list::-webkit-scrollbar-thumb:hover,
.recording-section::-webkit-scrollbar-thumb:hover,
.transcript-text::-webkit-scrollbar-thumb:hover,
.analysis-result::-webkit-scrollbar-thumb:hover,
.dialog-body::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
</style>