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

feat: 横屏视频生成

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