Commit 0e17dfb8 authored by nick zheng's avatar nick zheng

feat: 我的作品

parent 5218f169
...@@ -7,3 +7,13 @@ export function fetchGetTaskList<T>(payload: object) { ...@@ -7,3 +7,13 @@ export function fetchGetTaskList<T>(payload: object) {
export function fetchGetTaskConfig<T>(taskConfigId: string) { export function fetchGetTaskConfig<T>(taskConfigId: string) {
return request.post<T>(`/bizDigitalHumanMemberTaskConfigRest/getById.json?id=${taskConfigId}`) return request.post<T>(`/bizDigitalHumanMemberTaskConfigRest/getById.json?id=${taskConfigId}`)
} }
// 根据id获取作品配置
export function fetchGetTaskConfigById<T>(id: number) {
return request.post<T>(`/bizDigitalHumanMemberTaskStatusRest/getById.json?id=${id}`)
}
// 根据id删除作品
export function fetchDeleteTaskConfigById<T>(id: string) {
return request.post<T>(`/bizDigitalHumanMemberTaskStatusRest/deletedById.json?id=${id}`)
}
...@@ -16,4 +16,13 @@ export default [ ...@@ -16,4 +16,13 @@ export default [
taskConfigId: route.query.taskConfigId, taskConfigId: route.query.taskConfigId,
}), }),
}, },
{
path: '/video-preview/:id?',
name: 'VideoPreview',
meta: {
rank: 1001,
title: '預覽視頻',
},
component: () => import('@/views/video-work/video-preview.vue'),
},
] as RouteRecordRaw[] ] as RouteRecordRaw[]
...@@ -31,7 +31,7 @@ export default [ ...@@ -31,7 +31,7 @@ export default [
title: '我的作品', title: '我的作品',
rank: 1001, rank: 1001,
}, },
component: () => import('@/views/opus/opus.vue'), component: () => import('@/views/video-work/video-work-list.vue'),
}, },
{ {
path: '/work/draft', path: '/work/draft',
......
/**
* 下载文件到本地
* @param fileUrl 文件路径
* @param fileType 文件类型
*/
export function downloadFile(fileUrl: string, fileType: string = 'image/jpeg') {
const name = fileUrl.substring(fileUrl.lastIndexOf('/'))
const xhr = new XMLHttpRequest()
xhr.open('GET', fileUrl)
xhr.responseType = 'blob'
xhr.send()
xhr.onload = function () {
const fileBlob = xhr.response
// 将blob转成url
const fileUrl = URL.createObjectURL(new Blob([fileBlob], { type: fileType }))
const elA = document.createElement('a')
elA.href = fileUrl
elA.download = name
elA.click()
URL.revokeObjectURL(fileBlob)
}
}
...@@ -186,7 +186,7 @@ function handleGetDraftListUpdatePageSize(pageSize: number) { ...@@ -186,7 +186,7 @@ function handleGetDraftListUpdatePageSize(pageSize: number) {
<div <div
v-for="taskTypeItem in taskTypeList" v-for="taskTypeItem in taskTypeList"
:key="taskTypeItem.value" :key="taskTypeItem.value"
class="!h-[28px] cursor-pointer rounded-full !px-[12px] !leading-[28px] hover:text-[#5b647a]" class="!h-[28px] cursor-pointer select-none rounded-full !px-[12px] !leading-[28px] hover:text-[#5b647a]"
:class="taskTypeItem.value === currentSelectedTaskType ? 'bg-[#edeef7]' : ''" :class="taskTypeItem.value === currentSelectedTaskType ? 'bg-[#edeef7]' : ''"
@click="handleFilterDraftListByTaskType(taskTypeItem.value)" @click="handleFilterDraftListByTaskType(taskTypeItem.value)"
> >
......
This diff is collapsed.
import { formatDateTime } from '@/utils/date-formatter' import { formatDateTime } from '@/utils/date-formatter'
import { DataTableColumns, NEllipsis, NPopconfirm } from 'naive-ui' import { DataTableColumns, NEllipsis } from 'naive-ui'
import { h } from 'vue' import { h } from 'vue'
type TaskType = 'IMAGE_VIDEO' | 'BASE_VIDEO' | 'ADVANCED_VIDEO' type TaskType = 'IMAGE_VIDEO' | 'BASE_VIDEO' | 'ADVANCED_VIDEO'
type TaskStateType = 'SUBMIT' | 'GENERATING' | 'SUCCESS' | 'FAILED' type TaskStateType = 'SUBMIT' | 'GENERATING' | 'SUCCESS' | 'FAILED'
export interface TaskItem { export interface VideoWorkItem {
id: number id: number
taskCoverUrl: string taskCoverUrl: string
baiduTaskId: string baiduTaskId: string
...@@ -15,22 +15,30 @@ export interface TaskItem { ...@@ -15,22 +15,30 @@ export interface TaskItem {
status: TaskStateType status: TaskStateType
videoUrl: string videoUrl: string
duration: number duration: number
taskConfigId: number
subtitleFileUrl: null subtitleFileUrl: null
createTime: null createTime: null
updateTime: string updateTime: string
modifiedTime: Date modifiedTime: Date
} }
export function createOpusColumns({ export function createVideoWorkTableColumns({
handleOpusDownload, handleVideoWorkDownload,
handleOpusDeleteRow, handleVideoWorkPreview,
handleOpusExamine, handleVideoWorkEdit,
handleOpusEditing, handleVideoWorkDelete,
}: { }: {
handleOpusDownload: (rowData: TaskItem) => void handleVideoWorkDownload: (rowData: VideoWorkItem) => void
handleOpusDeleteRow: (rowData: TaskItem) => void handleVideoWorkPreview: (rowData: VideoWorkItem) => void
handleOpusExamine: (rowData: TaskItem) => void handleVideoWorkEdit: (rowData: VideoWorkItem) => void
handleOpusEditing: (rowData: TaskItem) => void handleVideoWorkDelete: (rowData: VideoWorkItem) => void
}): DataTableColumns<TaskItem> { }): DataTableColumns<VideoWorkItem> {
function durationToTime(duration: number) {
const hours = Math.floor(duration / 3600)
const minutes = Math.floor((duration - hours * 3600) / 60)
const remainingSeconds = duration % 60
return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`
}
return [ return [
{ {
type: 'selection', type: 'selection',
...@@ -39,19 +47,19 @@ export function createOpusColumns({ ...@@ -39,19 +47,19 @@ export function createOpusColumns({
{ {
title: '作品名稱', title: '作品名稱',
key: 'opusName', key: 'opusName',
width: 320, width: 290,
render(row) { render(row) {
return h('div', { class: 'flex items-center' }, [ return h('div', { class: 'flex items-center' }, [
h( h(
'div', 'div',
{ {
style: { width: '111px', height: '55px' }, style: { width: '64px', height: '36px', marginRight: '10px', flexShrink: 0 },
class: 'mr-[10px] flex items-center justify-center overflow-hidden relative', class: 'flex items-center justify-center overflow-hidden relative',
}, },
[ [
h('img', { h('img', {
src: row.taskCoverUrl, src: row.taskCoverUrl,
alt: '作品圖', alt: '作品圖',
class: 'h-full object-cover', class: 'h-full object-cover',
}), }),
], ],
...@@ -63,7 +71,7 @@ export function createOpusColumns({ ...@@ -63,7 +71,7 @@ export function createOpusColumns({
{ {
title: '狀態', title: '狀態',
key: 'state', key: 'state',
width: 150, width: 110,
render(row) { render(row) {
const stateMap = { const stateMap = {
SUBMIT: { text: '待生成', color: '#BBBBBB' }, SUBMIT: { text: '待生成', color: '#BBBBBB' },
...@@ -74,8 +82,13 @@ export function createOpusColumns({ ...@@ -74,8 +82,13 @@ export function createOpusColumns({
const state = stateMap[row.status] || { text: '', color: '#CCCCCC' } // 默认灰色 const state = stateMap[row.status] || { text: '', color: '#CCCCCC' } // 默认灰色
return h('div', { class: 'flex items-center ' }, [ return h('div', { class: 'flex items-center ' }, [
h('div', { h('div', {
class: 'w-[10px] h-[10px] rounded-[25px] mr-18px reading-[22px]', style: {
style: { backgroundColor: state.color }, backgroundColor: state.color,
width: '10px',
height: '10px',
borderRadius: '50%',
marginRight: '12px',
},
}), }),
h('span', {}, state.text), h('span', {}, state.text),
]) ])
...@@ -84,7 +97,7 @@ export function createOpusColumns({ ...@@ -84,7 +97,7 @@ export function createOpusColumns({
{ {
title: '類型', title: '類型',
key: 'type', key: 'type',
width: 120, width: 110,
render(row) { render(row) {
const typeMap = { const typeMap = {
IMAGE_VIDEO: '照片數字人', IMAGE_VIDEO: '照片數字人',
...@@ -97,9 +110,9 @@ export function createOpusColumns({ ...@@ -97,9 +110,9 @@ export function createOpusColumns({
{ {
title: '視頻時長', title: '視頻時長',
key: 'videoDuration', key: 'videoDuration',
width: 120, width: 100,
render(row) { render(row) {
return h('span', {}, row.duration ? row.duration : '--') return h('span', {}, row.duration ? durationToTime(Math.floor(row.duration / 1000)) : '--')
}, },
}, },
{ {
...@@ -120,50 +133,45 @@ export function createOpusColumns({ ...@@ -120,50 +133,45 @@ export function createOpusColumns({
h( h(
'span', 'span',
{ {
onClick: () => handleOpusExamine(row), onClick: () => handleVideoWorkPreview(row),
style: { marginRight: '20px' }, style: {
class: 'text-theme-color cursor-pointer hover:opacity-80', marginRight: '20px',
color: row.videoUrl ? '' : '#BBB',
cursor: row.videoUrl ? 'pointer' : 'not-allowed',
},
class: 'text-theme-color hover:opacity-80',
}, },
'查看', '查看',
), ),
h( h(
'span', 'span',
{ {
onClick: () => handleOpusDownload(row), onClick: () => handleVideoWorkDownload(row),
style: { marginRight: '20px' }, style: {
class: 'text-theme-color cursor-pointer hover:opacity-80', marginRight: '20px',
color: row.videoUrl ? '' : '#BBB',
cursor: row.videoUrl ? 'pointer' : 'not-allowed',
},
class: 'text-theme-color hover:opacity-80',
}, },
'下載', '下載',
), ),
h( h(
'span', 'span',
{ {
onClick: () => handleOpusEditing(row), onClick: () => handleVideoWorkEdit(row),
style: { marginRight: '20px' }, style: { marginRight: '20px' },
class: 'text-theme-color cursor-pointer hover:opacity-80', class: 'text-theme-color cursor-pointer hover:opacity-80',
}, },
'繼續編輯', '繼續編輯',
), ),
h( h(
NPopconfirm, 'span',
{
onPositiveClick: () => handleOpusDeleteRow(row),
onNegativeClick: () => {
window.$message.info('取消删除')
},
},
{ {
trigger: () => onClick: () => handleVideoWorkDelete(row),
h( class: 'text-theme-color cursor-pointer hover:opacity-80',
'span',
{
style: { marginRight: '20px' },
class: 'text-theme-color cursor-pointer hover:opacity-80',
},
'删除',
),
default: () => '確認要刪除這一行嗎?',
}, },
'删除',
), ),
]) ])
}, },
......
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { Left } from '@icon-park/vue-next'
import { fetchGetTaskConfigById } from '@/apis/opus'
import { VideoWorkItem } from './columns'
import { downloadFile } from '@/utils/download'
const router = useRouter()
const currentTaskConfig = ref<VideoWorkItem>()
onMounted(() => {
if (!router.currentRoute.value.params.id) {
router.replace({ name: 'Workbench' })
return
}
handleGetDigitalVideoWorkById(Number(router.currentRoute.value.params.id))
})
function handleBack() {
router.back()
}
function handleGetDigitalVideoWorkById(id: number) {
fetchGetTaskConfigById<VideoWorkItem>(id)
.then((res) => {
if (res.code === 0) {
currentTaskConfig.value = res.data
}
})
.catch(() => {
router.replace({ name: 'Workbench' })
})
}
function handleEditTaskConfig() {
router.push({ name: 'Creation', query: { taskConfigId: currentTaskConfig.value?.taskConfigId } })
}
function handleDownloadVideo() {
currentTaskConfig.value?.videoUrl && downloadFile(currentTaskConfig.value?.videoUrl, 'video/mp4')
}
</script>
<template>
<div class="flex h-screen min-w-[1280px] flex-col bg-[#f3f4fb]">
<header class="flex h-14 items-center justify-between bg-white px-4">
<div class="flex cursor-pointer items-center hover:opacity-80" @click="handleBack">
<Left theme="outline" size="18" fill="#333" :stroke-width="3" class="mr-2" />
<span class="font-500">返回</span>
</div>
<div class="flex items-center">
<div class="flex items-center gap-4">
<NButton class="h-[32px]! rounded-md!" @click="handleEditTaskConfig"> 繼續編輯 </NButton>
<NButton class="w-[72px]! h-[32px]! rounded-md!" type="info" @click="handleDownloadVideo"> 下載 </NButton>
</div>
</div>
</header>
<main class="flex flex-1 items-center justify-center">
<video
:src="currentTaskConfig?.videoUrl"
:poster="currentTaskConfig?.taskCoverUrl"
class="h-[538px] w-[902px]"
controls
/>
</main>
</div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, Ref, ref } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useIntervalFn } from '@vueuse/core'
import { Search, CloseOne } from '@icon-park/vue-next' import { Search, CloseOne } from '@icon-park/vue-next'
import { createData } from './opusData'
import { fetchGetTaskList } from '@/apis/opus'
import { createOpusColumns, TaskItem } from './columns'
import useTableScrollY from '@/composables/useTableScrollY' import useTableScrollY from '@/composables/useTableScrollY'
import { fetchDeleteTaskConfigById, fetchGetTaskList } from '@/apis/opus'
import { PaginationInfo } from '@/components/custom-pagination/custom-pagination.vue' import { PaginationInfo } from '@/components/custom-pagination/custom-pagination.vue'
import { createVideoWorkTableColumns, VideoWorkItem } from './columns'
import { downloadFile } from '@/utils/download'
const router = useRouter()
const { pageContentWrapRef, tableContentY } = useTableScrollY(48 + 28 + 24 + 44 + 48 + 48 + 50) const { pageContentWrapRef, tableContentY } = useTableScrollY(48 + 28 + 24 + 44 + 48 + 48 + 50)
...@@ -25,72 +29,153 @@ const taskTypeList = [ ...@@ -25,72 +29,153 @@ const taskTypeList = [
}, },
] ]
const isShowPagination = computed(() => {
return tableContentY.value > 0
})
const currentSelectedTaskType = ref('') const currentSelectedTaskType = ref('')
const checkedOpusKeys: Ref<number[]> = ref([]) const checkedVideoWorkIds = ref<number[]>([])
const opusList = ref(createData())
const searchQuery = ref('') const searchQuery = ref('')
const filteredTemplateData = ref<TaskItem[]>([]) const videoWorkTableLoading = ref(false)
const pagingInfo = ref({ const videoWorkList = ref<VideoWorkItem[]>([])
const pagingInfo = ref<PaginationInfo>({
pageNo: 0, pageNo: 0,
totalPages: 1, totalPages: 1,
pageSize: 10, pageSize: 10,
totalRows: 2, totalRows: 0,
}) })
const onCheckedOpusKeysChange = (newKeys: number[]) => { const isShowPagination = computed(() => {
checkedOpusKeys.value = newKeys return tableContentY.value > 0
} })
const isTaskAllSUCCESS = computed(() => {
return videoWorkList.value.every((taskItem) => {
return taskItem.status === 'SUCCESS'
})
})
watch(
() => isTaskAllSUCCESS.value,
(newVal) => {
newVal ? pauseIntervalFn() : resumeIntervalFn()
},
{ deep: true },
)
onMounted(() => {
handleGetDigitalVideoWorkList()
})
const { pause: pauseIntervalFn, resume: resumeIntervalFn } = useIntervalFn(
async () => {
if (videoWorkTableLoading.value) return
const opusActionColumns = createOpusColumns({ await fetchGetTaskList<VideoWorkItem[]>({
handleOpusDeleteRow(rowData) { taskType: currentSelectedTaskType.value,
window.$message.info(`删除 ${rowData.id}`) taskName: searchQuery.value,
pagingInfo: pagingInfo.value,
})
}, },
handleOpusExamine(rowData) { 2000,
window.$message.info(`查看 ${rowData.id}`) { immediateCallback: false, immediate: false },
)
const videoWorkActionColumns = createVideoWorkTableColumns({
handleVideoWorkDownload(rowData) {
if (!rowData.videoUrl) {
return
}
downloadFile(rowData.videoUrl, 'video/mp4')
}, },
handleOpusDownload(rowData) { handleVideoWorkPreview(rowData) {
window.$message.info(`下载 ${rowData.id}`) if (!rowData.videoUrl) {
return
}
router.push({
name: 'VideoPreview',
params: {
id: rowData.id,
},
})
}, },
handleOpusEditing(rowData) { handleVideoWorkEdit(rowData) {
window.$message.info(`编辑 ${rowData.id}`) router.push({ name: 'Creation', query: { taskConfigId: rowData.taskConfigId } })
},
handleVideoWorkDelete(rowData) {
window.$dialog.warning({
title: '請確認是否刪除',
content: '刪除後數據不可恢復',
negativeText: '取消',
positiveText: '確認',
onPositiveClick: async () => {
const res = await fetchDeleteTaskConfigById(rowData!.id! + '')
if (res.code === 0) {
window.$message.success('刪除成功')
videoWorkList.value.length === 1 && (pagingInfo.value.pageNo = pagingInfo.value.pageNo - 1)
await handleGetDigitalVideoWorkList()
}
},
onNegativeClick() {},
})
}, },
}) })
function handleUpdateCheckedVideoWorkIds(videoWorkIds: number[]) {
checkedVideoWorkIds.value = videoWorkIds
}
function handleFilterVideoWorkListByTaskType(taskType: string) {
currentSelectedTaskType.value = taskType
handleGetDigitalVideoWorkList()
}
const handleMultiSelectDelete = () => { const handleMultiSelectDelete = () => {
window.$dialog.warning({ window.$dialog.warning({
title: '提示', title: '提示',
content: '確認刪除嗎?', content: '確認刪除嗎?',
positiveText: '確認', positiveText: '確認',
negativeText: '取消', negativeText: '取消',
onPositiveClick: () => { onPositiveClick: async () => {
opusList.value = opusList.value.filter((item) => !checkedOpusKeys.value.includes(item.key)) if (checkedVideoWorkIds.value.length > 0) {
checkedOpusKeys.value = [] const res = await fetchDeleteTaskConfigById(checkedVideoWorkIds.value.join(','))
window.$message.success('删除成功')
}, if (res.code === 0) {
onNegativeClick: () => { window.$message.success('删除成功')
window.$message.error('取消') await handleGetDigitalVideoWorkList()
}
}
}, },
onNegativeClick() {},
}) })
} }
function handleSearchVideoWorkList() {
pagingInfo.value.pageNo = 1
handleGetDigitalVideoWorkList()
}
function handleClearSearchQuery() { function handleClearSearchQuery() {
searchQuery.value = '' searchQuery.value = ''
pagingInfo.value.pageNo = 1
handleGetDigitalVideoWorkList()
} }
async function handleGetTaskList() { async function handleGetDigitalVideoWorkList() {
const res = await fetchGetTaskList<TaskItem[]>({ pagingInfo: pagingInfo.value }) videoWorkTableLoading.value = true
const res = await fetchGetTaskList<VideoWorkItem[]>({
taskType: currentSelectedTaskType.value,
taskName: searchQuery.value,
pagingInfo: pagingInfo.value,
})
if (res.code === 0) { if (res.code === 0) {
filteredTemplateData.value = res.data videoWorkList.value = res.data
pagingInfo.value = res.pagingInfo as PaginationInfo pagingInfo.value = res.pagingInfo as PaginationInfo
videoWorkTableLoading.value = false
checkedVideoWorkIds.value = []
} }
} }
onMounted(() => {
handleGetTaskList()
})
function handleGetTaskListUpdatePageNo(pageNo: number) { function handleGetTaskListUpdatePageNo(pageNo: number) {
pagingInfo.value.pageNo = pageNo pagingInfo.value.pageNo = pageNo
...@@ -110,8 +195,9 @@ function handleGetTaskListUpdatePageSize(pageSize: number) { ...@@ -110,8 +195,9 @@ function handleGetTaskListUpdatePageSize(pageSize: number) {
<div <div
v-for="taskTypeItem in taskTypeList" v-for="taskTypeItem in taskTypeList"
:key="taskTypeItem.value" :key="taskTypeItem.value"
class="!h-[28px] cursor-pointer rounded-full !px-[12px] !leading-[28px] hover:text-[#5b647a]" class="!h-[28px] cursor-pointer select-none rounded-full !px-[12px] !leading-[28px] hover:text-[#5b647a]"
:class="taskTypeItem.value === currentSelectedTaskType ? 'bg-[#edeef7]' : ''" :class="taskTypeItem.value === currentSelectedTaskType ? 'bg-[#edeef7]' : ''"
@click="handleFilterVideoWorkListByTaskType(taskTypeItem.value)"
> >
{{ taskTypeItem.label }} {{ taskTypeItem.label }}
</div> </div>
...@@ -120,14 +206,14 @@ function handleGetTaskListUpdatePageSize(pageSize: number) { ...@@ -120,14 +206,14 @@ function handleGetTaskListUpdatePageSize(pageSize: number) {
<div> <div>
<NButton <NButton
class="!rounded-[4px]" class="!rounded-[4px]"
:class="{ '!border-[#2468f2] !bg-[#2468f2] !text-white': checkedOpusKeys.length > 0 }" :class="{ '!border-[#2468f2] !bg-[#2468f2] !text-white': checkedVideoWorkIds.length > 0 }"
:disabled="!checkedOpusKeys.length" :disabled="!checkedVideoWorkIds.length"
@click="handleMultiSelectDelete" @click="handleMultiSelectDelete"
> >
刪除 刪除
</NButton> </NButton>
<span v-show="checkedOpusKeys.length" class="color-[#999] ml-[8px] text-[12px]"> <span v-show="checkedVideoWorkIds.length" class="color-[#999] ml-[8px] text-[12px]">
選擇{{ checkedOpusKeys.length }}條記錄 選擇{{ checkedVideoWorkIds.length }}條記錄
</span> </span>
</div> </div>
...@@ -139,6 +225,7 @@ function handleGetTaskListUpdatePageSize(pageSize: number) { ...@@ -139,6 +225,7 @@ function handleGetTaskListUpdatePageSize(pageSize: number) {
type="text" type="text"
class="mx-[11px] w-[183px] border-none outline-none" class="mx-[11px] w-[183px] border-none outline-none"
placeholder="請輸入名稱進行蒐索" placeholder="請輸入名稱進行蒐索"
@keyup.enter="handleSearchVideoWorkList"
/> />
<CloseOne <CloseOne
v-show="searchQuery.length" v-show="searchQuery.length"
...@@ -148,19 +235,20 @@ function handleGetTaskListUpdatePageSize(pageSize: number) { ...@@ -148,19 +235,20 @@ function handleGetTaskListUpdatePageSize(pageSize: number) {
class="mr-[5px] cursor-pointer" class="mr-[5px] cursor-pointer"
@click="handleClearSearchQuery" @click="handleClearSearchQuery"
/> />
<Search theme="outline" size="16" fill="#00000040" class="cursor-pointer" /> <Search theme="outline" size="16" fill="#00000040" class="cursor-pointer" @click="handleSearchVideoWorkList" />
</div> </div>
</div> </div>
<div :style="{ height: tableContentY + 48 + 'px' }"> <div :style="{ height: tableContentY + 48 + 'px' }">
<NDataTable <NDataTable
:loading="videoWorkTableLoading"
:bordered="false" :bordered="false"
:single-line="false" :single-line="false"
:columns="opusActionColumns" :columns="videoWorkActionColumns"
:data="filteredTemplateData" :data="videoWorkList"
:checked-row-keys="checkedOpusKeys" :row-key="(row: VideoWorkItem) => row.id"
:max-height="tableContentY" :max-height="tableContentY"
:scroll-x="1286" :scroll-x="1086"
@update:checked-row-keys="onCheckedOpusKeysChange" @update:checked-row-keys="handleUpdateCheckedVideoWorkIds"
> >
<template #empty> <template #empty>
<div :style="{ height: tableContentY + 'px' }" class="flex items-center justify-center"> <div :style="{ height: tableContentY + 'px' }" class="flex items-center justify-center">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment