Commit d26ee41a authored by Dazzle Wu's avatar Dazzle Wu

feat: 视频生成页面

parent 1ae64fce
import { request } from '@/utils/request'
// 根据ID获取推荐模板信息
export function fetchDigitalHumanTemplateStatus<T>(id: number) {
return request.post<T>(`/aiDigitalHumanTemplateStatusRest/getDigitalHumanTemplateStatus.json?id=${id}`)
}
// 获取3D数字人形象信息
export function fetch3DImageList<T>() {
return request.post<T>('/bizDigitalHumanImageRest/get3DImageList.json')
}
// 获取2D精品数字人形象信息
export function fetch2DBoutiqueImageList<T>() {
return request.post<T>('/bizDigitalHumanImageRest/get2DBoutiqueImageList.json')
}
// 获取2D小样本数字人形象信息
export function fetch2DFewShotImageList<T>() {
return request.post<T>('/bizDigitalHumanImageRest/get2DFewShotImageList.json')
}
// 获取背景图
export function fetchBackgroundImage<T>() {
return request.post<T>('/bizDigitalHumanImageRest/getBackgroundImage.json')
}
// 根据人物名称分页获取人物信息
export function fetchInfoByImageName<T>(imageName: string) {
return request.post<T>(`/bizDigitalHumanImageRest/findByImageName.json?imageName=${imageName}`)
}
// 根据音色ID主键获取音色
export function fetchDigitalHumanTimbreById<T>() {
return request.post<T>('/bizDigitalHumanTimbreRest/getDigitalHumanTimbreById.json')
}
// 获取音色列表
export function fetchDigitalHumanTimbreList<T>() {
return request.post<T>('/bizDigitalHumanTimbreRest/getDigitalHumanTimbreList.json')
}
// 模糊查询音色
export function fetchTimbreByExample<T>(condition: string) {
return request.post<T>(`/bizDigitalHumanTimbreRest/getByExample.json?condition=${condition}`)
}
<script setup lang="ts">
interface TabItem {
key: number
label: string
}
interface Props {
value: number
list: TabItem[]
}
interface Emits {
(e: 'update:value', value: number): void
}
defineProps<Props>()
const emit = defineEmits<Emits>()
</script>
<template>
<div class="border-gray relative flex rounded-full border">
<div
v-for="(item, index) in list"
:key="index"
class="z-10 flex h-8 w-20 cursor-pointer items-center justify-center rounded-full"
:class="{ 'text-white': item.key === value }"
@click="emit('update:value', item.key)"
>
{{ item.label }}
</div>
<div
class="absolute h-8 w-20 rounded-full bg-blue-500 transition-transform duration-300"
:class="`translate-x-${value * 20}`"
></div>
</div>
</template>
<script setup lang="ts">
interface TabItem {
key: string
icon: string
label: string
}
interface Props {
value: string
list: TabItem[]
}
interface Emits {
(e: 'update:value', value: string): void
}
defineProps<Props>()
const emit = defineEmits<Emits>()
</script>
<template>
<div class="w-20 p-2">
<div
v-for="(item, index) in list"
:key="index"
class="mb-2 flex h-16 w-16 cursor-pointer flex-col items-center justify-center rounded-lg"
:class="{ 'bg-blue-100': item.key === value }"
@click="emit('update:value', item.key)"
>
<CustomIcon class="text-2xl" :icon="item.icon" />
<div>{{ item.label }}</div>
</div>
</div>
</template>
import { type RouteRecordRaw } from 'vue-router'
import Creation from '@/views/creation/creation.vue'
export default [
{
path: '/fe/creation',
name: 'Creation',
meta: {
rank: 1001,
title: '数字人创作',
},
component: Creation,
},
] as RouteRecordRaw[]
import { defineStore } from 'pinia'
import { DigitalTemplate } from '@/store/types/creation'
function defaultDigitalCreation(): DigitalTemplate {
return {
id: 1,
templateType: '产品营销',
videoName: '营销视频',
taskType: 'BASE_VIDEO',
requestId: undefined,
inputImageUrl: undefined,
driveType: 'TEXT',
text: '嗨!我是挥手问候家',
ttsParams: {
person: '5153',
speed: '5',
volume: '5',
pitch: '5',
},
inputAudioUrl: undefined,
callbackUrl: undefined,
figureId: '',
videoParams: {
width: 1920,
height: 0,
transparent: false,
},
dhParams: {
cameraId: undefined,
position: {
x: 100,
y: 111,
w: undefined,
h: undefined,
},
},
subtitleParams: {
subtitlePolicy: 'SRT',
enabled: true,
},
backgroundImageUrl: undefined,
autoAnimoji: true,
enablePalindrome: false,
templateId: null,
title: undefined,
logoParams: null,
bgmParams: null,
materialUrl: undefined,
}
}
function getLocalState(): DigitalTemplate {
return defaultDigitalCreation()
}
export const useDigitalCreationStore = defineStore('digital-creation-store', {
state: (): DigitalTemplate => getLocalState(),
actions: {
setFigureId(figureId: string | null) {
this.figureId = figureId
},
setBackgroundImageUrl(backgroundImageUrl: string) {
this.backgroundImageUrl = backgroundImageUrl
},
updateDigitalCreation(digitalCreation: DigitalTemplate) {
this.$state = { ...this.$state, ...digitalCreation }
},
resetDigitalCreation() {
this.$state = defaultDigitalCreation()
},
},
})
export interface DigitalTemplate {
id: number
templateType: string
videoName: string
taskType: string
requestId?: string
inputImageUrl?: string
driveType: string
text: string
ttsParams: {
person: string
speed: string
volume: string
pitch: string
}
inputAudioUrl?: string
callbackUrl?: string
figureId: string | null
videoParams: {
width: number
height: number
transparent: boolean
}
dhParams: {
cameraId?: number
position: {
x: number
y: number
w?: number
h?: number
}
}
subtitleParams: {
subtitlePolicy: string
enabled: boolean
}
backgroundImageUrl?: string
autoAnimoji: boolean
enablePalindrome: boolean
templateId?: any
title?: string
logoParams?: any
bgmParams?: any
materialUrl?: string
}
export interface ImageItem {
id: number
imageType: ImageType
imageName: string | null
figureId: string | null
imageUrl: string
}
export enum ImageType {
THREE_D = 'THREE_D',
TWO_D_BOUTIQUE = 'TWO_D_BOUTIQUE',
TWO_D_FEW_SHOT = 'TWO_D_FEW_SHOT',
BACKGROUND = 'BACKGROUND',
}
<script setup lang="ts">
import { fetchBackgroundImage } from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { ImageItem } from '@/store/types/creation'
import { ref } from 'vue'
const digitalCreationStore = useDigitalCreationStore()
const imageList = ref<ImageItem[]>([])
const uploadLoading = ref(false)
const showCropModal = ref(false)
const loaded = ref(Array(imageList.value.length).fill(false))
getBackgroundImageList()
async function getBackgroundImageList() {
const res = await fetchBackgroundImage<ImageItem[]>()
if (res.code === 0) {
imageList.value = res.data.map((i) => ({ ...i, checked: i.imageUrl === digitalCreationStore.backgroundImageUrl }))
loaded.value = Array(res.data.length).fill(false)
}
}
function uploadImage() {
showCropModal.value = true
}
function handleClickImage(image: ImageItem) {
digitalCreationStore.setBackgroundImageUrl(image.imageUrl)
}
function handleDelete() {
window.$dialog.warning({
title: '刪除圖片',
content: '是否刪除該圖片?',
positiveText: '是',
negativeText: '否',
onPositiveClick: () => {
window.$message.success('刪除成功')
},
onNegativeClick: () => {
window.$message.error('刪除失敗')
},
})
}
function onImageLoaded(index: number) {
loaded.value[index] = true
}
</script>
<template>
<n-input round placeholder="搜索">
<template #prefix>
<CustomIcon class="text-lg" icon="mingcute:search-line" />
</template>
</n-input>
<div class="h-4"></div>
<n-grid :x-gap="12" :y-gap="12" :cols="3">
<n-gi>
<n-spin :show="uploadLoading">
<label
class="h-22 w-22 hover:border-blue flex cursor-pointer flex-col items-center justify-center rounded-lg border border-gray-200"
for="upload"
>
<CustomIcon class="text-lg" icon="mingcute:add-line" />
</label>
<input id="upload" type="file" accept="image/*" class="hidden" @change="uploadImage" />
<template #description>上傳中</template>
</n-spin>
</n-gi>
<n-gi v-for="(image, index) in imageList" :key="index">
<n-spin :show="!loaded[index]">
<div
class="h-22 w-22 group relative cursor-pointer overflow-hidden rounded-lg border border-2"
:class="image.imageUrl === digitalCreationStore.backgroundImageUrl ? 'border-blue' : 'border-transparent'"
@click="handleClickImage(image)"
>
<img class="h-full w-full object-contain" :src="image.imageUrl" @load="onImageLoaded(index)" />
<div class="from-gray absolute bottom-0 h-5 w-full bg-gradient-to-t px-1 text-xs leading-5 text-white">
{{ image.imageName }}
</div>
<div
class="absolute right-1 top-1 hidden h-7 w-7 cursor-pointer items-center justify-center rounded-md bg-black/40 p-1 group-hover:flex"
@click.stop="handleDelete"
>
<CustomIcon icon="mi:delete" class="text-lg text-white" />
</div>
</div>
</n-spin>
</n-gi>
</n-grid>
<n-modal v-model:show="showCropModal" preset="card" title="裁剪圖片"> 内容 </n-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const x = ref(0)
const y = ref(0)
const w = ref(0)
const h = ref(0)
const isLock = ref(false)
</script>
<template>
<div>背景位置</div>
<div class="mt-4 flex gap-4">
<n-input-number v-model:value="x">
<template #prefix>X</template>
</n-input-number>
<n-input-number v-model:value="y">
<template #prefix>Y</template>
</n-input-number>
</div>
<div class="mt-4 flex gap-4">
<n-input-number v-model:value="w">
<template #prefix>W</template>
</n-input-number>
<n-input-number v-model:value="h">
<template #prefix>H</template>
</n-input-number>
</div>
<div class="mt-4 flex items-center justify-between">
<span>鎖定背景</span>
<n-switch v-model:value="isLock" />
</div>
</template>
<script setup lang="ts">
import BackgroundImages from './background-images.vue'
// import BackgroundPosition from './background-position.vue'
</script>
<template>
<n-tabs type="line" animated>
<n-tab-pane name="images" tab="圖片">
<BackgroundImages />
</n-tab-pane>
<!-- <n-tab-pane name="position" tab="位置">
<BackgroundPosition />
</n-tab-pane> -->
</n-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const active = ref(false)
</script>
<template>
<n-tabs type="line" animated>
<n-tab-pane name="caption" tab="字幕">
<div class="flex items-center justify-between">
<span>是否開啓</span>
<n-switch v-model:value="active" />
</div>
</n-tab-pane>
</n-tabs>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const pace = ref(1)
const intonation = ref(1)
const showAll = ref(false)
const tabValue = ref(0)
const tablist = [
{ label: '女性', key: 0 },
{ label: '男性', key: 1 },
]
</script>
<template>
<div v-if="!showAll">
<div class="relative flex items-center gap-2 rounded-2xl border p-2">
<div class="h-16 w-16 rounded-lg bg-gray-200"></div>
<div class="flex-1 overflow-hidden">
<div class="mb-2 flex items-center gap-2">
<div class="max-w-24 truncate">度清風</div>
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" />
</div>
<div class="flex gap-2">
<n-tag type="warning" round>知性大方</n-tag>
<n-tag type="success" round>客服助理</n-tag>
<n-tag type="success" round>知性大方</n-tag>
<n-tag type="warning" round>客服助理</n-tag>
</div>
</div>
<div class="absolute right-2 top-2">
<CustomIcon class="cursor-pointer text-lg" icon="ant-design:swap-outlined" @click="showAll = true" />
</div>
</div>
<div class="mt-4 text-lg">聲音</div>
<div class="mt-4 flex items-center gap-2">
<div class="w-12">語速:</div>
<n-slider v-model:value="pace" class="flex-1" :max="1.5" :min="0.5" :step="0.25" />
<div class="w-10">{{ pace }}x</div>
</div>
<div class="mt-4 flex items-center gap-2">
<div class="w-12">語調:</div>
<n-slider v-model:value="intonation" class="flex-1" :max="5" :min="1" :step="1" />
<div class="w-10">{{ intonation }}</div>
</div>
</div>
<div v-else>
<div class="flex items-center gap-4 pb-3">
<n-button text @click="showAll = false">
<template #icon>
<CustomIcon class="text-lg" icon="mingcute:left-line" />
</template>
返回
</n-button>
<n-input round placeholder="搜索">
<template #prefix>
<CustomIcon class="text-lg" icon="mingcute:search-line" />
</template>
</n-input>
</div>
<div class="flex justify-end pb-3">
<HorizontalTabs v-model:value="tabValue" :list="tablist" />
</div>
<div>
<div class="flex items-center gap-2 rounded-2xl border p-2">
<div class="h-16 w-16 rounded-lg bg-gray-200"></div>
<div class="flex-1 overflow-hidden">
<div class="mb-2 flex items-center gap-2">
<div class="max-w-24 truncate">度清風</div>
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" />
</div>
<div class="flex gap-2">
<n-tag type="warning" round>知性大方</n-tag>
<n-tag type="success" round>客服助理</n-tag>
<n-tag type="success" round>知性大方</n-tag>
<n-tag type="warning" round>客服助理</n-tag>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ImageItem } from '@/store/types/creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
interface Props {
value: ImageItem
}
interface Emits {
(e: 'click', id: string | null): void
}
defineProps<Props>()
const emit = defineEmits<Emits>()
const digitalCreationStore = useDigitalCreationStore()
</script>
<template>
<div
class="hover:border-blue relative h-28 w-16 cursor-pointer overflow-hidden rounded border border-2"
:class="digitalCreationStore.figureId === value.figureId ? 'border-blue' : 'border-transparent'"
@click="emit('click', value.figureId)"
>
<img :src="value.imageUrl" />
<div class="from-gray absolute bottom-0 h-5 w-full bg-gradient-to-t px-1 text-xs leading-5 text-white">
{{ value.imageName }}
</div>
<!-- <CustomIcon
v-if="digitalCreationStore.figureId === value.figureId"
icon="teenyicons:tick-circle-solid"
class="text-blue absolute -left-2 -top-2 text-lg"
/> -->
</div>
</template>
<script setup lang="ts">
import { fetch2DBoutiqueImageList, fetch2DFewShotImageList, fetch3DImageList } from '@/apis/digital-creation'
import { ImageItem, ImageType } from '@/store/types/creation'
import { onMounted, ref } from 'vue'
import DigitalCard from './digital-card.vue'
import { useDigitalCreationStore } from '@/store/modules/creation'
const digitalCreationStore = useDigitalCreationStore()
const threeDImageList = ref<ImageItem[]>([])
const twoDBoutiqueImageList = ref<ImageItem[]>([])
const twoDFewShotImageList = ref<ImageItem[]>([])
const allImageList = ref<ImageItem[]>([])
const showAll = ref(false)
onMounted(() => {
getDigitalImageList()
})
async function getDigitalImageList() {
const [res1, res2, res3] = await Promise.all([
fetch3DImageList<ImageItem[]>(),
fetch2DBoutiqueImageList<ImageItem[]>(),
fetch2DFewShotImageList<ImageItem[]>(),
])
res1.code === 0 && (threeDImageList.value = res1.data)
res2.code === 0 && (twoDBoutiqueImageList.value = res2.data)
res3.code === 0 && (twoDFewShotImageList.value = res3.data)
}
function handleClickDigitalImage(id: string | null) {
digitalCreationStore.setFigureId(id)
}
function handleClickAll(imageType: ImageType) {
switch (imageType) {
case ImageType.THREE_D:
allImageList.value = threeDImageList.value
break
case ImageType.TWO_D_BOUTIQUE:
allImageList.value = twoDBoutiqueImageList.value
break
case ImageType.TWO_D_FEW_SHOT:
allImageList.value = twoDFewShotImageList.value
break
}
showAll.value = true
}
</script>
<template>
<div v-if="!showAll">
<div class="pb-4">
<div class="flex items-center justify-between pb-3">
<span>3D數字人</span>
<span class="text-gray cursor-pointer text-xs" @click="handleClickAll(ImageType.THREE_D)">全部</span>
</div>
<div class="flex flex-wrap gap-3">
<DigitalCard
v-for="item in threeDImageList.slice(0, 4)"
:key="item.id"
:value="item"
@click="handleClickDigitalImage"
/>
</div>
</div>
<div class="pb-4">
<div class="flex items-center justify-between pb-3">
<span>2D精品數字人</span>
<span class="text-gray cursor-pointer text-xs" @click="handleClickAll(ImageType.TWO_D_BOUTIQUE)">全部</span>
</div>
<div class="flex flex-wrap gap-3">
<DigitalCard
v-for="item in twoDBoutiqueImageList.slice(0, 4)"
:key="item.id"
:value="item"
@click="handleClickDigitalImage"
/>
</div>
</div>
<div class="pb-4">
<div class="flex items-center justify-between pb-3">
<span>2D小樣本數字人</span>
<span class="text-gray cursor-pointer text-xs" @click="handleClickAll(ImageType.TWO_D_FEW_SHOT)">全部</span>
</div>
<div class="flex flex-wrap gap-3">
<DigitalCard
v-for="item in twoDFewShotImageList.slice(0, 4)"
:key="item.id"
:value="item"
@click="handleClickDigitalImage"
/>
</div>
</div>
</div>
<div v-else>
<div class="flex items-center gap-4 pb-3">
<n-button text @click="showAll = false">
<template #icon>
<CustomIcon class="text-lg" icon="mingcute:left-line" />
</template>
返回
</n-button>
<n-input round placeholder="搜索">
<template #prefix>
<CustomIcon class="text-lg" icon="mingcute:search-line" />
</template>
</n-input>
</div>
<div class="flex flex-wrap gap-3">
<DigitalCard v-for="item in allImageList" :key="item.id" :value="item" @click="handleClickDigitalImage" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const x = ref(0)
const y = ref(0)
const w = ref(0)
const h = ref(0)
</script>
<template>
<div>數字人位置</div>
<div class="mt-4 flex gap-4">
<n-input-number v-model:value="x">
<template #prefix>X</template>
</n-input-number>
<n-input-number v-model:value="y">
<template #prefix>Y</template>
</n-input-number>
</div>
<div class="mt-4 flex gap-4">
<n-input-number v-model:value="w">
<template #prefix>W</template>
</n-input-number>
<n-input-number v-model:value="h">
<template #prefix>H</template>
</n-input-number>
</div>
</template>
<script setup lang="ts">
import DigitalAudio from './digital-audio.vue'
import DigitalHuman from './digital-human.vue'
import DigitalPosition from './digital-position.vue'
</script>
<template>
<n-tabs type="line" animated>
<n-tab-pane name="human" tab="選擇">
<DigitalHuman />
</n-tab-pane>
<n-tab-pane name="position" tab="位置">
<DigitalPosition />
</n-tab-pane>
<n-tab-pane name="audio" tab="聲音">
<DigitalAudio />
</n-tab-pane>
</n-tabs>
</template>
<script setup lang="ts">
import { useDigitalCreationStore } from '@/store/modules/creation'
const digitalCreationStore = useDigitalCreationStore()
</script>
<template>
<div class="flex flex-col overflow-hidden rounded-2xl">
<div class="flex-1 overflow-hidden bg-gray-200">
<div class="relative mx-auto aspect-[9/16] h-full bg-green-50">
<img
v-show="digitalCreationStore.backgroundImageUrl"
:src="digitalCreationStore.backgroundImageUrl"
class="absolute h-full w-full object-cover"
/>
</div>
</div>
<div class="flex bg-white p-4">
<div class="flex-1 text-lg">00:11:22</div>
<div class="flex flex-1 justify-center">
<CustomIcon class="cursor-pointer text-2xl" icon="ph:play" />
</div>
<div class="flex flex-1 items-center justify-end gap-4">
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" />
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:fullscreen-line" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
</script>
<template>
<div class="rounded-2xl bg-white p-6">
<div class="pb-2">脚本</div>
<n-input
v-model:value="value"
type="textarea"
:maxlength="2000"
show-count
clearable
:autosize="{ minRows: 1, maxRows: 10 }"
/>
</div>
</template>
<script setup lang="ts">
import Layout from './layout/index.vue'
</script>
<template>
<Layout />
</template>
<script setup lang="ts">
import { ref } from 'vue'
const showExportModal = ref(false)
const exportForm = ref({
name: '',
ratio: '',
range: '',
format: '',
})
const ratioList = [
{ value: '720', label: '720p' },
{ value: '1080', label: '1080p' },
]
const rangeList = [
{ value: 'all', label: '全部' },
{ value: 'digital', label: '僅數字人(透明背景)' },
]
const formatList = [{ value: 'mp4', label: 'MP4' }]
function confirmExport() {
showExportModal.value = false
}
</script>
<template>
<header class="flex h-14 items-center justify-between bg-white px-4">
<div class="flex cursor-pointer items-center">
<CustomIcon class="text-lg" icon="mingcute:left-line" />
<span>返回</span>
</div>
<div class="flex items-center">
<div class="flex items-center gap-4">
<div class="flex items-center gap-2">
<CustomIcon class="text-green" icon="ep:success-filled" />
<span>已自動保存</span>
</div>
<n-button class="!rounded-md"> 保存爲草稿 </n-button>
<n-button class="!rounded-md" type="info" @click="showExportModal = true"> 導出視頻 </n-button>
</div>
</div>
</header>
<n-modal
v-model:show="showExportModal"
preset="dialog"
title="導出視頻"
positive-text="導出"
negative-text="取消"
@positive-click="confirmExport"
@negative-click="showExportModal = false"
>
<n-form ref="formRef" :label-width="120" :model="exportForm" label-placement="left">
<n-form-item label="視頻名稱" required>
<n-input v-model:value="exportForm.name" placeholder="請輸入視頻名稱" />
</n-form-item>
<n-form-item label="視頻分辨率" required>
<n-radio-group v-model:value="exportForm.ratio" name="ratio">
<n-space>
<n-radio v-for="ratio in ratioList" :key="ratio.value" :value="ratio.value">
{{ ratio.label }}
</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="導出範圍" required>
<n-radio-group v-model:value="exportForm.range" name="range">
<n-space>
<n-radio v-for="range in rangeList" :key="range.value" :value="range.value">
{{ range.label }}
</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
<n-form-item label="視頻格式" required>
<n-radio-group v-model:value="exportForm.format" name="format">
<n-space>
<n-radio v-for="format in formatList" :key="format.value" :value="format.value">
{{ format.label }}
</n-radio>
</n-space>
</n-radio-group>
</n-form-item>
</n-form>
</n-modal>
</template>
<script setup lang="ts">
import { fetchDigitalHumanTemplateStatus } from '@/apis/digital-creation'
import { DigitalTemplate } from '@/store/types/creation'
import HeaderBar from './header-bar.vue'
import MainContent from './main-content.vue'
import SideBar from './side-bar.vue'
import { onMounted } from 'vue'
import { useDigitalCreationStore } from '@/store/modules/creation'
const digitalCreationStore = useDigitalCreationStore()
onMounted(() => {
getDigitalImageList(1)
})
async function getDigitalImageList(id: number) {
const res = await fetchDigitalHumanTemplateStatus<DigitalTemplate>(id)
if (res.code === 0) {
digitalCreationStore.updateDigitalCreation(res.data)
}
}
</script>
<template>
<div class="h-screen bg-[#f3f4fb]">
<n-layout content-class="layout-wrapper-content" class="h-full !bg-transparent">
<n-layout-header class="!bg-transparent">
<HeaderBar />
</n-layout-header>
<n-layout has-sider class="flex-1 !bg-transparent p-4">
<n-layout-content class="rounded-2xl !bg-transparent">
<MainContent />
</n-layout-content>
<n-layout-sider class="!bg-transparent" width="420">
<SideBar />
</n-layout-sider>
</n-layout>
</n-layout>
</div>
</template>
<style lang="scss" scoped>
:deep(.layout-wrapper-content) {
@apply flex flex-col;
}
</style>
<script setup lang="ts">
import PriviewContent from '../components/preview-content.vue'
import ScriptContent from '../components/script-content.vue'
</script>
<template>
<main class="flex h-full flex-col gap-4">
<PriviewContent class="flex-1" />
<ScriptContent />
</main>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import BackgroundSetting from '../components/background/background-setting.vue'
import CaptionSetting from '../components/caption/caption-setting.vue'
import DigitalSetting from '../components/digital/digital-setting.vue'
const value = ref('')
const barList = [
{
key: 'digital',
label: '數字人',
icon: 'icon-park-outline:robot',
},
{
key: 'background',
label: '背景',
icon: 'icon-park-outline:background-color',
},
{
key: 'caption',
label: '字幕',
icon: 'icon-park-outline:text-message',
},
]
</script>
<template>
<section class="h-full pl-4">
<div class="flex h-full rounded-2xl bg-white">
<div class="flex-1 overflow-hidden px-4 py-2">
<DigitalSetting v-if="value === 'digital'" />
<BackgroundSetting v-if="value === 'background'" />
<CaptionSetting v-if="value === 'caption'" />
</div>
<VerticalTabs v-model:value="value" class="border-l" :list="barList"></VerticalTabs>
</div>
</section>
</template>
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