feat: 优化
This commit is contained in:
@@ -1,112 +0,0 @@
|
||||
<template>
|
||||
<div class="pipeline-progress">
|
||||
<!-- 状态和进度 -->
|
||||
<div class="progress-header">
|
||||
<span class="state-text">{{ stateLabel }}</span>
|
||||
<span v-if="stateDescription" class="state-desc">{{ stateDescription }}</span>
|
||||
</div>
|
||||
|
||||
<a-progress
|
||||
:percent="displayProgress"
|
||||
:status="progressStatus"
|
||||
:stroke-color="progressColor"
|
||||
/>
|
||||
|
||||
<!-- 错误时显示 -->
|
||||
<div v-if="isFailed && error" class="error-section">
|
||||
<span class="error-text">{{ error }}</span>
|
||||
<a-button size="small" @click="handleRetry">重试</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import type { PipelineState } from '@/views/kling/hooks/pipeline/types'
|
||||
import { STATE_CONFIG } from '@/views/kling/hooks/pipeline/states'
|
||||
|
||||
interface Props {
|
||||
state: PipelineState | string
|
||||
progress: number
|
||||
isBusy: boolean
|
||||
isReady: boolean
|
||||
isFailed: boolean
|
||||
isCompleted: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
retry: []
|
||||
reset: []
|
||||
}>()
|
||||
|
||||
const stateConfig = computed(() => STATE_CONFIG[props.state as PipelineState])
|
||||
const stateLabel = computed(() => stateConfig.value.label)
|
||||
const stateDescription = computed(() => stateConfig.value.description)
|
||||
|
||||
const progressStatus = computed(() => {
|
||||
if (props.isFailed) return 'exception'
|
||||
if (props.isCompleted) return 'success'
|
||||
return 'active'
|
||||
})
|
||||
|
||||
const progressColor = computed(() => {
|
||||
if (props.isFailed) return '#ff4d4f'
|
||||
if (props.isCompleted) return '#52c41a'
|
||||
return '#1890ff'
|
||||
})
|
||||
|
||||
const displayProgress = computed(() => {
|
||||
if (props.isFailed) return 0
|
||||
return props.progress
|
||||
})
|
||||
|
||||
function handleRetry() {
|
||||
emit('retry')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.pipeline-progress {
|
||||
padding: 12px 16px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-light);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.progress-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
|
||||
.state-text {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.state-desc {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.error-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 12px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255, 77, 79, 0.1);
|
||||
border-radius: 6px;
|
||||
|
||||
.error-text {
|
||||
flex: 1;
|
||||
color: #ff4d4f;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -18,7 +18,7 @@
|
||||
<a-button
|
||||
class="preview-button"
|
||||
size="small"
|
||||
:disabled="!selectedVoiceId"
|
||||
:disabled="!selectedVoiceId || isPlayerInitializing"
|
||||
:loading="previewLoadingVoiceId === selectedVoiceId"
|
||||
@click="handleSynthesize"
|
||||
>
|
||||
@@ -74,6 +74,7 @@ let player = null
|
||||
const playerContainer = ref(null)
|
||||
const audioUrl = ref('')
|
||||
const currentVoiceName = ref('')
|
||||
const isPlayerInitializing = ref(false)
|
||||
|
||||
// 默认封面图片(音频波形图标)
|
||||
const defaultCover = `data:image/svg+xml;base64,${btoa(`
|
||||
@@ -154,6 +155,8 @@ const handleVoiceChange = (value, option) => {
|
||||
|
||||
const handleSynthesize = () => {
|
||||
if (!selectedVoiceId.value) return
|
||||
// 防止在播放器初始化过程中重复点击
|
||||
if (isPlayerInitializing.value) return
|
||||
|
||||
const voice = userVoiceCards.value.find(v => v.id === selectedVoiceId.value)
|
||||
if (!voice) return
|
||||
@@ -171,23 +174,17 @@ watch(() => props.speechRate, (newRate) => {
|
||||
setSpeechRate(newRate)
|
||||
}, { immediate: true })
|
||||
|
||||
/**
|
||||
* 处理音色
|
||||
*/
|
||||
const handlePlayVoiceSample = (voice) => {
|
||||
currentVoiceName.value = voice.name
|
||||
playVoiceSample(
|
||||
voice,
|
||||
(data) => {
|
||||
const url = data.audioUrl || data.objectUrl
|
||||
if (!url) {
|
||||
console.error('无效的音频数据格式', data)
|
||||
return
|
||||
}
|
||||
if (!url) return
|
||||
initPlayer(url)
|
||||
},
|
||||
(error) => {
|
||||
console.error('音频播放失败', error)
|
||||
// 音频播放失败,静默处理
|
||||
},
|
||||
{ autoPlay: false } // 禁用自动播放,由 APlayer 控制
|
||||
)
|
||||
@@ -197,31 +194,46 @@ const handlePlayVoiceSample = (voice) => {
|
||||
* 初始化 APlayer
|
||||
*/
|
||||
const initPlayer = (url) => {
|
||||
// 防止并发初始化
|
||||
if (isPlayerInitializing.value) {
|
||||
return
|
||||
}
|
||||
|
||||
isPlayerInitializing.value = true
|
||||
destroyPlayer()
|
||||
audioUrl.value = url
|
||||
|
||||
nextTick(() => {
|
||||
player = new APlayer({
|
||||
container: playerContainer.value,
|
||||
autoplay: true,
|
||||
theme: '#3b82f6',
|
||||
volume: 0.7,
|
||||
loop: 'none',
|
||||
audio: [{
|
||||
name: currentVoiceName.value || '语音合成',
|
||||
artist: '合成',
|
||||
url: url,
|
||||
cover: defaultCover
|
||||
}]
|
||||
})
|
||||
try {
|
||||
player = new APlayer({
|
||||
container: playerContainer.value,
|
||||
autoplay: true,
|
||||
theme: '#3b82f6',
|
||||
volume: 0.7,
|
||||
loop: 'none',
|
||||
audio: [{
|
||||
name: currentVoiceName.value || '语音合成',
|
||||
artist: '合成',
|
||||
url: url,
|
||||
cover: defaultCover
|
||||
}]
|
||||
})
|
||||
|
||||
player.on('ended', () => {
|
||||
player.seek(0)
|
||||
})
|
||||
player.on('ended', () => {
|
||||
player.seek(0)
|
||||
})
|
||||
|
||||
player.on('error', (e) => {
|
||||
console.error('APlayer 播放错误:', e)
|
||||
})
|
||||
player.on('error', (e) => {
|
||||
console.error('APlayer 播放错误:', e)
|
||||
})
|
||||
|
||||
player.on('canplay', () => {
|
||||
isPlayerInitializing.value = false
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('APlayer 初始化失败:', e)
|
||||
isPlayerInitializing.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -244,8 +256,11 @@ const downloadAudio = () => {
|
||||
* 销毁播放器
|
||||
*/
|
||||
const destroyPlayer = () => {
|
||||
isPlayerInitializing.value = false
|
||||
if (player) {
|
||||
try {
|
||||
// 先暂停播放,防止销毁过程中出错
|
||||
player.pause()
|
||||
player.destroy()
|
||||
} catch (e) {
|
||||
console.error('销毁播放器失败:', e)
|
||||
@@ -259,8 +274,6 @@ const destroyPlayer = () => {
|
||||
audioUrl.value = ''
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
|
||||
onMounted(async () => {
|
||||
await voiceStore.refresh()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user