Files
sionrui/frontend/app/web-gold/src/views/dh/VoiceCopy.vue
2026-04-05 17:27:31 +08:00

285 lines
8.4 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.
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { toast } from 'vue-sonner'
import { Icon } from '@iconify/vue'
import dayjs from 'dayjs'
import { VoiceService } from '@/api/voice'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Spinner } from '@/components/ui/spinner'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
import TaskPageLayout from '@/views/system/task-management/components/TaskPageLayout.vue'
import VoiceCopyDialog from './VoiceCopyDialog.vue'
// ========== 响应式数据 ==========
const loading = ref(false)
const voiceList = ref([])
const deleteDialogVisible = ref(false)
const deleteTarget = ref(null)
const dialogVisible = ref(false)
const dialogMode = ref('create')
const dialogRecord = ref(null)
const audioPlayer = ref(null)
const searchParams = reactive({
name: '',
pageNo: 1,
pageSize: 10
})
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
})
// ========== 工具函数 ==========
const formatDateTime = (value) => {
if (!value) return '-'
return dayjs(value).format('YYYY-MM-DD HH:mm:ss')
}
// ========== 数据加载 ==========
const loadVoiceList = async () => {
loading.value = true
try {
const res = await VoiceService.getPage({
pageNo: pagination.current,
pageSize: pagination.pageSize,
name: searchParams.name || undefined
})
if (res.code !== 0) {
toast.error(res.msg || '加载失败')
return
}
voiceList.value = res.data.list || []
pagination.total = res.data.total || 0
} catch (error) {
console.error('加载配音列表失败:', error)
toast.error('加载失败,请稍后重试')
} finally {
loading.value = false
}
}
// ========== 搜索和分页 ==========
function handleSearch() {
pagination.current = 1
loadVoiceList()
}
function handleReset() {
searchParams.name = ''
pagination.current = 1
loadVoiceList()
}
function handlePageChange(page) {
pagination.current = page
loadVoiceList()
}
// ========== CRUD 操作 ==========
function handleCreate() {
dialogMode.value = 'create'
dialogRecord.value = null
dialogVisible.value = true
}
function handleEdit(record) {
dialogMode.value = 'edit'
dialogRecord.value = record
dialogVisible.value = true
}
function handleDelete(record) {
deleteTarget.value = record
deleteDialogVisible.value = true
}
async function confirmDelete() {
if (!deleteTarget.value) return
try {
const res = await VoiceService.delete(deleteTarget.value.id)
if (res.code !== 0) {
toast.error(res.msg || '删除失败')
return
}
toast.success('删除成功')
loadVoiceList()
} catch (error) {
console.error('删除失败:', error)
toast.error('删除失败,请稍后重试')
} finally {
deleteDialogVisible.value = false
deleteTarget.value = null
}
}
// ========== 音频播放 ==========
function handlePlayAudio(record) {
if (record.fileUrl && audioPlayer.value) {
audioPlayer.value.src = record.fileUrl
audioPlayer.value.play()
} else {
toast.warning('音频文件不存在')
}
}
// ========== 生命周期 ==========
onMounted(() => loadVoiceList())
</script>
<template>
<div class="p-4">
<TaskPageLayout
:loading="loading"
:current="pagination.current"
:page-size="pagination.pageSize"
:total="pagination.total"
@page-change="handlePageChange"
>
<!-- 筛选条件 -->
<template #filters>
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-3">
<Button @click="handleCreate">
新建配音
</Button>
</div>
<div class="flex items-center gap-3">
<Input
v-model="searchParams.name"
placeholder="搜索配音名称..."
class="w-64"
@keypress.enter="handleSearch"
>
<template #prefix>
<Icon icon="lucide:search" class="size-4 text-muted-foreground" />
</template>
</Input>
<Button variant="outline" @click="handleSearch">搜索</Button>
<Button variant="ghost" @click="handleReset">重置</Button>
</div>
</div>
</template>
<!-- 表格 -->
<template #table>
<Table>
<TableHeader>
<TableRow class="hover:bg-transparent">
<TableHead class="w-44">配音名称</TableHead>
<TableHead>备注</TableHead>
<TableHead class="w-44">创建时间</TableHead>
<TableHead class="w-40 text-center">操作</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<!-- 空状态 -->
<TableRow v-if="voiceList.length === 0 && !loading">
<TableCell :colspan="4" class="h-48 text-center">
<div class="flex flex-col items-center gap-3 text-muted-foreground">
<Icon icon="lucide:mic-off" class="size-10 opacity-50" />
<span>暂无配音数据</span>
</div>
</TableCell>
</TableRow>
<!-- 数据列表 -->
<TableRow v-else v-for="record in voiceList" :key="record.id" class="group">
<TableCell>
<span class="font-medium">{{ record.name || '未命名' }}</span>
</TableCell>
<TableCell>
<Tooltip v-if="record.note" :delay-duration="200">
<TooltipTrigger as-child>
<span class="text-muted-foreground line-clamp-1">{{ record.note }}</span>
</TooltipTrigger>
<TooltipContent class="max-w-xs">
{{ record.note }}
</TooltipContent>
</Tooltip>
<span v-else class="text-muted-foreground">-</span>
</TableCell>
<TableCell class="text-muted-foreground">
{{ formatDateTime(record.createTime) }}
</TableCell>
<TableCell class="text-center">
<div class="flex items-center justify-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
<Button variant="ghost" size="sm" class="h-8 px-2" @click="handlePlayAudio(record)">
<Icon icon="lucide:play" class="size-4 mr-1" />
播放
</Button>
<Button variant="ghost" size="sm" class="h-8 px-2" @click="handleEdit(record)">
<Icon icon="lucide:pencil" class="size-4 mr-1" />
编辑
</Button>
<Button variant="ghost" size="sm" class="h-8 px-2 text-destructive hover:text-destructive" @click="handleDelete(record)">
<Icon icon="lucide:trash-2" class="size-4 mr-1" />
删除
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
</template>
<!-- 弹窗 -->
<template #modals>
<!-- 新建/编辑弹窗 -->
<VoiceCopyDialog
v-model:open="dialogVisible"
:mode="dialogMode"
:record="dialogRecord"
@success="loadVoiceList"
/>
<!-- 删除确认 -->
<AlertDialog :open="deleteDialogVisible" @update:open="(v) => deleteDialogVisible = v">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>确认删除</AlertDialogTitle>
<AlertDialogDescription>
确定要删除配音{{ deleteTarget?.name }}此操作不可恢复
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>取消</AlertDialogCancel>
<AlertDialogAction
class="bg-destructive text-destructive-foreground hover:bg-destructive/90"
@click="confirmDelete"
>
删除
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</template>
</TaskPageLayout>
</div>
<audio ref="audioPlayer" class="hidden" />
</template>