🎨chore: 使用 oxlint, oxfmt&格式化代码

This commit is contained in:
2026-02-08 21:16:25 +08:00
parent 9d35c6a9d8
commit 3a801ba016
78 changed files with 3367 additions and 1468 deletions

View File

@@ -31,7 +31,9 @@ const selectedBackgroundFile = ref<File | null>(null)
const selectedBackgroundPreview = ref<string>('')
const isCombinatorLoading = ref(false)
const compositingProgress = ref(0)
const compositingPhase = ref<'loading' | 'analyzing' | 'preparing' | 'executing' | 'finalizing'>('loading')
const compositingPhase = ref<
'loading' | 'analyzing' | 'preparing' | 'executing' | 'finalizing'
>('loading')
const combinatorError = ref<string>('')
const fileInputRef = ref<HTMLInputElement | null>(null)
const compositedVideoBlob = ref<Blob | null>(null)
@@ -39,11 +41,11 @@ const compositedVideoBlob = ref<Blob | null>(null)
// 阶段显示文本
const phaseText = computed(() => {
const phaseMap: Record<typeof compositingPhase.value, string> = {
'loading': '加载资源...',
'analyzing': '分析图片...',
'preparing': '准备合成...',
'executing': '合成中...',
'finalizing': '完成处理...',
loading: '加载资源...',
analyzing: '分析图片...',
preparing: '准备合成...',
executing: '合成中...',
finalizing: '完成处理...',
}
return phaseMap[compositingPhase.value]
})
@@ -51,9 +53,9 @@ const phaseText = computed(() => {
const handleBackgroundFileSelect = (event: Event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
// 验证文件类型
if (!file.type.startsWith('image/')) {
toast.add({
@@ -64,7 +66,7 @@ const handleBackgroundFileSelect = (event: Event) => {
})
return
}
selectedBackgroundFile.value = file
const reader = new FileReader()
reader.onload = (e) => {
@@ -85,12 +87,12 @@ const composeBackgroundVideo = async () => {
})
return
}
try {
isCombinatorLoading.value = true
compositingProgress.value = 0
combinatorError.value = ''
// 使用 FFmpeg WASM 进行视频背景合成
const resultBlob = await useVideoBackgroundCompositing(
props.video.video_alpha_url!,
@@ -99,12 +101,12 @@ const composeBackgroundVideo = async () => {
onProgress: (info) => {
compositingProgress.value = info.progress
compositingPhase.value = info.phase
}
},
}
)
compositedVideoBlob.value = resultBlob
toast.add({
title: '合成成功',
description: '背景已成功合成,可预览或下载',
@@ -126,7 +128,7 @@ const composeBackgroundVideo = async () => {
const downloadCompositedVideo = () => {
if (!compositedVideoBlob.value) return
const url = URL.createObjectURL(compositedVideoBlob.value)
const link = document.createElement('a')
link.href = url
@@ -138,7 +140,9 @@ const downloadCompositedVideo = () => {
}
const compositedVideoUrl = computed(() => {
return compositedVideoBlob.value ? URL.createObjectURL(compositedVideoBlob.value) : ''
return compositedVideoBlob.value
? URL.createObjectURL(compositedVideoBlob.value)
: ''
})
const startDownload = (url: string, filename: string) => {
@@ -148,12 +152,9 @@ const startDownload = (url: string, filename: string) => {
downloadingState.video = 0
}
const {
download,
progressEmitter,
} = useDownload(url, filename)
const { download, progressEmitter } = useDownload(url, filename)
progressEmitter.on('progress', progress => {
progressEmitter.on('progress', (progress) => {
if (url.endsWith('.ass')) {
downloadingState.subtitle = progress
} else {
@@ -176,7 +177,7 @@ const startDownload = (url: string, filename: string) => {
})
})
progressEmitter.on('error', err => {
progressEmitter.on('error', (err) => {
if (url.endsWith('.ass')) {
downloadingState.subtitle = 0
} else {
@@ -198,54 +199,111 @@ const startDownload = (url: string, filename: string) => {
<div
class="w-full flex gap-2 rounded-xl border border-neutral-200 dark:border-neutral-700 hover:shadow transition overflow-hidden p-3"
>
<div class="flex-0 h-48 aspect-[10/16] flex flex-col items-center justify-center rounded-lg shadow overflow-hidden relative group">
<div v-if="!video.video_cover" class="w-full h-full flex flex-col justify-center items-center gap-2" :class="!isFailed ? 'bg-primary' : 'bg-rose-400'">
<UIcon v-if="!isFailed" class="animate-spin text-4xl text-white" name="tabler:loader"/>
<UIcon v-else class="text-4xl text-white" name="tabler:alert-triangle"/>
<div
class="flex-0 h-48 aspect-[10/16] flex flex-col items-center justify-center rounded-lg shadow overflow-hidden relative group"
>
<div
v-if="!video.video_cover"
class="w-full h-full flex flex-col justify-center items-center gap-2"
:class="!isFailed ? 'bg-primary' : 'bg-rose-400'"
>
<UIcon
v-if="!isFailed"
class="animate-spin text-4xl text-white"
name="tabler:loader"
/>
<UIcon
v-else
class="text-4xl text-white"
name="tabler:alert-triangle"
/>
<div class="flex flex-col items-center gap-0.5">
<span class="text-sm font-bold text-white/90">
{{ isFailed ? '生成失败' : '火速生成中...' }}
</span>
<span v-if="!isFailed" class="text-xs font-medium text-white/50">{{ video.progress }}%</span>
<span
v-if="!isFailed"
class="text-xs font-medium text-white/50"
>
{{ video.progress }}%
</span>
</div>
</div>
<NuxtImg v-else :src="video.video_cover" class="w-full h-full brightness-90 object-cover"/>
<div class="absolute inset-0 bg-black/10 backdrop-blur-md flex justify-center items-center rounded-lg opacity-0 group-hover:opacity-100 duration-300">
<div class="rounded-full w-14 aspect-square bg-gray-300/50 backdrop-blur-md flex justify-center items-center cursor-pointer" @click="isPreviewModalOpen = true">
<Icon name="i-tabler-play" class="text-white text-3xl" />
<NuxtImg
v-else
:src="video.video_cover"
class="w-full h-full brightness-90 object-cover"
/>
<div
class="absolute inset-0 bg-black/10 backdrop-blur-md flex justify-center items-center rounded-lg opacity-0 group-hover:opacity-100 duration-300"
>
<div
class="rounded-full w-14 aspect-square bg-gray-300/50 backdrop-blur-md flex justify-center items-center cursor-pointer"
@click="isPreviewModalOpen = true"
>
<Icon
name="i-tabler-play"
class="text-white text-3xl"
/>
</div>
</div>
</div>
<div class="flex-1 flex flex-col justify-between gap-2">
<div class="flex-1 rounded-lg bg-neutral-100 dark:bg-neutral-800 p-2 px-2.5">
<div
class="flex-1 rounded-lg bg-neutral-100 dark:bg-neutral-800 p-2 px-2.5"
>
<ul class="grid grid-cols-2 gap-1.5">
<li class="col-span-2">
<!-- <h2 class="text-2xs font-medium text-primary-500">标题</h2>-->
<p class="text-sm font-bold line-clamp-1">{{ video.title || '无标题' }}</p>
<p class="text-sm font-bold line-clamp-1">
{{ video.title || '无标题' }}
</p>
</li>
<li class="">
<h2 class="text-2xs font-medium text-primary-500">完成时间</h2>
<p class="text-xs line-clamp-1">{{ video.complete_time ? dayjs(video.complete_time * 1000).format('YYYY-MM-DD HH:mm:ss') : '进行中' }}</p>
<p class="text-xs line-clamp-1">
{{
video.complete_time
? dayjs(video.complete_time * 1000).format(
'YYYY-MM-DD HH:mm:ss'
)
: '进行中'
}}
</p>
</li>
<li class="">
<h2 class="text-2xs font-medium text-primary-500">生成耗时</h2>
<p class="text-xs line-clamp-1">{{ video.duration ? dayjs.duration(video.duration || 0).format('HH:mm:ss') : '进行中' }}</p>
<p class="text-xs line-clamp-1">
{{
video.duration
? dayjs.duration(video.duration || 0).format('HH:mm:ss')
: '进行中'
}}
</p>
</li>
<li class="col-span-2 cursor-pointer" @click="isFullContentOpen = true">
<li
class="col-span-2 cursor-pointer"
@click="isFullContentOpen = true"
>
<h2 class="text-2xs font-medium text-primary-500">驱动文本</h2>
<p class="text-xs line-clamp-4 text-justify">{{ video.content }}</p>
</li>
</ul>
</div>
<div class="flex justify-end sm:justify-between items-center group flex-nowrap whitespace-nowrap">
<!-- <div-->
<!-- class="hidden sm:flex items-center gap-1 transition-all group-hover:opacity-0 group-hover:pointer-events-none">-->
<!-- <UIcon class="text-primary text-lg" name="i-tabler-user-square-rounded"/>-->
<!-- <p class="text-xs">数字人 {{ video.digital_human_id }}</p>-->
<!-- </div>-->
<div
class="flex justify-end sm:justify-between items-center group flex-nowrap whitespace-nowrap"
>
<!-- <div-->
<!-- class="hidden sm:flex items-center gap-1 transition-all group-hover:opacity-0 group-hover:pointer-events-none">-->
<!-- <UIcon class="text-primary text-lg" name="i-tabler-user-square-rounded"/>-->
<!-- <p class="text-xs">数字人 {{ video.digital_human_id }}</p>-->
<!-- </div>-->
<div
class="w-fit hidden sm:flex items-center gap-1 transition-all group-hover:opacity-0 group-hover:pointer-events-none">
<p class="text-2xs text-neutral-400 dark:text-neutral-500">{{ video.digital_human_id }}</p>
class="w-fit hidden sm:flex items-center gap-1 transition-all group-hover:opacity-0 group-hover:pointer-events-none"
>
<p class="text-2xs text-neutral-400 dark:text-neutral-500">
{{ video.digital_human_id }}
</p>
</div>
<div class="space-x-2">
<UButton
@@ -258,13 +316,24 @@ const startDownload = (url: string, filename: string) => {
/>
<UButtonGroup size="xs">
<UButton
:label="downloadingState.subtitle > 0 && downloadingState.subtitle < 100 ? `${downloadingState.subtitle.toFixed(0)}%` : '字幕'"
:loading="downloadingState.subtitle > 0 && downloadingState.subtitle < 100"
:label="
downloadingState.subtitle > 0 && downloadingState.subtitle < 100
? `${downloadingState.subtitle.toFixed(0)}%`
: '字幕'
"
:loading="
downloadingState.subtitle > 0 && downloadingState.subtitle < 100
"
:disabled="!video.subtitle"
color="primary"
leading-icon="i-tabler-file-download"
variant="soft"
@click="startDownload(video.subtitle!, (video.title || video.task_id) + '.ass')"
@click="
startDownload(
video.subtitle!,
(video.title || video.task_id) + '.ass'
)
"
/>
<UDropdown
:items="[
@@ -273,8 +342,11 @@ const startDownload = (url: string, filename: string) => {
label: '绿幕视频下载',
icon: 'tabler:download',
click: () => {
startDownload(video.video_url!, (video.title || video.task_id) + '.mp4')
}
startDownload(
video.video_url!,
(video.title || video.task_id) + '.mp4'
)
},
},
{
label: '合成背景图片',
@@ -282,14 +354,20 @@ const startDownload = (url: string, filename: string) => {
click: () => {
isVideoBackgroundPreviewOpen = true
},
disabled: !video.video_alpha_url
disabled: !video.video_alpha_url,
},
],
]"
>
<UButton
:label="downloadingState.video > 0 && downloadingState.video < 100 ? `${downloadingState.video.toFixed(0)}%` : '视频'"
:loading="downloadingState.video > 0 && downloadingState.video < 100"
:label="
downloadingState.video > 0 && downloadingState.video < 100
? `${downloadingState.video.toFixed(0)}%`
: '视频'
"
:loading="
downloadingState.video > 0 && downloadingState.video < 100
"
:disabled="!video.video_url"
color="primary"
leading-icon="i-tabler-download"
@@ -302,10 +380,17 @@ const startDownload = (url: string, filename: string) => {
</div>
<!-- Full video content -->
<UModal v-model="isFullContentOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<UCard
:ui="{
ring: '',
divide: 'divide-y divide-gray-100 dark:divide-gray-800',
}"
>
<template #header>
<div class="flex items-center justify-between">
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
<h3
class="text-base font-semibold leading-6 text-gray-900 dark:text-white"
>
{{ video.title || '无标题' }}
<span class="block text-xs text-primary">驱动内容</span>
</h3>
@@ -326,48 +411,93 @@ const startDownload = (url: string, filename: string) => {
<template #footer>
<div class="flex justify-end gap-2">
<UButton color="primary" @click="isFullContentOpen = false">关闭</UButton>
<UButton
color="primary"
@click="isFullContentOpen = false"
>
关闭
</UButton>
</div>
</template>
</UCard>
</UModal>
<UModal v-model="isPreviewModalOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<UCard
:ui="{
ring: '',
divide: 'divide-y divide-gray-100 dark:divide-gray-800',
}"
>
<template #header>
<div class="flex items-center justify-between">
<div class="text-base font-semibold leading-6 text-gray-900 dark:text-white overflow-hidden">
<div
class="text-base font-semibold leading-6 text-gray-900 dark:text-white overflow-hidden"
>
<p>绿幕视频预览</p>
<p class="text-xs text-blue-500 w-full overflow-hidden text-nowrap text-ellipsis">
<p
class="text-xs text-blue-500 w-full overflow-hidden text-nowrap text-ellipsis"
>
{{ video.title }}
</p>
</div>
<UButton class="-my-1" color="gray" icon="i-tabler-x" variant="ghost" @click="isPreviewModalOpen = false" />
<UButton
class="-my-1"
color="gray"
icon="i-tabler-x"
variant="ghost"
@click="isPreviewModalOpen = false"
/>
</div>
</template>
<video class="w-full rounded shadow" controls autoplay :src="video.video_url" />
<video
class="w-full rounded shadow"
controls
autoplay
:src="video.video_url"
/>
</UCard>
</UModal>
<UModal v-model="isVideoBackgroundPreviewOpen">
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
<UCard
:ui="{
ring: '',
divide: 'divide-y divide-gray-100 dark:divide-gray-800',
}"
>
<template #header>
<div class="flex items-center justify-between">
<div class="text-base font-semibold leading-6 text-gray-900 dark:text-white overflow-hidden">
<div
class="text-base font-semibold leading-6 text-gray-900 dark:text-white overflow-hidden"
>
<p>视频背景合成</p>
<p class="text-xs text-blue-500 w-full overflow-hidden text-nowrap text-ellipsis">
<p
class="text-xs text-blue-500 w-full overflow-hidden text-nowrap text-ellipsis"
>
{{ video.title }}
</p>
</div>
<UButton class="-my-1" color="gray" icon="i-tabler-x" variant="ghost" @click="isVideoBackgroundPreviewOpen = false" />
<UButton
class="-my-1"
color="gray"
icon="i-tabler-x"
variant="ghost"
@click="isVideoBackgroundPreviewOpen = false"
/>
</div>
</template>
<div class="space-y-4">
<!-- 背景图片选择区域 -->
<div v-if="!compositedVideoBlob && !isCombinatorLoading" class="border-2 border-dashed border-neutral-200 dark:border-neutral-700 rounded-lg p-4">
<div
v-if="!compositedVideoBlob && !isCombinatorLoading"
class="border-2 border-dashed border-neutral-200 dark:border-neutral-700 rounded-lg p-4"
>
<div class="space-y-3">
<div class="text-sm font-medium text-gray-900 dark:text-white">选择背景图片</div>
<div class="text-sm font-medium text-gray-900 dark:text-white">
选择背景图片
</div>
<!-- 预览区域 -->
<!-- <div v-if="selectedBackgroundPreview" class="relative w-full aspect-video rounded-lg overflow-hidden bg-neutral-100 dark:bg-neutral-800">
<img :src="selectedBackgroundPreview" alt="背景预览" class="w-full h-full object-cover" />
@@ -376,7 +506,7 @@ const startDownload = (url: string, filename: string) => {
<UIcon class="text-3xl text-neutral-400" name="tabler:photo" />
<span class="text-xs text-neutral-400">点击选择图片</span>
</div> -->
<!-- 文件输入 -->
<input
ref="fileInputRef"
@@ -385,7 +515,7 @@ const startDownload = (url: string, filename: string) => {
class="hidden"
@change="handleBackgroundFileSelect"
/>
<!-- 选择按钮 -->
<UButton
block
@@ -395,9 +525,12 @@ const startDownload = (url: string, filename: string) => {
variant="soft"
@click="fileInputRef?.click()"
/>
<!-- 选中的文件名 -->
<div v-if="selectedBackgroundFile" class="text-xs text-neutral-500 dark:text-neutral-400">
<div
v-if="selectedBackgroundFile"
class="text-xs text-neutral-500 dark:text-neutral-400"
>
已选择: {{ selectedBackgroundFile.name }}
</div>
</div>
@@ -413,20 +546,32 @@ const startDownload = (url: string, filename: string) => {
/>
<!-- 合成进度 -->
<div v-if="isCombinatorLoading" class="space-y-2">
<div
v-if="isCombinatorLoading"
class="space-y-2"
>
<div class="flex justify-between items-center">
<span class="text-sm font-medium text-gray-900 dark:text-white">{{ phaseText }}</span>
<span class="text-xs text-neutral-500">{{ compositingProgress }}%</span>
<span class="text-sm font-medium text-gray-900 dark:text-white">
{{ phaseText }}
</span>
<span class="text-xs text-neutral-500">
{{ compositingProgress }}%
</span>
</div>
<UProgress :value="compositingProgress" />
</div>
<!-- 合成预览 -->
<div v-if="compositedVideoBlob" class="space-y-2">
<div class="text-sm font-medium text-gray-900 dark:text-white">视频预览</div>
<video
<div
v-if="compositedVideoBlob"
class="space-y-2"
>
<div class="text-sm font-medium text-gray-900 dark:text-white">
视频预览
</div>
<video
class="w-full rounded-lg shadow bg-black"
controls
controls
autoplay
muted
:src="compositedVideoUrl"
@@ -446,13 +591,15 @@ const startDownload = (url: string, filename: string) => {
v-if="compositedVideoBlob"
color="gray"
label="重新选择"
@click="() => {
selectedBackgroundFile = null
selectedBackgroundPreview = ''
compositedVideoBlob = null
combinatorError = ''
isCombinatorLoading = false
}"
@click="
() => {
selectedBackgroundFile = null
selectedBackgroundPreview = ''
compositedVideoBlob = null
combinatorError = ''
isCombinatorLoading = false
}
"
/>
<UButton
v-if="compositedVideoBlob"
@@ -477,6 +624,4 @@ const startDownload = (url: string, filename: string) => {
</div>
</template>
<style scoped>
</style>
<style scoped></style>

View File

@@ -610,7 +610,10 @@ defineExpose({
}
.subtitle.stroke {
text-shadow: 1px 1px 0 #000, -1px -1px 0 #000, 1px -1px 0 #000,
text-shadow:
1px 1px 0 #000,
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000;
}