Commit 7aa80c52 authored by Dazzle Wu's avatar Dazzle Wu

feat: 横屏视频生成

parent eec5ddc5
......@@ -56,7 +56,7 @@ export function fetchTimbreByExample<T>(condition: string) {
}
// 根据草稿id获取草稿的配置信息
export function fetchDraftConfigById<T>(id: number) {
export function fetchDraftConfigById<T>(id: string) {
return request.post<T>(`/bizDigitalHumanMemberDraftConfigRest/getById.json?id=${id}`)
}
......@@ -65,6 +65,11 @@ export function saveDraftConfig<T>(payload: object) {
return request.post<T>('/bizDigitalHumanMemberDraftConfigRest/saveOrUpdate.json', payload)
}
// 导出视频需要消耗灵豆
export function consumeUniversalCurrency<T>(audioUrl: string) {
return request.post<T>(`/aiDigitalHumanTaskRest/consumeUniversalCurrency.json?audioUrl=${audioUrl}`)
}
// 导出视频到我的作品
export function createDigitalHumanVideoTask<T>(payload: object) {
return request.post<T>('/aiDigitalHumanTaskRest/createDigitalHumanVideoTask.json', payload, { timeout: 12000 })
......
......@@ -3,3 +3,7 @@ import { request } from '@/utils/request'
export function fetchGetTaskList<T>(payload: object) {
return request.post<T>('/bizDigitalHumanMemberTaskStatusRest/getMemberTaskStatusList.json', payload)
}
export function fetchGetTaskConfig<T>(taskConfigId: string) {
return request.post<T>(`/bizDigitalHumanMemberTaskConfigRest/getById.json?id=${taskConfigId}`)
}
......@@ -6,6 +6,6 @@ export function fetchDigitalHumanTemplateStatusList<T>(payload: object) {
}
// 根据ID获取推荐模板信息
export function fetchDigitalHumanTemplateStatus<T>(id: number) {
export function fetchDigitalHumanTemplateStatus<T>(id: string) {
return request.post<T>(`/aiDigitalHumanTemplateStatusRest/getDigitalHumanTemplateStatus.json?id=${id}`)
}
......@@ -3,12 +3,17 @@ import Creation from '@/views/creation/creation.vue'
export default [
{
path: '/creation/:templateId/:draftId?',
path: '/creation',
name: 'Creation',
meta: {
rank: 1001,
title: '数字人创作',
},
component: Creation,
props: (route) => ({
templateId: route.query.templateId,
draftId: route.query.draftId,
taskConfigId: route.query.taskConfigId,
}),
},
] as RouteRecordRaw[]
......@@ -5,6 +5,7 @@ function defaultAudioSetting(): AudioConfig {
return {
langType: LangType.CANTONESE,
voiceType: VoiceType.CANTONESE_FEMALE,
consumption: 0,
}
}
......@@ -24,6 +25,10 @@ export const useAudioSettingStore = defineStore('audio-setting-store', {
this.voiceType = voiceType
},
setConsumption(consumption: number) {
this.consumption = consumption
},
updateAUdioSetting(audioSetting: AudioConfig) {
this.$state = { ...this.$state, ...audioSetting }
},
......
......@@ -25,6 +25,7 @@ export enum VoiceType {
export interface AudioConfig {
langType: LangType
voiceType: VoiceType
consumption: number
}
export interface DigitalImageItem {
......
......@@ -24,6 +24,7 @@ export interface DigitalTemplate {
templateName: string
taskType: TaskType
requestId: string | null
digitalHumanImageUrl: string | null
inputImageUrl: string | null
driveType: DriveType
text: string
......@@ -32,7 +33,7 @@ export interface DigitalTemplate {
speed: string
volume: string
pitch: string
}
} | null
inputAudioUrl: string | null
callbackUrl: string | null
figureId: string
......@@ -40,7 +41,7 @@ export interface DigitalTemplate {
width: number
height: number
transparent: boolean
}
} | null
dhParams: {
cameraId: number | null
position: {
......@@ -49,11 +50,11 @@ export interface DigitalTemplate {
w: number
h: number
}
}
} | null
subtitleParams: {
subtitlePolicy: string
enabled: boolean
}
} | null
backgroundImageUrl: string | null
autoAnimoji: boolean
enablePalindrome: boolean
......@@ -61,9 +62,9 @@ export interface DigitalTemplate {
title: string | null
logoParams: {
logoUrl: string | null
}
} | null
bgmParams: {
bgmUrl: string | null
}
} | null
materialUrl: string | null
}
......@@ -7,7 +7,7 @@ import {
} from '@/apis/digital-creation'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { DigitalImageItem, ImageType } from '@/store/types/creation'
import { onMounted, ref, watch } from 'vue'
import { onMounted, ref } from 'vue'
import DigitalCard from './digital-human-card.vue'
const digitalCreationStore = useDigitalCreationStore()
......@@ -18,16 +18,6 @@ const allImageList = ref<DigitalImageItem[]>([])
const showAll = ref(false)
const searchName = ref('')
watch(
() => [digitalCreationStore.figureId, allImageList.value.length],
([figureId, len]) => {
if (!digitalCreationStore.inputImageUrl && figureId && len) {
const imageUrl = allImageList.value.find((i) => i.figureId === figureId)?.imageUrl
imageUrl && digitalCreationStore.setInputImageUrl(imageUrl)
}
},
)
onMounted(() => {
getDigitalImageList()
})
......
......@@ -21,20 +21,40 @@ const audioSetting = ref<AudioSetting>({
sampleRate: 16000,
content: '',
voiceType: VoiceType.CANTONESE_FEMALE,
speed: 5,
speed: -0.5,
volume: 5,
pitch: 5,
})
const ttsSpeedMarks: { [speed: string]: number } = {
'3': -1,
'4': -0.8,
'5': -0.5,
'6': 0,
'7': 1,
}
const resizeObserver = new ResizeObserver((entries) => {
const { contentRect } = entries[0]
previewContentWidth.value = contentRect.width
previewContentHeight.value = contentRect.height
})
const digitalHumanWidth = computed(() => (digitalCreationStore.w * previewContentWidth.value!) / 1080)
const digitalHumanHeight = computed(() => (digitalCreationStore.h * previewContentHeight.value!) / 1920)
const digitalHumanLeft = computed(() => (digitalCreationStore.x * previewContentWidth.value!) / 1080)
const digitalHumanTop = computed(() => (digitalCreationStore.y * previewContentHeight.value!) / 1920)
const isLandscape = computed(() => digitalCreationStore.width > digitalCreationStore.height)
const digitalHumanWidth = computed(
() => (digitalCreationStore.w * previewContentWidth.value) / (isLandscape.value ? 1920 : 1080),
)
const digitalHumanHeight = computed(
() => (digitalCreationStore.h * previewContentHeight.value) / (isLandscape.value ? 1080 : 1920),
)
const digitalHumanLeft = computed(
() => (digitalCreationStore.x * previewContentWidth.value) / (isLandscape.value ? 1920 : 1080),
)
const digitalHumanTop = computed(
() => (digitalCreationStore.y * previewContentHeight.value) / (isLandscape.value ? 1080 : 1920),
)
const audioUrl = computed({
get() {
return digitalCreationStore.inputAudioUrl || ''
......@@ -87,7 +107,7 @@ function disconnectWebSocket() {
function sendDataToWebSocket() {
audioSetting.value.content = digitalCreationStore.text
audioSetting.value.voiceType = audioSettingStore.voiceType
audioSetting.value.speed = Number(digitalCreationStore.speed)
audioSetting.value.speed = ttsSpeedMarks[digitalCreationStore.speed]
audioSetting.value.volume = Number(digitalCreationStore.volume)
audioSetting.value.pitch = Number(digitalCreationStore.pitch)
websocket.send(JSON.stringify(audioSetting.value))
......@@ -106,12 +126,13 @@ function controlAudio() {
if (
audioSetting.value.content !== digitalCreationStore.text ||
audioSetting.value.voiceType !== audioSettingStore.voiceType ||
audioSetting.value.speed !== Number(digitalCreationStore.speed) ||
audioSetting.value.speed !== ttsSpeedMarks[digitalCreationStore.speed] ||
audioSetting.value.volume !== Number(digitalCreationStore.volume) ||
(audioSettingStore.langType === LangType.MANDARIN &&
audioSetting.value.pitch !== Number(digitalCreationStore.pitch))
) {
audioData.value = ''
audioUrl.value = ''
return
}
......@@ -123,7 +144,11 @@ function controlAudio() {
<template>
<div class="flex flex-col overflow-hidden rounded-2xl">
<div class="flex-1 overflow-hidden bg-gray-200">
<div ref="previewContent" class="relative mx-auto aspect-[9/16] h-full overflow-hidden bg-gray-100">
<div
ref="previewContent"
class="relative mx-auto h-full overflow-hidden bg-gray-100"
:class="isLandscape ? 'aspect-[16/9]' : 'aspect-[9/16]'"
>
<img
v-show="digitalCreationStore.backgroundImageUrl"
:src="digitalCreationStore.backgroundImageUrl!"
......@@ -145,20 +170,20 @@ function controlAudio() {
<div class="flex h-12 bg-white px-4">
<!-- <div class="flex flex-1 items-center text-lg">00:11:22</div> -->
<div class="flex flex-1 items-center justify-center">
<CustomIcon
v-if="audioData && audioUrl"
class="cursor-pointer text-2xl"
:icon="audioPlaying ? 'ph:pause' : 'ph:play'"
@click="controlAudio"
/>
<n-button
v-if="!audioData"
v-else
type="info"
:loading="isConnected"
:disabled="!digitalCreationStore.text"
@click="generatePreview"
>生成預覽</n-button
>
<CustomIcon
v-else
class="cursor-pointer text-2xl"
:icon="audioPlaying ? 'ph:pause' : 'ph:play'"
@click="controlAudio"
/>
</div>
<!-- <div class="flex flex-1 items-center justify-end gap-4">
<CustomIcon class="cursor-pointer text-lg" icon="mingcute:volume-line" />
......
<script setup lang="ts">
import { createDigitalHumanVideoTask, saveDraftConfig } from '@/apis/digital-creation'
import { consumeUniversalCurrency, createDigitalHumanVideoTask, saveDraftConfig } from '@/apis/digital-creation'
import { fetchUniversalCurrency } from '@/apis/user'
import { useAudioSettingStore } from '@/store/modules/audio-setting'
import { useDigitalCreationStore } from '@/store/modules/creation'
import { BaseVideoTask, DraftConfig } from '@/store/types/creation'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const audioSettingStore = useAudioSettingStore()
const digitalCreationStore = useDigitalCreationStore()
const draftName = ref('')
const userCurrency = ref(0)
const editDraftName = ref(false)
const autoSaveSuccess = ref(false)
const showExportModal = ref(false)
const isExporting = ref(false)
const ratioValue = ref(720)
// const ratioValue = ref(720)
const ratioList = [
{ value: 720, label: '720p' },
{ value: 1080, label: '1080p' },
......@@ -24,26 +27,52 @@ const transparent = [
]
let timer: any
watch(
() => ratioValue.value,
(newVal) => {
if (newVal === 1080) {
digitalCreationStore.setWidth(1080)
digitalCreationStore.setHeight(1920)
const isLandscape = computed(() => digitalCreationStore.width > digitalCreationStore.height)
const ratioValue = computed({
get() {
return Math.max(digitalCreationStore.width, digitalCreationStore.height) === 1920 ? 1080 : 720
},
set(value) {
if (value === 1080) {
digitalCreationStore.setWidth(isLandscape.value ? 1920 : 1080)
digitalCreationStore.setHeight(isLandscape.value ? 1080 : 1920)
} else {
digitalCreationStore.setWidth(720)
digitalCreationStore.setHeight(1280)
digitalCreationStore.setWidth(isLandscape.value ? 1280 : 720)
digitalCreationStore.setHeight(isLandscape.value ? 720 : 1280)
}
},
})
const consumption = computed({
get() {
return audioSettingStore.consumption
},
set(value) {
audioSettingStore.setConsumption(value)
},
})
const enableExport = computed(() => {
return digitalCreationStore.id && digitalCreationStore.backgroundImageUrl && digitalCreationStore.inputAudioUrl
})
watch(
() => digitalCreationStore.inputAudioUrl,
(newVal) => {
newVal && calculateConsumption()
},
)
onMounted(() => {
getUniversalCurrency()
timer = setInterval(() => {
!isExporting.value && saveDraft()
}, 5000)
})
onUnmounted(() => {
saveDraft()
clearInterval(timer)
timer = null
})
......@@ -73,59 +102,49 @@ async function saveDraft(autoSave: boolean = true) {
}
}
async function calculateConsumption() {
const res = await consumeUniversalCurrency<number>(digitalCreationStore.inputAudioUrl!)
if (res.code === 0) {
consumption.value = res.data
}
}
// 导出视频
function confirmExport() {
if (!digitalCreationStore.videoName) {
window.$message.error('請輸入視頻名稱')
return false
}
if (!userCurrency.value) {
window.$message.error('餘額不足')
return false
}
createBaseVideoTask()
}
async function createBaseVideoTask() {
isExporting.value = true
const balance = await getUniversalCurrency()
if (!balance) {
isExporting.value = false
window.$message.error('餘額不足')
return
}
if (!digitalCreationStore.id) {
isExporting.value = false
window.$message.error('請先保存視頻為草稿')
return
}
if (!digitalCreationStore.backgroundImageUrl) {
isExporting.value = false
window.$message.error('請選擇背景圖片')
return
}
if (!digitalCreationStore.inputAudioUrl) {
isExporting.value = false
window.$message.error('請生成預覽音頻')
return
}
const payload: BaseVideoTask = {
draftId: digitalCreationStore.id,
draftId: digitalCreationStore.id!,
videoName: digitalCreationStore.videoName,
width: ratioValue.value === 720 ? 720 : 1080,
height: ratioValue.value === 720 ? 1280 : 1920,
width: digitalCreationStore.width,
height: digitalCreationStore.height,
transparent: digitalCreationStore.transparent,
videoType: 'mp4',
audioUrl: digitalCreationStore.inputAudioUrl,
audioUrl: digitalCreationStore.inputAudioUrl!,
}
const res = await createDigitalHumanVideoTask(payload)
if (res.code === 0) {
window.$message.success('導出成功')
showExportModal.value = false
router.push('/work/videos')
router.push({ name: 'WorkVideos' })
}
}
async function getUniversalCurrency() {
const res = await fetchUniversalCurrency<number>()
if (res.code === 0) {
return res.data
userCurrency.value = res.data
}
}
</script>
......@@ -157,7 +176,7 @@ async function getUniversalCurrency() {
<span>已自動保存</span>
</div>
<n-button class="!rounded-md" @click="saveDraft(false)"> 保存爲草稿 </n-button>
<n-button class="!rounded-md" type="info" :disabled="!digitalCreationStore.id" @click="showExportModal = true">
<n-button class="!rounded-md" type="info" :disabled="!enableExport" @click="showExportModal = true">
導出視頻
</n-button>
</div>
......@@ -165,7 +184,7 @@ async function getUniversalCurrency() {
</header>
<n-modal v-model:show="showExportModal" preset="dialog" title="導出視頻" @after-leave="isExporting = false">
<n-form ref="formRef" :label-width="120" label-placement="left">
<n-form ref="formRef" :label-width="100" label-placement="left">
<n-form-item label="視頻名稱" required>
<n-input v-model:value="digitalCreationStore.videoName" placeholder="請輸入視頻名稱" />
</n-form-item>
......@@ -190,6 +209,12 @@ async function getUniversalCurrency() {
<n-form-item label="視頻格式" required>
<n-radio value="mp4" checked> MP4 </n-radio>
</n-form-item>
<n-form-item label="剩餘靈豆">
<span>{{ userCurrency }}</span>
</n-form-item>
<n-form-item label="生成視頻消耗靈豆">
<span>{{ consumption }}</span>
</n-form-item>
</n-form>
<template #action>
<n-button @click="showExportModal = false">取消</n-button>
......
......@@ -5,23 +5,32 @@ import { useDigitalCreationStore } from '@/store/modules/creation'
import { DraftConfig, LangType } from '@/store/types/creation'
import { DigitalTemplate } from '@/store/types/template'
import { onMounted } from 'vue'
import { useRoute } from 'vue-router'
import HeaderBar from './header-bar.vue'
import MainContent from './main-content.vue'
import SideBar from './side-bar.vue'
import { fetchGetTaskConfig } from '@/apis/opus'
interface Props {
templateId?: string
draftId?: string
taskConfigId?: string
}
const props = defineProps<Props>()
const route = useRoute()
const digitalCreationStore = useDigitalCreationStore()
onMounted(() => {
if (route.params.draftId) {
getDraft(Number(route.params.draftId))
} else {
getDigitalTemplate(Number(route.params.templateId))
if (props.templateId) {
getDigitalTemplate(props.templateId)
} else if (props.draftId) {
getDraft(props.draftId)
} else if (props.taskConfigId) {
getTaskConfig(props.taskConfigId)
}
})
async function getDigitalTemplate(id: number) {
async function getDigitalTemplate(id: string) {
const res = await fetchDigitalHumanTemplateStatus<DigitalTemplate>(id)
if (res.code === 0) {
const digitalTemplate = res.data
......@@ -33,26 +42,26 @@ async function getDigitalTemplate(id: number) {
videoDuration: null,
taskType: digitalTemplate.taskType,
requestId: digitalTemplate.requestId,
inputImageUrl: null,
inputImageUrl: digitalTemplate.digitalHumanImageUrl,
driveType: digitalTemplate.driveType,
text: digitalTemplate.text,
person: digitalTemplate.ttsParams.person,
speed: digitalTemplate.ttsParams.speed,
volume: digitalTemplate.ttsParams.volume,
pitch: digitalTemplate.ttsParams.pitch,
person: digitalTemplate.ttsParams?.person || null,
speed: digitalTemplate.ttsParams?.speed || '5',
volume: digitalTemplate.ttsParams?.volume || '5',
pitch: digitalTemplate.ttsParams?.pitch || '5',
inputAudioUrl: digitalTemplate.inputAudioUrl,
callbackUrl: digitalTemplate.callbackUrl,
figureId: digitalTemplate.figureId,
width: 720,
height: 1280,
transparent: digitalTemplate.videoParams.transparent ? 'Y' : 'N',
cameraId: digitalTemplate.dhParams.cameraId,
x: digitalTemplate.dhParams.position.x || 0,
y: digitalTemplate.dhParams.position.y || 0,
w: digitalTemplate.dhParams.position.w || 0,
h: digitalTemplate.dhParams.position.h || 0,
subtitlePolicy: digitalTemplate.subtitleParams.subtitlePolicy,
enabled: digitalTemplate.subtitleParams.enabled ? 'Y' : 'N',
width: digitalTemplate.videoParams?.width || 1080,
height: digitalTemplate.videoParams?.height || 1920,
transparent: digitalTemplate.videoParams?.transparent ? 'Y' : 'N',
cameraId: digitalTemplate.dhParams?.cameraId || null,
x: digitalTemplate.dhParams?.position.x || 0,
y: digitalTemplate.dhParams?.position.y || 0,
w: digitalTemplate.dhParams?.position.w || 0,
h: digitalTemplate.dhParams?.position.h || 0,
subtitlePolicy: digitalTemplate.subtitleParams?.subtitlePolicy || 'SRT',
enabled: digitalTemplate.subtitleParams?.enabled ? 'Y' : 'N',
backgroundImageUrl: digitalTemplate.backgroundImageUrl,
autoAnimoji: digitalTemplate.autoAnimoji ? 'Y' : 'N',
enablePalindrome: digitalTemplate.enablePalindrome ? 'Y' : 'N',
......@@ -67,12 +76,19 @@ async function getDigitalTemplate(id: number) {
}
}
async function getDraft(id: number) {
async function getDraft(id: string) {
const res = await fetchDraftConfigById<DraftConfig>(id)
if (res.code === 0) {
digitalCreationStore.updateDigitalCreation(res.data)
}
}
async function getTaskConfig(id: string) {
const res = await fetchGetTaskConfig<DraftConfig>(id)
if (res.code === 0) {
digitalCreationStore.updateDigitalCreation(res.data)
}
}
</script>
<template>
......
<script setup lang="ts">
import { fetchDraftsList } from '@/apis/drafts'
import { DraftConfig } from '@/store/types/creation'
import { TaskType } from '@/store/types/template'
import { Right } from '@icon-park/vue-next'
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const recentCreationList = ref<DraftConfig[]>([])
onMounted(() => {
getDraftsList()
})
async function getDraftsList() {
const res = await fetchDraftsList<DraftConfig[]>({ pagingInfo: { pageNo: 1, pageSize: 7 } })
if (res.code === 0) {
recentCreationList.value = res.data
}
}
function formatTaskType(taskType: TaskType) {
switch (taskType) {
case TaskType.IMAGE_VIDEO: {
return '照片數字人'
}
case TaskType.BASE_VIDEO: {
return '基礎數字人'
}
case TaskType.ADVANCED_VIDEO: {
return '高級數字人'
}
}
}
function handleGoToDrafts() {
router.push('/work/draft')
}
function handleClickDraft(item: DraftConfig) {
router.push(`/creation/${item.templateId}/${item.id}`)
}
</script>
<template>
<div class="rounded-[16px] bg-white px-[24px] pb-[16px] pt-[24px]">
<div class="flex justify-between text-[18px] text-[#000]">
最近創作
<div class="flex h-[22px] cursor-pointer items-center text-[14px] text-[#5b647a]" @click="handleGoToDrafts">
<div>查看全部</div>
<Right theme="outline" size="16" fill="#5b647a" />
</div>
</div>
<div class="flex flex-wrap justify-between">
<div v-for="(item, index) in recentCreationList" :key="index">
<div class="mt-[16px] overflow-hidden rounded-[10px]" @click="handleClickDraft(item)">
<div class="relative flex h-[145px] w-[145px] items-center justify-center bg-[#f0f0f0]">
<img
:src="item.coverUrl!"
class="absolute h-full w-full scale-100 cursor-pointer rounded-[12px] object-cover blur-[32px] filter transition-transform duration-300 ease-in-out"
/>
<img
:src="item.coverUrl!"
class="hover:scale-104 absolute inset-0 aspect-[1] cursor-pointer object-contain transition-transform duration-300 ease-in-out"
/>
<div
class="absolute bottom-[8px] left-[8px] cursor-default rounded-[4px] bg-[#000000]/[.5] px-[6px] py-[2px] text-[12px] text-[#FFFFFF]"
>
{{ formatTaskType(item.taskType) }}
</div>
</div>
</div>
<n-ellipsis class="mt-[12px] cursor-default text-[#151b26]" style="max-width: 150px">
{{ item.draftName }}
</n-ellipsis>
</div>
</div>
</div>
</template>
<style lang="scss">
.n-card {
max-width: 146px;
}
.n-card .n-card-cover {
border-radius: 12px !important;
}
.n-popover__content {
font-size: 12px;
}
.n-popover:not(.n-popover--raw) {
color: #000;
background-color: #fff !important;
}
.n-card.n-card--bordered {
border: none !important;
}
.n-popover-shared .n-popover-arrow-wrapper .n-popover-arrow {
background-color: #fff !important ;
}
</style>
<script setup lang="ts">
import { fetchDraftsList } from '@/apis/drafts'
import { TaskType } from '@/store/types/template'
import { formatDateTime } from '@/utils/date-formatter'
import { Right } from '@icon-park/vue-next'
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { formatDateTime } from '@/utils/date-formatter'
interface CreationTemplateInfoItem {
id: number
taskType: string
taskType: TaskType
coverUrl: string
modifiedTime: string
draftName: string
......@@ -47,13 +48,13 @@ function getRecentCreationList() {
})
}
function creationTypeFormatter(type: string) {
function creationTypeFormatter(type: TaskType) {
switch (type) {
case 'IMAGE_VIDEO':
case TaskType.IMAGE_VIDEO:
return '圖片視頻'
case 'BASE_VIDEO':
case TaskType.BASE_VIDEO:
return '基礎視頻'
case 'ADVANCED_VIDEO':
case TaskType.ADVANCED_VIDEO:
return '精編視頻'
default:
return '其他內容'
......@@ -61,9 +62,15 @@ function creationTypeFormatter(type: string) {
}
function handleGoToDrafts() {
// router.push('/work/draft')
router.push({ name: 'WorkDraft' })
}
function handleToCreation(draftId: number) {
router.push({
name: 'Creation',
query: { draftId },
})
}
</script>
<template>
......@@ -88,6 +95,7 @@ function handleGoToDrafts() {
>
<div
class="relative mb-[12px] h-[145px] w-[145px] cursor-pointer overflow-hidden rounded-[12px] bg-[#f3f4fb]"
@click="handleToCreation(templateInfoItem.id)"
>
<img
class="z-1 relative h-full w-full object-contain transition-[scale] duration-300 ease-in-out hover:scale-110"
......
<script setup lang="ts">
import { fetchDigitalHumanTemplateStatusList } from '@/apis/template.ts'
import { DigitalTemplate } from '@/store/types/template.ts'
import { DigitalTemplate, TemplateType } from '@/store/types/template.ts'
import { useInfiniteScroll } from '@vueuse/core'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
......@@ -17,9 +17,9 @@ const templateBottomEl = ref<HTMLElement | null>(null)
const templateClassify = [
{ value: '', label: '熱門' },
{ value: 'FINANCIAL_MARKETING', label: '理財營銷' },
{ value: 'EDUCATION_LEARNING', label: '教育學習' },
{ value: 'FESTIVAL_HOTS_SPOTS', label: '節日熱點' },
{ value: TemplateType.FINANCIAL_MARKETING, label: '理財營銷' },
{ value: TemplateType.EDUCATION_LEARNING, label: '教育學習' },
{ value: TemplateType.FESTIVAL_HOTS_SPOTS, label: '節日熱點' },
]
const templateList = ref<DigitalTemplate[]>([])
......@@ -73,7 +73,10 @@ function handleOpenModal(template: DigitalTemplate) {
}
function handleToCreation(template: DigitalTemplate) {
router.push(`/creation/${template.id}`)
router.push({
name: 'Creation',
query: { templateId: template.id },
})
}
</script>
......
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