Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
D
digitalPerson-fe
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
digitalPerson
digitalPerson-fe
Commits
bfc4695a
Commit
bfc4695a
authored
Sep 28, 2024
by
Dazzle Wu
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 最近创作和推荐模板联调
parent
6c2fc5cb
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
350 additions
and
220 deletions
+350
-220
digital-creation.ts
src/apis/digital-creation.ts
+8
-8
template.ts
src/apis/template.ts
+13
-0
creation.ts
src/store/modules/creation.ts
+8
-2
creation.ts
src/store/types/creation.ts
+8
-64
template.ts
src/store/types/template.ts
+69
-0
background-setting.vue
...ews/creation/components/background/background-setting.vue
+56
-17
digital-audio-card.vue
src/views/creation/components/digital/digital-audio-card.vue
+2
-2
digital-audio.vue
src/views/creation/components/digital/digital-audio.vue
+38
-18
header-bar.vue
src/views/creation/layout/header-bar.vue
+47
-21
index.vue
src/views/creation/layout/index.vue
+7
-4
recent-creation.vue
src/views/home/components/recent-creation.vue
+53
-32
recommend-template.vue
src/views/home/components/recommend-template.vue
+39
-44
template-preview-modal.vue
src/views/home/components/template-preview-modal.vue
+2
-8
No files found.
src/apis/digital-creation.ts
View file @
bfc4695a
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'
)
...
...
@@ -25,9 +20,14 @@ export function fetchBackgroundImage<T>() {
return
request
.
post
<
T
>
(
'/aiDigitalHumanImageRest/getBackgroundImageList.json'
)
}
// 模糊查询背景图
export
function
fetchSearchBackgroundImageList
<
T
>
(
imageName
:
string
)
{
return
request
.
post
<
T
>
(
`/aiDigitalHumanImageRest/searchBackgroundImageList.json?imageName=
${
imageName
}
`
)
}
// 上传背景图片
export
function
uploadImageFile
<
T
>
(
imageName
:
string
,
formData
:
FormData
)
{
return
request
.
post
<
T
>
(
`/baiduDigitalHumanFileRest/uploadImageFile.json?imageName=
${
imageName
}
`
,
formData
,
{
return
request
.
post
<
T
>
(
`/baiduDigitalHumanFileRest/upload
Background
ImageFile.json?imageName=
${
imageName
}
`
,
formData
,
{
headers
:
{
'Content-Type'
:
'multipart/form-data'
},
timeout
:
12000
,
})
...
...
@@ -41,7 +41,7 @@ export function deleteBackgroundImageById<T>(id: number) {
// 根据人物名称分页获取人物信息
export
function
fetchInfoByImageName
<
T
>
(
imageName
:
string
)
{
return
request
.
post
<
T
>
(
`/bizDigitalHumanImageRest/findByImageName.json?imageName=
${
imageName
}
`
,
{
pagingInfo
:
{
pageNo
:
1
,
pageSize
:
9999
},
pagingInfo
:
{
pageNo
:
1
,
pageSize
:
1000
},
})
}
...
...
@@ -67,5 +67,5 @@ export function saveDraftConfig<T>(payload: object) {
// 导出视频到我的作品
export
function
createDigitalHumanVideoTask
<
T
>
(
payload
:
object
)
{
return
request
.
post
<
T
>
(
'/aiDigitalHumanTaskRest/createDigitalHumanVideoTask.json'
,
payload
)
return
request
.
post
<
T
>
(
'/aiDigitalHumanTaskRest/createDigitalHumanVideoTask.json'
,
payload
,
{
timeout
:
12000
}
)
}
src/apis/template.ts
0 → 100644
View file @
bfc4695a
import
{
request
}
from
'@/utils/request'
// 获取模板
export
function
fetchDigitalHumanTemplateStatusList
<
T
>
()
{
return
request
.
post
<
T
>
(
'/aiDigitalHumanTemplateStatusRest/getDigitalHumanTemplateStatusList.json?pageNo=1&pageSize=1000'
,
)
}
// 根据ID获取推荐模板信息
export
function
fetchDigitalHumanTemplateStatus
<
T
>
(
id
:
number
)
{
return
request
.
post
<
T
>
(
`/aiDigitalHumanTemplateStatusRest/getDigitalHumanTemplateStatus.json?id=
${
id
}
`
)
}
src/store/modules/creation.ts
View file @
bfc4695a
import
{
DraftConfig
,
DriveType
,
LangType
,
TaskType
}
from
'@/store/types/creation'
import
{
DraftConfig
,
LangType
}
from
'@/store/types/creation'
import
{
DriveType
,
TaskType
}
from
'@/store/types/template'
import
{
defineStore
}
from
'pinia'
function
defaultDigitalCreation
():
DraftConfig
{
...
...
@@ -7,6 +8,7 @@ function defaultDigitalCreation(): DraftConfig {
coverUrl
:
null
,
draftName
:
''
,
videoName
:
''
,
videoDuration
:
null
,
taskType
:
TaskType
.
BASE_VIDEO
,
requestId
:
null
,
inputImageUrl
:
null
,
...
...
@@ -37,7 +39,7 @@ function defaultDigitalCreation(): DraftConfig {
logoUrl
:
null
,
bgmUrl
:
null
,
materialUrl
:
null
,
pronunciationLanguage
L
:
LangType
.
CANTONESE
,
pronunciationLanguage
:
LangType
.
CANTONESE
,
}
}
...
...
@@ -49,6 +51,10 @@ export const useDigitalCreationStore = defineStore('digital-creation-store', {
state
:
():
DraftConfig
=>
getLocalState
(),
actions
:
{
setDraftName
(
draftName
:
string
)
{
this
.
draftName
=
draftName
},
setFigureId
(
figureId
:
string
)
{
this
.
figureId
=
figureId
},
...
...
src/store/types/creation.ts
View file @
bfc4695a
import
{
DriveType
,
TaskType
}
from
'./template'
export
enum
ImageType
{
THREE_D
=
'THREE_D'
,
TWO_D_BOUTIQUE
=
'TWO_D_BOUTIQUE'
,
TWO_D_FEW_SHOT
=
'TWO_D_FEW_SHOT'
,
}
export
enum
TaskType
{
IMAGE_VIDEO
=
'IMAGE_VIDEO'
,
BASE_VIDEO
=
'BASE_VIDEO'
,
ADVANCED_VIDEO
=
'ADVANCED_VIDEO'
,
}
export
enum
DriveType
{
TEXT
=
'TEXT'
,
VOICE
=
'VOICE'
,
export
enum
BackgroundImageType
{
PERSON
=
'PERSON'
,
PUBLIC
=
'PUBLIC'
,
}
export
enum
LangType
{
...
...
@@ -31,59 +27,6 @@ export interface AudioConfig {
voiceType
:
VoiceType
}
export
interface
DigitalTemplate
{
id
:
number
coverUrl
:
string
|
null
demonstrationGifUrl
:
string
|
null
demonstrationVideoUrl
:
string
|
null
templateType
:
string
templateName
:
string
taskType
:
TaskType
requestId
:
string
|
null
inputImageUrl
:
string
|
null
driveType
:
DriveType
text
:
string
ttsParams
:
{
person
:
string
|
null
speed
:
string
volume
:
string
pitch
:
string
}
inputAudioUrl
:
string
|
null
callbackUrl
:
string
|
null
figureId
:
string
videoParams
:
{
width
:
number
height
:
number
transparent
:
boolean
}
dhParams
:
{
cameraId
:
number
|
null
position
:
{
x
:
number
y
:
number
w
:
number
h
:
number
}
}
subtitleParams
:
{
subtitlePolicy
:
string
enabled
:
boolean
}
backgroundImageUrl
:
string
|
null
autoAnimoji
:
boolean
enablePalindrome
:
boolean
templateId
:
string
|
null
title
:
string
|
null
logoParams
:
{
logoUrl
:
string
|
null
}
bgmParams
:
{
bgmUrl
:
string
|
null
}
materialUrl
:
string
|
null
}
export
interface
DigitalImageItem
{
id
:
number
imageType
:
ImageType
...
...
@@ -94,7 +37,7 @@ export interface DigitalImageItem {
export
interface
BackgroundImageItem
{
id
:
number
imageSource
:
string
imageSource
:
BackgroundImageType
imageName
:
string
imageUrl
:
string
}
...
...
@@ -125,6 +68,7 @@ export interface DraftConfig {
coverUrl
:
string
|
null
draftName
:
string
videoName
:
string
videoDuration
:
number
|
null
taskType
:
TaskType
requestId
:
string
|
null
inputImageUrl
:
string
|
null
...
...
@@ -155,7 +99,7 @@ export interface DraftConfig {
logoUrl
:
string
|
null
bgmUrl
:
string
|
null
materialUrl
:
string
|
null
pronunciationLanguage
L
:
LangType
pronunciationLanguage
:
LangType
memberId
?:
number
modifiedTime
?:
string
}
...
...
src/store/types/template.ts
0 → 100644
View file @
bfc4695a
export
enum
TemplateType
{
FINANCIAL_MARKETING
=
'FINANCIAL_MARKETING'
,
// 理財營銷
EDUCATION_LEARNING
=
'EDUCATION_LEARNING'
,
// 教育學習
FESTIVAL_HOTS_SPOTS
=
'FESTIVAL_HOTS_SPOTS'
,
// 節日熱點
}
export
enum
TaskType
{
IMAGE_VIDEO
=
'IMAGE_VIDEO'
,
BASE_VIDEO
=
'BASE_VIDEO'
,
ADVANCED_VIDEO
=
'ADVANCED_VIDEO'
,
}
export
enum
DriveType
{
TEXT
=
'TEXT'
,
VOICE
=
'VOICE'
,
}
export
interface
DigitalTemplate
{
id
:
number
coverUrl
:
string
demonstrationGifUrl
:
string
demonstrationVideoUrl
:
string
templateType
:
TemplateType
templateName
:
string
taskType
:
TaskType
requestId
:
string
|
null
inputImageUrl
:
string
|
null
driveType
:
DriveType
text
:
string
ttsParams
:
{
person
:
string
|
null
speed
:
string
volume
:
string
pitch
:
string
}
inputAudioUrl
:
string
|
null
callbackUrl
:
string
|
null
figureId
:
string
videoParams
:
{
width
:
number
height
:
number
transparent
:
boolean
}
dhParams
:
{
cameraId
:
number
|
null
position
:
{
x
:
number
y
:
number
w
:
number
h
:
number
}
}
subtitleParams
:
{
subtitlePolicy
:
string
enabled
:
boolean
}
backgroundImageUrl
:
string
|
null
autoAnimoji
:
boolean
enablePalindrome
:
boolean
templateId
:
string
|
null
title
:
string
|
null
logoParams
:
{
logoUrl
:
string
|
null
}
bgmParams
:
{
bgmUrl
:
string
|
null
}
materialUrl
:
string
|
null
}
src/views/creation/components/background/background-setting.vue
View file @
bfc4695a
...
...
@@ -2,18 +2,25 @@
import
{
deleteBackgroundImageById
,
fetchBackgroundImage
,
fetch
InfoByImageName
,
fetch
SearchBackgroundImageList
,
uploadImageFile
,
}
from
'@/apis/digital-creation'
import
{
useDigitalCreationStore
}
from
'@/store/modules/creation'
import
{
BackgroundImageItem
}
from
'@/store/types/creation'
import
{
BackgroundImageItem
,
BackgroundImageType
}
from
'@/store/types/creation'
import
{
onMounted
,
ref
}
from
'vue'
const
digitalCreationStore
=
useDigitalCreationStore
()
const
imageList
=
ref
<
BackgroundImageItem
[]
>
([])
const
backgroundImageTypeList
=
[
{
value
:
BackgroundImageType
.
PERSON
,
label
:
'我的'
},
{
value
:
BackgroundImageType
.
PUBLIC
,
label
:
'背景庫'
},
]
const
currentBackgroundImageType
=
ref
(
BackgroundImageType
.
PERSON
)
const
personBackgroundImageList
=
ref
<
BackgroundImageItem
[]
>
([])
const
publicBackgroundImageList
=
ref
<
BackgroundImageItem
[]
>
([])
const
personBackgroundImageListLoaded
=
ref
(
Array
(
personBackgroundImageList
.
value
.
length
).
fill
(
false
))
const
publicBackgroundImageListLoaded
=
ref
(
Array
(
publicBackgroundImageList
.
value
.
length
).
fill
(
false
))
const
searchName
=
ref
(
''
)
const
uploadLoading
=
ref
(
false
)
const
loaded
=
ref
(
Array
(
imageList
.
value
.
length
).
fill
(
false
))
onMounted
(()
=>
{
getBackgroundImageList
()
...
...
@@ -22,18 +29,24 @@ onMounted(() => {
async
function
getBackgroundImageList
()
{
const
res
=
await
fetchBackgroundImage
<
BackgroundImageItem
[]
>
()
if
(
res
.
code
===
0
)
{
imageList
.
value
=
res
.
data
publicBackgroundImageList
.
value
=
res
.
data
.
filter
((
i
)
=>
i
.
imageSource
===
BackgroundImageType
.
PUBLIC
)
personBackgroundImageList
.
value
=
res
.
data
.
filter
((
i
)
=>
i
.
imageSource
===
BackgroundImageType
.
PERSON
)
}
}
function
handleUpdateBackgroundImageType
(
backgroundImageType
:
BackgroundImageType
)
{
currentBackgroundImageType
.
value
=
backgroundImageType
}
async
function
handleSearch
(
value
:
string
)
{
if
(
!
value
)
{
getBackgroundImageList
()
return
}
const
res
=
await
fetch
InfoByImageName
<
BackgroundImageItem
[]
>
(
value
)
const
res
=
await
fetch
SearchBackgroundImageList
<
BackgroundImageItem
[]
>
(
value
)
if
(
res
.
code
===
0
)
{
imageList
.
value
=
res
.
data
publicBackgroundImageList
.
value
=
res
.
data
.
filter
((
i
)
=>
i
.
imageSource
===
BackgroundImageType
.
PUBLIC
)
personBackgroundImageList
.
value
=
res
.
data
.
filter
((
i
)
=>
i
.
imageSource
===
BackgroundImageType
.
PERSON
)
}
}
...
...
@@ -92,7 +105,9 @@ function handleDelete(id: number) {
}
function
onImageLoaded
(
index
:
number
)
{
loaded
.
value
[
index
]
=
true
currentBackgroundImageType
.
value
===
BackgroundImageType
.
PUBLIC
?
(
publicBackgroundImageListLoaded
.
value
[
index
]
=
true
)
:
(
personBackgroundImageListLoaded
.
value
[
index
]
=
true
)
}
</
script
>
...
...
@@ -100,12 +115,25 @@ function onImageLoaded(index: number) {
<n-tabs
type=
"line"
animated
class=
"h-full"
>
<n-tab-pane
name=
"images"
tab=
"圖片"
class=
"h-full"
>
<div
class=
"h-full overflow-y-auto px-4 py-2"
>
<div
class=
"mb-4 flex items-center gap-4"
>
<n-button-group>
<n-button
v-for=
"backgroundImageTypeItem in backgroundImageTypeList"
:key=
"backgroundImageTypeItem.value"
:type=
"currentBackgroundImageType === backgroundImageTypeItem.value ? 'info' : 'default'"
class=
"text-xs! w-[70px]!"
@
click=
"handleUpdateBackgroundImageType(backgroundImageTypeItem.value)"
>
{{
backgroundImageTypeItem
.
label
}}
</n-button>
</n-button-group>
<n-input
v-model:value=
"searchName"
round
placeholder=
"搜索"
@
input=
"handleSearch"
>
<template
#
prefix
>
<CustomIcon
class=
"text-lg"
icon=
"mingcute:search-line"
/>
</
template
>
</n-input>
<div
class=
"h-4"
></div>
</div>
<n-grid
:x-gap=
"12"
:y-gap=
"12"
:cols=
"3"
>
<n-gi>
<n-spin
:show=
"uploadLoading"
>
...
...
@@ -119,8 +147,19 @@ function onImageLoaded(index: number) {
<
template
#
description
>
上傳中
</
template
>
</n-spin>
</n-gi>
<n-gi
v-for=
"(image, index) in imageList"
:key=
"index"
>
<n-spin
:show=
"!loaded[index]"
>
<n-gi
v-for=
"(image, index) in currentBackgroundImageType === BackgroundImageType.PUBLIC
? publicBackgroundImageList
: personBackgroundImageList"
:key=
"index"
>
<n-spin
:show=
"
currentBackgroundImageType === BackgroundImageType.PUBLIC
? !publicBackgroundImageListLoaded[index]
: !personBackgroundImageListLoaded[index]
"
>
<div
class=
"h-22 w-22 group relative cursor-pointer overflow-hidden rounded-lg border border-2 bg-gray-100"
:class=
"
...
...
@@ -135,7 +174,7 @@ function onImageLoaded(index: number) {
{{ image.imageName }}
</div>
<div
v-if=
"
image.id
"
v-if=
"
currentBackgroundImageType === BackgroundImageType.PERSON
"
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(image.id)"
>
...
...
src/views/creation/components/digital/digital-audio-card.vue
View file @
bfc4695a
...
...
@@ -41,8 +41,8 @@ function playAudio() {
<div
class=
"max-w-32 truncate"
>
{{
value
?.
name
}}
</div>
<CustomIcon
class=
"cursor-pointer text-lg"
icon=
"mingcute:volume-line"
@
click
.
stop
.
prevent=
"playAudio"
/>
</div>
<div
class=
"flex gap-
2
"
>
<n-tag
v-for=
"(style, index) in value?.style"
:key=
"index"
type=
"warning"
round
>
{{
style
}}
</n-tag>
<div
class=
"flex gap-
1
"
>
<n-tag
v-for=
"(style, index) in value?.style"
:key=
"index"
type=
"warning"
size=
"tiny"
round
>
{{
style
}}
</n-tag>
</div>
</div>
<div
v-if=
"showToggle"
class=
"absolute right-2 top-2"
>
...
...
src/views/creation/components/digital/digital-audio.vue
View file @
bfc4695a
...
...
@@ -10,14 +10,28 @@ import DigitalAudioCard from './digital-audio-card.vue'
const
audioSettingStore
=
useAudioSettingStore
()
const
digitalCreationStore
=
useDigitalCreationStore
()
const
lanList
=
ref
([
{
key
:
LangType
.
CANTONESE
,
label
:
'粵語'
},
{
key
:
LangType
.
MANDARIN
,
label
:
'普通話'
},
{
value
:
LangType
.
CANTONESE
,
label
:
'粵語'
},
{
value
:
LangType
.
MANDARIN
,
label
:
'普通話'
},
])
const
sexValue
=
ref
(
0
)
const
sexList
=
[
{
key
:
0
,
label
:
'女性'
},
{
key
:
1
,
label
:
'男性'
},
]
const
speedMarks
:
{
[
speed
:
number
]:
string
}
=
{
3
:
'0.5x'
,
4
:
'0.8x'
,
5
:
'1x'
,
6
:
'1.3x'
,
7
:
'1.5x'
,
}
const
pitchMarks
:
{
[
speed
:
number
]:
number
}
=
{
3
:
1
,
4
:
2
,
5
:
3
,
6
:
4
,
7
:
5
,
}
const
digitalTimbreValue
=
ref
<
TimbreItem
>
()
const
digitalTimbreList
=
ref
<
TimbreItem
[]
>
([])
const
digitalTimbreFemaleList
=
ref
<
TimbreItem
[]
>
([])
...
...
@@ -43,14 +57,14 @@ const speed = computed({
},
})
const
volume
=
computed
({
get
()
{
return
Number
(
digitalCreationStore
.
volume
)
},
set
(
value
)
{
digitalCreationStore
.
setVolume
(
String
(
value
))
},
})
//
const volume = computed({
//
get() {
//
return Number(digitalCreationStore.volume)
//
},
//
set(value) {
//
digitalCreationStore.setVolume(String(value))
//
},
//
})
const
pitch
=
computed
({
get
()
{
...
...
@@ -125,7 +139,13 @@ function handleClickAudioCard(timbreItem: TimbreItem) {
<div
class=
"h-full overflow-y-auto px-4 py-2"
>
<div
v-if=
"!showAll"
>
<div
class=
"flex justify-end pb-3"
>
<HorizontalTabs
v-model:value=
"langType"
:list=
"lanList"
/>
<n-select
v-model:value=
"langType"
class=
"w-[150px]!"
size=
"small"
:options=
"lanList"
placeholder=
"請選擇語言"
/>
</div>
<DigitalAudioCard
...
...
@@ -138,18 +158,18 @@ function handleClickAudioCard(timbreItem: TimbreItem) {
<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=
"speed"
class=
"flex-1"
:max=
"
15"
:min=
"0"
:step=
"1
"
/>
<div
class=
"w-10"
>
{{
speed
}}
</div>
<n-slider
v-model:value=
"speed"
class=
"flex-1"
:max=
"
7"
:min=
"3"
:step=
"1"
:tooltip=
"false
"
/>
<div
class=
"w-10"
>
{{
speed
Marks
[
speed
]
}}
</div>
</div>
<div
class=
"mt-4 flex items-center gap-2"
>
<
!--
<
div
class=
"mt-4 flex items-center gap-2"
>
<div
class=
"w-12"
>
音量:
</div>
<n-slider
v-model:value=
"volume"
class=
"flex-1"
:max=
"
15"
:min=
"0"
:step=
"1
"
/>
<n-slider
v-model:value=
"volume"
class=
"flex-1"
:max=
"
7"
:min=
"3"
:step=
"1"
:tooltip=
"false
"
/>
<div
class=
"w-10"
>
{{
volume
}}
</div>
</div>
</div>
-->
<div
v-if=
"langType === LangType.MANDARIN"
class=
"mt-4 flex items-center gap-2"
>
<div
class=
"w-12"
>
語調:
</div>
<n-slider
v-model:value=
"pitch"
class=
"flex-1"
:max=
"
15"
:min=
"0"
:step=
"1
"
/>
<div
class=
"w-10"
>
{{
pitch
}}
</div>
<n-slider
v-model:value=
"pitch"
class=
"flex-1"
:max=
"
7"
:min=
"3"
:step=
"1"
:tooltip=
"false
"
/>
<div
class=
"w-10"
>
{{
pitch
Marks
[
pitch
]
}}
</div>
</div>
</div>
...
...
src/views/creation/layout/header-bar.vue
View file @
bfc4695a
...
...
@@ -8,9 +8,11 @@ import { useRouter } from 'vue-router'
const
router
=
useRouter
()
const
digitalCreationStore
=
useDigitalCreationStore
()
const
draftName
=
ref
(
''
)
const
editDraftName
=
ref
(
false
)
const
autoSaveSuccess
=
ref
(
false
)
const
showExportModal
=
ref
(
false
)
const
isExporting
=
ref
(
false
)
const
ratioValue
=
ref
(
720
)
const
ratioList
=
[
{
value
:
720
,
label
:
'720p'
},
...
...
@@ -37,7 +39,7 @@ watch(
onMounted
(()
=>
{
timer
=
setInterval
(()
=>
{
saveDraft
()
!
isExporting
.
value
&&
saveDraft
()
},
5000
)
})
...
...
@@ -46,12 +48,24 @@ onUnmounted(() => {
timer
=
null
})
function
handleEditDraftName
()
{
draftName
.
value
=
digitalCreationStore
.
draftName
editDraftName
.
value
=
true
}
function
handleUpdateDraftName
()
{
editDraftName
.
value
=
false
if
(
!
draftName
.
value
)
{
window
.
$message
.
error
(
'草稿名稱不能為空'
)
draftName
.
value
=
digitalCreationStore
.
draftName
return
}
digitalCreationStore
.
setDraftName
(
draftName
.
value
)
}
// 保存为草稿
async
function
saveDraft
(
autoSave
:
boolean
=
true
)
{
const
payload
:
DraftConfig
=
{
...
digitalCreationStore
.
$state
,
draftName
:
digitalCreationStore
.
draftName
,
}
const
payload
:
DraftConfig
=
{
...
digitalCreationStore
.
$state
}
const
res
=
await
saveDraftConfig
<
DraftConfig
>
(
payload
)
if
(
res
.
code
===
0
)
{
autoSave
?
(
autoSaveSuccess
.
value
=
true
)
:
window
.
$message
.
success
(
'保存成功'
)
...
...
@@ -69,17 +83,26 @@ function confirmExport() {
}
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
)
{
window
.
$message
.
error
(
'請先生成預覽音頻'
)
isExporting
.
value
=
false
window
.
$message
.
error
(
'請生成預覽音頻'
)
return
}
const
payload
:
BaseVideoTask
=
{
...
...
@@ -109,17 +132,20 @@ async function getUniversalCurrency() {
<
template
>
<header
class=
"flex h-14 items-center justify-between bg-white px-4"
>
<div
class=
"flex items-center gap-4"
>
<CustomIcon
class=
"cursor-pointer text-lg"
icon=
"mingcute:left-line"
@
click=
"router.
replace('/')
"
/>
<CustomIcon
class=
"cursor-pointer text-lg"
icon=
"mingcute:left-line"
@
click=
"router.
back
"
/>
<n-input
v-if=
"editDraftName"
v-model:value=
"d
igitalCreationStore.d
raftName"
v-model:value=
"draftName"
placeholder=
"請輸入草稿名稱"
style=
"width: 400px"
@
blur=
"editDraftName = false"
show-count
clearable
:maxlength=
"30"
class=
"w-96!"
@
blur=
"handleUpdateDraftName"
/>
<div
v-else
class=
"flex items-center gap-2"
>
<span>
{{
digitalCreationStore
.
draftName
}}
</span>
<CustomIcon
class=
"
cursor-pointer text-lg"
icon=
"icon-park-outline:edit"
@
click=
"editDraftName = tru
e"
/>
<CustomIcon
class=
"
text-theme-color cursor-pointer text-lg"
icon=
"bxs:edit"
@
click=
"handleEditDraftNam
e"
/>
</div>
</div>
...
...
@@ -130,20 +156,14 @@ async function getUniversalCurrency() {
<span>
已自動保存
</span>
</div>
<n-button
class=
"!rounded-md"
@
click=
"saveDraft(false)"
>
保存爲草稿
</n-button>
<n-button
class=
"!rounded-md"
type=
"info"
@
click=
"showExportModal = true"
>
導出視頻
</n-button>
<n-button
class=
"!rounded-md"
type=
"info"
:disabled=
"!digitalCreationStore.id"
@
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-modal
v-model:show=
"showExportModal"
preset=
"dialog"
title=
"導出視頻"
@
after-leave=
"isExporting = false"
>
<n-form
ref=
"formRef"
:label-width=
"120"
label-placement=
"left"
>
<n-form-item
label=
"視頻名稱"
required
>
<n-input
v-model:value=
"digitalCreationStore.videoName"
placeholder=
"請輸入視頻名稱"
/>
...
...
@@ -170,5 +190,11 @@ async function getUniversalCurrency() {
<n-radio
value=
"mp4"
checked
>
MP4
</n-radio>
</n-form-item>
</n-form>
<template
#
action
>
<n-button
@
click=
"showExportModal = false"
>
取消
</n-button>
<n-button
type=
"primary"
:loading=
"isExporting"
:disabled=
"isExporting"
@
click=
"confirmExport"
>
{{
isExporting
?
'提交中…'
:
'導出'
}}
</n-button>
</
template
>
</n-modal>
</template>
src/views/creation/layout/index.vue
View file @
bfc4695a
<
script
setup
lang=
"ts"
>
import
{
fetchDigitalHumanTemplateStatus
,
fetchDraftConfigById
}
from
'@/apis/digital-creation'
import
{
fetchDraftConfigById
}
from
'@/apis/digital-creation'
import
{
fetchDigitalHumanTemplateStatus
}
from
'@/apis/template'
import
{
useDigitalCreationStore
}
from
'@/store/modules/creation'
import
{
DigitalTemplate
,
DraftConfig
,
LangType
}
from
'@/store/types/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'
...
...
@@ -26,8 +28,9 @@ async function getDigitalTemplate(id: number) {
const
draftConfig
:
DraftConfig
=
{
id
:
null
,
coverUrl
:
digitalTemplate
.
coverUrl
,
draftName
:
`
自定義草稿名稱
${
new
Date
().
toLocaleString
()}
`
,
draftName
:
`
新建數字人視頻
`
,
videoName
:
''
,
videoDuration
:
null
,
taskType
:
digitalTemplate
.
taskType
,
requestId
:
digitalTemplate
.
requestId
,
inputImageUrl
:
null
,
...
...
@@ -58,7 +61,7 @@ async function getDigitalTemplate(id: number) {
logoUrl
:
digitalTemplate
.
logoParams
?.
logoUrl
||
null
,
bgmUrl
:
digitalTemplate
.
bgmParams
?.
bgmUrl
||
null
,
materialUrl
:
digitalTemplate
.
materialUrl
,
pronunciationLanguage
L
:
LangType
.
CANTONESE
,
pronunciationLanguage
:
LangType
.
CANTONESE
,
}
digitalCreationStore
.
updateDigitalCreation
(
draftConfig
)
}
...
...
src/views/home/components/recent-creation.vue
View file @
bfc4695a
<
script
setup
lang=
"ts"
>
// import { fetchRecentCreationList } from '@/apis/drafts'
// import { useUserStore } from '@/store/modules/user'
import
{
fetchDraftsList
}
from
'@/apis/drafts'
import
{
useUserStore
}
from
'@/store/modules/user'
import
{
DraftConfig
}
from
'@/store/types/creation'
import
{
TaskType
}
from
'@/store/types/template'
import
{
Right
}
from
'@icon-park/vue-next'
import
{
onMounted
}
from
'vue'
import
{
onMounted
,
ref
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
const
router
=
useRouter
()
//
const userStore = useUserStore()
const
userStore
=
useUserStore
()
// const token = userStore.token
const
token
=
userStore
.
token
const
recentCreationList
=
ref
<
DraftConfig
[]
>
([])
onMounted
(()
=>
{
// getRecentCreation
List()
getDrafts
List
()
})
async
function
getDraftsList
()
{
const
res
=
await
fetchDraftsList
<
DraftConfig
[]
>
(
token
,
{
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 getRecentCreationList() {
// fetchRecentCreationList(token).then((res) => {
// if (res.code !== 0) return ''
// console.log(res)
// })
// }
function
handleClickDraft
(
item
:
DraftConfig
)
{
router
.
push
(
`/creation/
${
item
.
templateId
}
/
${
item
.
id
}
`
)
}
</
script
>
<
template
>
...
...
@@ -34,29 +57,27 @@ function handleGoToDrafts() {
</div>
</div>
<div
class=
"flex flex-wrap justify-between"
>
<div
v-for=
"item in 7"
:key=
"item"
>
<n-card
class=
"mt-[16px] rounded-[10px]"
>
<template
#
cover
>
<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=
"https://meta-human-editor-prd.cdn.bcebos.com/2024-04-17T16:53:29.223639/sd-qdmv5bsghiyx1xb9z_1713344008761.png?x-bce-process=image/format,f_auto/resize,w_500/quality,q_90
"
: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=
"https://meta-human-editor-prd.cdn.bcebos.com/2024-04-17T16:53:29.223639/sd-qdmv5bsghiyx1xb9z_1713344008761.png?x-bce-process=image/format,f_auto/resize,w_500/quality,q_90
"
: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>
</
template
>
</n-card>
<n-ellipsis
class=
"mt-[12px] cursor-default text-[#151b26]"
style=
"max-width: 150px"
>
金融课程2024-09-11 09:48:41
{{
item
.
draftName
}}
</n-ellipsis>
</div>
</div>
...
...
src/views/home/components/recommend-template.vue
View file @
bfc4695a
<
script
setup
lang=
"ts"
>
import
{
computed
,
ref
}
from
'vue
'
import
TemplatePreviewModal
from
'./template-preview-modal.vue
'
import
{
fetchDigitalHumanTemplateStatusList
}
from
'@/apis/template.ts
'
import
{
DigitalTemplate
}
from
'@/store/types/template.ts
'
import
{
useInfiniteScroll
}
from
'@vueuse/core'
import
{
templateData
}
from
'../templateData.ts'
import
{
computed
,
onMounted
,
ref
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
TemplatePreviewModal
from
'./template-preview-modal.vue'
const
router
=
useRouter
()
const
PreviewModalVisible
=
ref
(
false
)
const
selectedTemplate
=
ref
({
imageUrl
:
''
,
videoUrl
:
''
,
})
const
checkedClassifyValue
=
ref
(
'Trending'
)
const
selectedTemplate
=
ref
<
DigitalTemplate
>
()
const
checkedClassifyValue
=
ref
(
''
)
const
loadingMoreTemplate
=
ref
(
false
)
const
templatePageSize
=
ref
(
30
)
const
isHoverTemplate
=
ref
<
{
[
key
:
string
]:
boolean
}
>
({})
const
templateBottomEl
=
ref
<
HTMLElement
|
null
>
(
null
)
const
templateClassify
=
[
{
value
:
'Trending'
,
label
:
'熱門'
},
{
value
:
'Shakermaker'
,
label
:
'産品營銷'
,
},
{
value
:
'NewsInformatio'
,
label
:
'新聞資訊'
,
},
{
value
:
'BusinessCard'
,
label
:
'名片'
,
},
{
value
:
'Health'
,
label
:
'醫療健康'
,
},
{
value
:
'Education'
,
label
:
'教育培訓'
,
},
{
value
:
'Invite'
,
label
:
'邀請函'
,
},
{
value
:
''
,
label
:
'熱門'
},
{
value
:
'FINANCIAL_MARKETING'
,
label
:
'理財營銷'
},
{
value
:
'EDUCATION_LEARNING'
,
label
:
'教育學習'
},
{
value
:
'FESTIVAL_HOTS_SPOTS'
,
label
:
'節日熱點'
},
]
const
templateList
=
ref
(
templateData
)
const
templateList
=
ref
<
DigitalTemplate
[]
>
([]
)
const
filteredTemplateData
=
computed
(()
=>
{
if
(
!
checkedClassifyValue
.
value
)
{
return
templateList
.
value
}
return
templateList
.
value
.
filter
((
item
:
{
templateType
:
string
}
)
=>
item
.
templateType
===
checkedClassifyValue
.
value
)
return
templateList
.
value
.
filter
((
item
)
=>
item
.
templateType
===
checkedClassifyValue
.
value
)
})
const
displayedData
=
computed
(()
=>
{
...
...
@@ -58,6 +38,7 @@ const displayedData = computed(() => {
const
canLoadMore
=
computed
(()
=>
{
return
templatePageSize
.
value
<
filteredTemplateData
.
value
.
length
})
useInfiniteScroll
(
templateBottomEl
,
()
=>
{
...
...
@@ -73,18 +54,31 @@ useInfiniteScroll(
{
distance
:
10
},
)
function
handleOpenModal
(
template
:
typeof
selectedTemplate
.
value
)
{
onMounted
(()
=>
{
getTemplateList
()
})
async
function
getTemplateList
()
{
const
res
=
await
fetchDigitalHumanTemplateStatusList
<
DigitalTemplate
[]
>
()
if
(
res
.
code
===
0
)
{
templateList
.
value
=
res
.
data
}
}
function
handleOpenModal
(
template
:
DigitalTemplate
)
{
selectedTemplate
.
value
=
{
...
template
}
PreviewModalVisible
.
value
=
true
}
function
handleToCreation
(
template
:
DigitalTemplate
)
{
router
.
push
(
`/creation/
${
template
.
id
}
`
)
}
</
script
>
<
template
>
<div
class=
"mt-[16px] h-full min-h-[980px] min-w-[1160px] overscroll-none rounded-[16px] bg-white px-[24px] pb-[16px]"
>
<div
class=
"mt-[16px] min-h-full min-w-[1160px] overscroll-none rounded-[16px] bg-white px-[24px] pb-[16px]"
>
<div
class=
"sticky top-0 z-10 bg-white pt-[24px]"
>
<div
class=
"mb-[24px] cursor-default text-[18px] text-[#000]"
>
推薦模
闆
</div>
<div
class=
"mb-[24px] cursor-default text-[18px] text-[#000]"
>
推薦模
板
</div>
<div
class=
"pb-[16px]"
>
<n-radio-group
v-model:value=
"checkedClassifyValue"
>
<n-radio-button
...
...
@@ -110,18 +104,18 @@ function handleOpenModal(template: typeof selectedTemplate.value) {
<div
class=
"relative flex items-center justify-center bg-[#f0f0f0]"
>
<img
v-if=
"!isHoverTemplate[item.id]"
:src=
"item.
image
Url"
:src=
"item.
cover
Url"
class=
"hover:scale-104 inset-0 w-full cursor-pointer object-cover transition-transform duration-300 ease-in-out"
@
click=
"handleOpenModal(item)"
/>
<video
v-else
:src=
"item.
v
ideoUrl"
:src=
"item.
demonstrationV
ideoUrl"
class=
"hover:scale-104 inset-0 w-full cursor-pointer object-cover transition-transform duration-300 ease-in-out"
autoplay
muted
:poster=
"item.
image
Url"
:poster=
"item.
cover
Url"
@
click=
"handleOpenModal(item)"
></video>
<div
...
...
@@ -129,6 +123,7 @@ function handleOpenModal(template: typeof selectedTemplate.value) {
></div>
<div
class=
"overlay absolute bottom-2 left-2 right-2 hidden h-8 cursor-pointer rounded-[6px] border border-gray-300 bg-white/90 px-2 text-center text-[14px] leading-[30px] text-[#000000]"
@
click=
"handleToCreation(item)"
>
做同款
</div>
...
...
@@ -143,7 +138,7 @@ function handleOpenModal(template: typeof selectedTemplate.value) {
<div
class=
"relative top-[10px] h-[1px] w-[14px] bg-[#a9b4cc]"
></div>
</div>
<TemplatePreviewModal
v-model=
"PreviewModalVisible"
:selected-template=
"selectedTemplate"
/>
<TemplatePreviewModal
v-model=
"PreviewModalVisible"
:selected-template=
"selectedTemplate
!
"
/>
</div>
</template>
<
style
lang=
"scss"
scoped
>
...
...
src/views/home/components/template-preview-modal.vue
View file @
bfc4695a
<
script
setup
lang=
"ts"
>
import
{
DigitalTemplate
}
from
'@/store/types/template'
import
{
Close
}
from
'@icon-park/vue-next'
const
PreviewModalVisible
=
defineModel
<
boolean
>
()
const
props
=
defineProps
<
{
selectedTemplate
:
{
id
:
number
coverUrl
:
string
demonstrationGifUrl
:
string
demonstrationVideoUrl
:
string
templateName
:
string
templateType
:
string
}
selectedTemplate
:
DigitalTemplate
}
>
()
const
emit
=
defineEmits
<
{
(
event
:
'go-to-create'
,
id
:
number
):
void
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment