Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mengchangaigc
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
姚珂
mengchangaigc
Commits
5d11284c
Commit
5d11284c
authored
Sep 02, 2026
by
黄同智
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
爆款复刻的70%页面
parent
048729dc
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
2332 additions
and
3 deletions
+2332
-3
index.vue
apps/web-vue/src/components/Upload/index.vue
+6
-2
index.vue
apps/web-vue/src/layout/index.vue
+1
-1
index.ts
apps/web-vue/src/router/index.ts
+19
-0
Step-1.vue
apps/web-vue/src/views/burst/components/Step-1.vue
+192
-0
Step-2.vue
apps/web-vue/src/views/burst/components/Step-2.vue
+575
-0
Step-3.vue
apps/web-vue/src/views/burst/components/Step-3.vue
+492
-0
Step-4.vue
apps/web-vue/src/views/burst/components/Step-4.vue
+14
-0
VoiceConfigDialog.vue
.../web-vue/src/views/burst/components/VoiceConfigDialog.vue
+389
-0
index copy.vue
apps/web-vue/src/views/burst/index copy.vue
+85
-0
index.vue
apps/web-vue/src/views/burst/index.vue
+386
-0
replica.vue
apps/web-vue/src/views/burst/replica.vue
+173
-0
No files found.
apps/web-vue/src/components/Upload/index.vue
View file @
5d11284c
<
template
>
<div>
<el-upload
v-if=
"listType === 'drap'"
class=
"upload-demo"
drag
<el-upload
v-if=
"listType === 'drap'"
drag
:class=
"bgClass ?? 'upload-demo'"
action=
"https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:multiple=
"multiple"
:limit=
"limit"
:accept=
"accept"
:showFileList=
"showFileList"
>
<slot
v-if=
"!hasFile"
name=
"trigger"
>
...
...
@@ -38,6 +38,10 @@ import type { UploadProps, UploadUserFile, UploadFile } from 'element-plus';
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
;
const
props
=
defineProps
({
bgClass
:
{
type
:
String
,
default
:
'upload-demo'
},
action
:
{
type
:
String
,
default
:
''
,
...
...
@@ -193,7 +197,7 @@ defineExpose({
<
style
scoped
lang=
"scss"
>
:deep
(
.upload-demo
)
{
.el-upload-dragger
{
background-color
:
rgba
(
var
(
--
main-color
)
,
0
.04
)
;
background-color
:
#eee
;
}
}
.file-upload-wrapper
{
...
...
apps/web-vue/src/layout/index.vue
View file @
5d11284c
...
...
@@ -91,7 +91,7 @@ const menus = [
},
{
name
:
'爆款复刻'
,
path
:
'/'
,
path
:
'/
burst
'
,
icon
:
'caijian'
,
},
{
...
...
apps/web-vue/src/router/index.ts
View file @
5d11284c
...
...
@@ -11,6 +11,7 @@ import Layout from '@/layout/index.vue'
import
Task
from
'@/views/task/index.vue'
import
QuickCreation
from
'@/views/quickCreation/index.vue'
import
Agent
from
'@/views/agent/index.vue'
import
Burst
from
'@/views/burst/index.vue'
const
routes
=
[
{
...
...
@@ -92,6 +93,24 @@ const routes = [
]
},
{
path
:
'/burst'
,
component
:
Layout
,
children
:
[
{
path
:
''
,
name
:
'Burst'
,
component
:
Burst
,
meta
:
{
title
:
'爆款复刻'
,
icon
:
'caijian'
,
affix
:
true
}
}
]
},
{
path
:
'/burst/replica'
,
name
:
'BurstReplica'
,
component
:
()
=>
import
(
'@/views/burst/replica.vue'
),
meta
:
{
title
:
'爆款复刻工作台'
,
hidden
:
true
}
},
{
path
:
'/zhibo'
,
component
:
Layout
,
children
:
[
...
...
apps/web-vue/src/views/burst/components/Step-1.vue
0 → 100644
View file @
5d11284c
<
template
>
<section
class=
"step-one maxw-1200 mx-auto"
>
<div
class=
"notice-box flex-items-center gap-10 h-48 px-18 round-8 mb-26"
>
<el-icon
class=
"color-main fs-16"
>
<InfoFilled
/>
</el-icon>
<span
class=
"fs-12 color-000"
>
说明:确认剧集视频后,系统将对原视频进行资产解析,提取主要的人物、场景、道具资产,用于后续替换
</span>
</div>
<div
class=
"flex-between mb-26"
>
<h2
class=
"fs-20 fw-700 color-000"
>
原片剧集
</h2>
</div>
<div
class=
"flex-between mb-14"
>
<div
class=
"flex-items-center gap-16 fs-12 color-536"
>
<span
class=
"flex-items-center gap-4"
>
<el-icon>
<VideoCamera
/>
</el-icon>
全部剧集
<span
class=
"color-000 fw-700"
>
{{
episodes
.
length
}}
</span>
</span>
<span>
已上传
<span
class=
"color-main fw-700"
>
{{
uploadedCount
}}
</span></span>
<span>
上传中
<span
class=
"color-main fw-700"
>
{{
uploadingCount
}}
</span></span>
<span>
上传失败
<span
class=
"danger-text fw-700"
>
{{
failedCount
}}
</span></span>
</div>
<el-button
class=
"h-32 px-18 fs-12"
@
click=
"addEpisode"
>
<el-icon>
<Plus
/>
</el-icon>
<span>
新增视频
</span>
</el-button>
</div>
<el-table
:data=
"episodes"
class=
"episode-table w-full"
row-key=
"id"
>
<el-table-column
label=
"剧集信息"
min-width=
"420"
>
<template
#
default=
"
{ row }">
<div
class=
"flex-items-center gap-12 py-8"
>
<img
:src=
"row.cover"
:alt=
"row.name"
class=
"episode-cover w-48 h-32 round-4 cover"
/>
<div
class=
"flex-col"
>
<span
class=
"fs-14 fw-700 color-000 lh-22"
>
{{
row
.
name
}}
</span>
<span
class=
"fs-12 color-666 lh-20"
>
{{
row
.
filename
}}
</span>
</div>
</div>
</
template
>
</el-table-column>
<el-table-column
prop=
"duration"
label=
"时长"
width=
"150"
/>
<el-table-column
prop=
"size"
label=
"大小"
width=
"170"
/>
<el-table-column
label=
"状态"
width=
"170"
>
<
template
#
default=
"{ row }"
>
<span
class=
"flex-items-center gap-6 fs-12 color-333"
>
<span
class=
"status-dot wh-8 round"
:class=
"row.status"
></span>
{{
row
.
statusText
}}
*
</span>
</
template
>
</el-table-column>
<el-table-column
label=
"操作"
width=
"190"
>
<
template
#
default
>
<el-button
type=
"primary"
link
class=
"fs-12"
>
重新上传
</el-button>
<el-button
type=
"danger"
link
class=
"fs-12"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
<div
class=
"fixed bottom-10 left-0 flex-center w-full"
>
<el-button
type=
"primary"
class=
"start-btn h-44 round-140 border-none mt-24"
@
click=
"goNext"
>
<el-icon>
<MagicStick
/>
</el-icon>
<span>
进入下一步
</span>
<el-icon>
<ArrowRight
/>
</el-icon>
</el-button>
</div>
</section>
</template>
<
script
setup
lang=
"ts"
>
import
{
computed
,
ref
}
from
'vue'
;
import
{
ArrowRight
,
InfoFilled
,
MagicStick
,
Plus
,
VideoCamera
}
from
'@element-plus/icons-vue'
;
import
coverImg
from
'@/static/image/huaban.png'
;
type
EpisodeStatus
=
'done'
|
'uploading'
|
'failed'
;
type
EpisodeItem
=
{
id
:
string
;
name
:
string
;
filename
:
string
;
duration
:
string
;
size
:
string
;
status
:
EpisodeStatus
;
statusText
:
string
;
cover
:
string
;
};
const
emit
=
defineEmits
<
{
(
e
:
'next'
):
void
;
}
>
();
const
episodes
=
ref
<
EpisodeItem
[]
>
([
{
id
:
'episode-1'
,
name
:
'第 1 集'
,
filename
:
'r_cde1602bd05a40f9b3d3606dc531df01.mp4'
,
duration
:
'00:15'
,
size
:
'19.44 MB'
,
status
:
'done'
,
statusText
:
'已完成'
,
cover
:
coverImg
,
},
]);
const
uploadedCount
=
computed
(()
=>
episodes
.
value
.
filter
((
item
)
=>
item
.
status
===
'done'
).
length
);
const
uploadingCount
=
computed
(()
=>
episodes
.
value
.
filter
((
item
)
=>
item
.
status
===
'uploading'
).
length
);
const
failedCount
=
computed
(()
=>
episodes
.
value
.
filter
((
item
)
=>
item
.
status
===
'failed'
).
length
);
const
addEpisode
=
()
=>
{
episodes
.
value
.
push
({
id
:
`episode-
${
Date
.
now
()}
`
,
name
:
`第
${
episodes
.
value
.
length
+
1
}
集`
,
filename
:
'new_replica_source.mp4'
,
duration
:
'00:00'
,
size
:
'0 MB'
,
status
:
'uploading'
,
statusText
:
'上传中'
,
cover
:
coverImg
,
});
};
const
goNext
=
()
=>
{
emit
(
'next'
);
};
</
script
>
<
style
scoped
lang=
"scss"
>
.step-one
{
width
:
1200px
;
margin
:
0
auto
;
}
.notice-box
{
border
:
1px
solid
#cfd8ff
;
background
:
#eef1ff
;
}
.color-536
{
color
:
#536883
;
}
.danger-text
{
color
:
#f5222d
;
}
.episode-table
{
border
:
1px
solid
#dce6f2
;
border-bottom
:
0
;
}
.episode-cover
{
flex
:
0
0
48px
;
background
:
#f3f6fb
;
}
.status-dot.done
{
background
:
#18b66a
;
}
.status-dot.uploading
{
background
:
rgba
(
var
(
--
main-color
)
,
1
);
}
.status-dot.failed
{
background
:
#f5222d
;
}
:deep
(
.episode-table
.el-table__header
th
)
{
height
:
40px
;
font-size
:
12px
;
color
:
#536883
;
font-weight
:
500
;
background
:
#f8fafc
;
}
:deep
(
.episode-table
.el-table__cell
)
{
border-color
:
#dce6f2
;
}
:deep
(
.episode-table
.el-table__row
)
{
height
:
62px
;
}
</
style
>
apps/web-vue/src/views/burst/components/Step-2.vue
0 → 100644
View file @
5d11284c
<
template
>
<div
class=
"step-two-page h-full hidden flex"
:class=
"
{ 'detail-open': selectedAsset }">
<div
class=
"step-left h-full relative"
:class=
"selectedAsset ? 'flex-1' : 'w-full'"
>
<div
class=
"h-full w-full"
>
<section
class=
"step-two h-full px-24 relative flex-col"
>
<div
v-if=
"!isParsed"
class=
"parse-panel flex-col flex-center text-center"
>
<div
class=
"parse-illus relative mb-16"
>
<el-icon
class=
"document-icon"
>
<Document
/>
</el-icon>
<span
class=
"loading-badge flex-center wh-24 round"
>
<el-icon
class=
"loading-icon"
>
<Loading
/>
</el-icon>
</span>
</div>
<div
class=
"fs-18 fw-700 color-000 lh-30"
>
剧集资产解析中
</div>
<div
class=
"fs-14 color-536 lh-24"
>
剧集资产解析中,请耐心等待...
</div>
<div
class=
"parse-card flex-between gap-24 mt-52 px-52 py-22 round-14"
>
<div
v-for=
"item in parseTasks"
:key=
"item.key"
class=
"parse-item flex-col flex-center"
>
<el-progress
type=
"circle"
:percentage=
"item.progress"
:width=
"48"
:stroke-width=
"5"
:status=
"item.progress === 100 ? 'success' : undefined"
:color=
"progressColor"
/>
<div
class=
"fs-14 color-536 lh-24 mt-12"
>
{{
item
.
label
}}
</div>
</div>
</div>
</div>
<div
v-else
class=
"parsed-panel h-full flex-col"
>
<div
class=
"result-notice flex-between h-48 px-16 round-8 mt-14 mb-20"
>
<div
class=
"flex-items-center gap-10 fs-12 color-000"
>
<el-icon
class=
"color-main "
>
<InfoFilled
/>
</el-icon>
<span>
请完成新旧角色、场景、和道具映射关系;确认替换关系后,系统将自动对原视频进行智能切片并提取关键词对应,用于后续分镜生成。
</span>
</div>
<el-icon
class=
"color-536 pointer"
>
<Close
/>
</el-icon>
</div>
<div
class=
"flex-between mb-28"
>
<div
class=
"asset-tabs flex-items-center gap-28"
>
<div
v-for=
"tab in assetTabs"
:key=
"tab.key"
class=
"asset-tab flex-items-center gap-6 pointer"
:class=
"
{ active: activeTab === tab.key }" @click="changeTab(tab.key)">
<span>
{{
tab
.
label
}}
</span>
<span
class=
"tab-count flex-center h-16 px-6 round-8 color-fff fs-12"
>
{{
tab
.
count
}}
</span>
</div>
</div>
<div
class=
"asset-actions flex-items-center gap-16 fs-12 color-536"
>
<span>
形象总计
<span
class=
"color-000"
>
3
</span></span>
<span>
已解析
<span
class=
"color-main"
>
0
</span></span>
<span>
失败
<span
class=
"danger-text"
>
0
</span></span>
<span
class=
"divider"
></span>
<el-button
link
class=
"fs-12 color-000"
>
<el-icon>
<Plus
/>
</el-icon>
添加角色
</el-button>
<span
class=
"divider"
></span>
<el-button
link
class=
"fs-12 color-000"
>
<el-icon>
<List
/>
</el-icon>
批量生成
</el-button>
<span
class=
"divider"
></span>
<el-icon
class=
"fs-18 color-536 pointer"
>
<MoreFilled
/>
</el-icon>
</div>
</div>
<div
class=
"asset-scroll flex-1 over-y-auto hide-scroll pb-80"
>
<div
v-if=
"currentAssets.length"
class=
"asset-list flex flex-wrap gap-20"
>
<div
v-for=
"item in currentAssets"
:key=
"item.id"
class=
"asset-card bg-fff round-8 p-12 pointer"
:class=
"
{ active: selectedAsset?.id === item.id }" @click="selectAsset(item)">
<div
class=
"asset-compare flex gap-8"
>
<div
class=
"asset-side flex-col flex-1"
>
<div
class=
"asset-media relative round-6 hidden"
>
<span
class=
"corner-tag old flex-center color-fff fs-12"
>
原
</span>
<img
:src=
"item.origin"
:alt=
"item.oldName"
class=
"w-full h-full cover"
/>
</div>
<div
class=
"fs-14 color-000 lh-24 mt-8"
>
{{
item
.
oldName
}}
</div>
</div>
<div
class=
"asset-side flex-col flex-1"
>
<div
class=
"asset-media empty-media relative flex-center round-6"
>
<span
class=
"corner-tag new flex-center color-fff fs-12"
>
新
</span>
<div
class=
"flex-col flex-center color-999 fs-12"
>
<el-icon
class=
"fs-22 mb-6"
>
<Picture
/>
</el-icon>
<span>
暂未配置设定图
</span>
</div>
</div>
<div
class=
"fs-14 color-000 lh-24 mt-8"
>
{{
item
.
newName
}}
</div>
</div>
</div>
<div
class=
"flex-between mt-8"
>
<el-button
class=
"h-32 px-14 fs-12"
@
click
.
stop=
"openVoiceDialog(item)"
>
<el-icon>
<Microphone
/>
</el-icon>
配置音色
</el-button>
<el-button
class=
"more-btn wh-32 p-0"
plain
>
<el-icon>
<MoreFilled
/>
</el-icon>
</el-button>
</div>
</div>
</div>
<div
v-else
class=
"empty-assets flex-center h-220 bg-fff round-8 color-999 fs-14"
>
暂无道具资产
</div>
</div>
</div>
</section>
</div>
<div
class=
"absolute bottom-10 w-full color-000 flex-center"
>
<el-button
type=
"primary"
class=
"start-btn h-44 round-140 border-none mt-24"
@
click=
"goNext"
>
<el-icon>
<MagicStick
/>
</el-icon>
<span>
分镜解析
</span>
<el-icon>
<ArrowRight
/>
</el-icon>
</el-button>
</div>
</div>
<div
class=
"py-14 pr-14 flex-1"
>
<aside
v-if=
"selectedAsset"
class=
"asset-detail bg-fff h-full p-20 flex-col round-14"
>
<div
class=
"detail-header flex-between mb-24"
>
<div
class=
" fw-700 color-000"
>
{{
selectedAsset
.
newName
}}
</div>
<div
class=
"flex-items-center gap-16"
>
<el-icon
class=
" color-333 pointer"
>
<Edit
/>
</el-icon>
<el-button
link
class=
"fs-12 color-000"
>
<el-icon>
<Plus
/>
</el-icon>
添加变装
</el-button>
<span
class=
"divider"
></span>
<el-icon
class=
" color-333 pointer"
>
<Star
/>
</el-icon>
<el-icon
class=
" color-333 pointer"
>
<Download
/>
</el-icon>
<span
class=
"divider"
></span>
<el-button
type=
"primary"
class=
"h-32 px-18 fs-12 border-none"
>
确认角色
</el-button>
<el-icon
class=
" color-999 pointer"
@
click=
"clearSelectedAsset"
>
<Close
/>
</el-icon>
</div>
</div>
<div
class=
"detail-preview-wrap flex-1 flex gap-12 hidden"
>
<div
class=
"detail-preview flex-1 flex-center round-6"
>
<div
class=
"flex-col flex-center color-999 fs-14"
>
<el-icon
class=
"fs-28 mb-8"
>
<Picture
/>
</el-icon>
<span>
暂未配置设定图
</span>
</div>
</div>
<div
class=
"thumb-column flex-col items-center gap-10"
>
<div
class=
"fs-12 color-333 lh-20"
>
当前
</div>
<div
class=
"current-thumb wh-48 round-6 hidden pointer"
>
<img
:src=
"selectedAsset.origin"
:alt=
"selectedAsset.oldName"
class=
"w-full h-full cover"
/>
</div>
</div>
</div>
<div
class=
"agent-panel mt-14 round-14 hidden bg-fff mr-70"
>
<div
class=
"agent-title flex-between h-48 px-12"
>
<div
class=
"flex-items-center gap-8 fs-14 fw-700 color-000"
>
<el-icon>
<MagicStick
/>
</el-icon>
Agent 生成形象
</div>
<div
class=
"flex-items-center gap-16 fs-12 color-333"
>
<span
class=
"flex-items-center gap-4 pointer"
>
<el-icon>
<FolderOpened
/>
</el-icon>
从角色库选择
</span>
<span
class=
"flex-items-center gap-4 pointer"
>
<el-icon>
<Upload
/>
</el-icon>
本地上传
</span>
</div>
</div>
<div
class=
"agent-content m-12 p-12 round-10"
>
<el-button
class=
"wh-36 p-0 mb-10"
plain
>
<el-icon>
<Plus
/>
</el-icon>
</el-button>
<div
class=
"fs-12 color-000 lh-22"
>
- 任务:完成角色
{{
selectedAsset
.
newName
}}
的上半身正面平视特写和该角色的全身三视图,左边是角色的上半身正面平视特写,右边是该角色的全身三视图。三视图不可以有分割线,比例是
16:9,用于展示面部、发型、表情、眼神、上半身服装和配饰细节。
</div>
<div
class=
"flex-items-center flex-wrap gap-6 mt-12"
>
<el-tag
effect=
"plain"
round
>
Doubao-Seedream-5.0-Pro
</el-tag>
<el-tag
effect=
"plain"
round
>
1张
</el-tag>
<el-tag
effect=
"plain"
round
>
1k
</el-tag>
<el-tag
effect=
"plain"
round
>
16:9
</el-tag>
<el-tag
effect=
"plain"
round
>
现代都市通用
</el-tag>
</div>
<div
class=
"flex justify-end mt-10"
>
<el-button
type=
"primary"
class=
"h-32 px-18 border-none"
>
<el-icon>
<MagicStick
/>
</el-icon>
30
</el-button>
</div>
</div>
</div>
</aside>
</div>
<VoiceConfigDialog
v-model=
"voiceDialogVisible"
@
confirm=
"confirmVoice"
/>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
computed
,
onBeforeUnmount
,
onMounted
,
ref
}
from
'vue'
;
import
{
ArrowRight
,
Close
,
Document
,
Download
,
Edit
,
FolderOpened
,
InfoFilled
,
List
,
Loading
,
MagicStick
,
Microphone
,
MoreFilled
,
Picture
,
Plus
,
Star
,
Upload
,
}
from
'@element-plus/icons-vue'
;
import
coverImg
from
'@/static/image/huaban.png'
;
import
roleImg
from
'@/static/image/meinv.png'
;
import
VoiceConfigDialog
from
'./VoiceConfigDialog.vue'
;
type
AssetTabKey
=
'role'
|
'scene'
|
'prop'
;
type
ParseTask
=
{
key
:
string
;
label
:
string
;
progress
:
number
;
};
type
AssetItem
=
{
id
:
string
;
oldName
:
string
;
newName
:
string
;
origin
:
string
;
};
const
isParsed
=
ref
(
false
);
const
activeTab
=
ref
<
AssetTabKey
>
(
'role'
);
const
selectedAsset
=
ref
<
AssetItem
>
();
const
voiceDialogVisible
=
ref
(
false
);
const
voiceAsset
=
ref
<
AssetItem
>
();
const
emit
=
defineEmits
<
{
(
e
:
'next'
):
void
;
}
>
();
const
parseTasks
=
ref
<
ParseTask
[]
>
([
{
key
:
'summary'
,
label
:
'剧情概要解析'
,
progress
:
0
},
{
key
:
'role'
,
label
:
'角色解析'
,
progress
:
0
},
{
key
:
'scene'
,
label
:
'场景解析'
,
progress
:
0
},
{
key
:
'prop'
,
label
:
'道具解析'
,
progress
:
0
},
]);
const
assetTabs
=
[
{
key
:
'role'
,
label
:
'角色'
,
count
:
3
},
{
key
:
'scene'
,
label
:
'场景'
,
count
:
1
},
{
key
:
'prop'
,
label
:
'道具'
,
count
:
0
},
]
as
const
;
const
assetMap
:
Record
<
AssetTabKey
,
AssetItem
[]
>
=
{
role
:
[
{
id
:
'role-1'
,
oldName
:
'万秋'
,
newName
:
'万秋'
,
origin
:
roleImg
},
{
id
:
'role-2'
,
oldName
:
'村支书'
,
newName
:
'村支书'
,
origin
:
coverImg
},
{
id
:
'role-3'
,
oldName
:
'[群像]村民'
,
newName
:
'[群像]村民'
,
origin
:
roleImg
},
],
scene
:
[
{
id
:
'scene-1'
,
oldName
:
'村口院落'
,
newName
:
'村口院落'
,
origin
:
coverImg
},
],
prop
:
[],
};
let
timer
:
number
|
undefined
;
const
progressColor
=
'rgba(var(--main-color), 1)'
;
const
currentAssets
=
computed
(()
=>
assetMap
[
activeTab
.
value
]);
const
changeTab
=
(
key
:
AssetTabKey
)
=>
{
activeTab
.
value
=
key
;
selectedAsset
.
value
=
undefined
;
};
const
selectAsset
=
(
item
:
AssetItem
)
=>
{
selectedAsset
.
value
=
item
;
};
const
clearSelectedAsset
=
()
=>
{
selectedAsset
.
value
=
undefined
;
};
const
openVoiceDialog
=
(
item
:
AssetItem
)
=>
{
voiceAsset
.
value
=
item
;
voiceDialogVisible
.
value
=
true
;
};
const
confirmVoice
=
(
value
:
{
tab
:
string
;
voiceId
?:
string
;
prompt
?:
string
})
=>
{
console
.
log
(
'confirm voice'
,
voiceAsset
.
value
,
value
);
};
const
goNext
=
()
=>
{
emit
(
'next'
);
};
const
runParsing
=
()
=>
{
let
times
=
0
;
timer
=
window
.
setInterval
(()
=>
{
times
+=
1
;
parseTasks
.
value
=
parseTasks
.
value
.
map
((
item
,
index
)
=>
{
const
fastComplete
=
index
!==
1
&&
times
>=
4
;
const
nextProgress
=
fastComplete
?
100
:
Math
.
min
(
100
,
item
.
progress
+
18
+
index
*
4
);
return
{
...
item
,
progress
:
nextProgress
,
};
});
if
(
parseTasks
.
value
.
every
((
item
)
=>
item
.
progress
===
100
))
{
window
.
clearInterval
(
timer
);
timer
=
undefined
;
window
.
setTimeout
(()
=>
{
isParsed
.
value
=
true
;
},
500
);
}
},
420
);
};
onMounted
(()
=>
{
runParsing
();
});
onBeforeUnmount
(()
=>
{
if
(
timer
)
{
window
.
clearInterval
(
timer
);
}
});
</
script
>
<
style
scoped
lang=
"scss"
>
.step-two-page
{
background
:
#f8fafc
;
}
.step-left
,
.asset-detail
{
min-width
:
0
;
}
.asset-detail
{
box-shadow
:
0
0
8px
#eee
;
}
.step-two
{
box-sizing
:
border-box
;
}
.parsed-panel
,
.asset-scroll
{
min-height
:
0
;
}
.parse-panel
{
min-height
:
520px
;
}
.parse-illus
{
width
:
72px
;
height
:
72px
;
}
.document-icon
{
font-size
:
72px
;
color
:
#e4e8ef
;
}
.loading-badge
{
position
:
absolute
;
right
:
2px
;
bottom
:
8px
;
color
:
#fff
;
background
:
rgba
(
var
(
--
main-color
)
,
1
);
box-shadow
:
0
2px
8px
rgba
(
var
(
--
main-color
)
,
.4
);
}
.loading-icon
{
animation
:
spin
1s
linear
infinite
;
}
.parse-card
{
width
:
560px
;
border
:
1px
solid
#e6ebf5
;
background
:
#fbfcff
;
}
.parse-item
{
flex
:
1
;
}
.color-536
{
color
:
#536883
;
}
.result-notice
{
border
:
1px
solid
#cfd8ff
;
background
:
#eef1ff
;
}
.asset-tab
{
color
:
#536883
;
&
.active
{
color
:
rgba
(
var
(
--
main-color
)
,
1
);
}
}
.tab-count
{
min-width
:
16px
;
background
:
#f04455
;
}
.divider
{
width
:
1px
;
height
:
14px
;
background
:
#dce6f2
;
}
.danger-text
{
color
:
#f5222d
;
}
.asset-card
{
flex
:
0
0
calc
((
100%
-
60px
)
/
4
);
border
:
1px
solid
transparent
;
box-shadow
:
0
6px
20px
rgba
(
22
,
36
,
58
,
.04
);
&
.active
{
border-color
:
rgba
(
var
(
--
main-color
)
,
1
);
}
}
.detail-open
{
.asset-card
{
flex
:
0
0
calc
((
100%
-
20px
)
/
2
);
}
.asset-media
{
height
:
352px
;
}
}
.asset-media
{
height
:
376px
;
background
:
#f4f6fa
;
}
.empty-media
{
border
:
1px
solid
#eef1f6
;
}
.corner-tag
{
position
:
absolute
;
top
:
0
;
left
:
0
;
z-index
:
1
;
width
:
18px
;
height
:
18px
;
border-radius
:
4px
0
4px
0
;
}
.corner-tag.old
{
background
:
#6b7b93
;
}
.corner-tag.new
{
background
:
rgba
(
var
(
--
main-color
)
,
1
);
}
.more-btn
{
background
:
#f8fafc
;
}
.asset-detail
{
border-left
:
1px
solid
#edf2f7
;
}
.detail-preview-wrap
{
min-height
:
360px
;
}
.detail-preview
{
background
:
#f4f6fa
;
}
.thumb-column
{
flex
:
0
0
58px
;
}
.current-thumb
{
border
:
2px
solid
rgba
(
var
(
--
main-color
)
,
1
);
}
.agent-panel
{
border
:
1px
solid
#edf2f7
;
}
.agent-title
{
background
:
#f8fafc
;
}
.agent-content
{
border
:
1px
solid
#edf2f7
;
background
:
#fff
;
}
:deep
(
.el-progress-circle__track
)
{
stroke
:
#dfe5f6
;
}
:deep
(
.el-progress__text
)
{
font-size
:
12px
!
important
;
font-weight
:
700
;
color
:
#000
;
}
:deep
(
.el-progress.is-success
.el-progress__text
)
{
color
:
#18b66a
;
}
@keyframes
spin
{
from
{
transform
:
rotate
(
0deg
);
}
to
{
transform
:
rotate
(
360deg
);
}
}
</
style
>
apps/web-vue/src/views/burst/components/Step-3.vue
0 → 100644
View file @
5d11284c
<
template
>
<section
class=
"step-three h-full"
>
<div
v-if=
"!isReady"
class=
"planning-panel h-full flex-col flex-center text-center"
>
<div
class=
"fs-18 fw-700 color-000 lh-30"
>
分镜策划进行中
</div>
<div
class=
"fs-14 color-536 lh-24 mt-8"
>
Agent正在进行分镜策划,请耐心等待...
</div>
<div
class=
"progress-meta flex-between w-400 mt-36 fs-14 color-536"
>
<span>
当前剧集进度:
</span>
<span>
第1集/共1集
</span>
</div>
<div
class=
"planning-card w-400 p-12 mt-14 round-10 bg-fff"
>
<div
class=
"fs-14 fw-700 color-000 lh-24 text-left"
>
第1集 策划解析进度:
{{
planningProgress
}}
%
</div>
<el-progress
class=
"mt-14"
:percentage=
"planningProgress"
:show-text=
"false"
:stroke-width=
"6"
/>
</div>
</div>
<div
v-else
class=
"storyboard-workspace h-full flex hidden"
>
<aside
class=
"episode-rail flex-col items-center pt-12"
>
<div
class=
"fs-12 color-536 lh-22 mb-8"
>
集数
</div>
<div
class=
"episode-no flex-center w-36 h-36 round-8 color-fff fw-700"
>
1
</div>
</aside>
<aside
class=
"asset-sidebar bg-fff flex-col px-12 py-12 hidden"
>
<div
class=
"flex-items-center gap-8 fs-16 fw-700 color-000 lh-24 mb-16"
>
<el-icon
class=
"color-aaa"
>
<Document
/>
</el-icon>
<span>
第 1 集 丝袜产品展示
</span>
<el-icon
class=
"fs-14 color-536"
>
<Notebook
/>
</el-icon>
<span
class=
"fs-12 color-536"
>
剧本
</span>
</div>
<div
class=
"asset-filter flex-items-center h-34 p-4 round-8 mb-14"
>
<el-button
v-for=
"item in filters"
:key=
"item.value"
class=
"filter-btn flex-1 h-26 fs-12 border-none"
:class=
"
{ active: activeFilter === item.value }" text @click="activeFilter = item.value">
{{
item
.
label
}}
</el-button>
</div>
<div
class=
"asset-scroll flex-1 over-y-auto hide-scroll"
>
<div
class=
"flex-between fs-14 fw-700 color-000 lh-24 mb-10"
>
<span>
角色
</span>
<el-icon
class=
"color-536 pointer"
>
<Plus
/>
</el-icon>
</div>
<div
class=
"role-card mb-18"
>
<img
:src=
"peopleImg"
alt=
"苏晓"
class=
"role-img w-118 h-118 round-8 cover"
/>
<div
class=
"fs-14 color-000 lh-22 mt-6"
>
苏晓
</div>
<div
class=
"fs-12 color-536 lh-20"
>
主形象
</div>
<el-button
class=
"w-118 h-32 mt-8 fs-12"
>
未配置音色
</el-button>
</div>
<div
class=
"flex-between fs-14 fw-700 color-000 lh-24 mb-10"
>
<span>
场景
</span>
<el-icon
class=
"color-536 pointer"
>
<Plus
/>
</el-icon>
</div>
<div
class=
"empty-card flex-col flex-center h-186 round-8 mb-18"
>
<el-icon
class=
"fs-26 color-ccc mb-8"
>
<Picture
/>
</el-icon>
<span
class=
"fs-12 color-aaa"
>
暂未选择场景
</span>
</div>
<div
class=
"flex-between fs-14 fw-700 color-000 lh-24 mb-10"
>
<span>
道具
</span>
<el-icon
class=
"color-536 pointer"
>
<Plus
/>
</el-icon>
</div>
<div
class=
"empty-card flex-col flex-center h-80 round-8"
>
<el-icon
class=
"fs-26 color-ccc mb-8"
>
<Star
/>
</el-icon>
<span
class=
"fs-12 color-aaa"
>
暂未选择道具
</span>
</div>
</div>
</aside>
<main
class=
"story-main flex-1 flex-col hidden"
>
<header
class=
"toolbar flex-between px-12 py-10"
>
<div></div>
<div
class=
"flex-items-center gap-12"
>
<el-button
class=
"h-32 px-16 fs-12"
>
<el-icon>
<Refresh
/>
</el-icon>
重新解析
</el-button>
<el-button
class=
"h-32 px-16 fs-12"
>
<el-icon>
<List
/>
</el-icon>
批量生视频
</el-button>
</div>
</header>
<section
class=
"story-content flex-1 flex gap-12 px-12 hidden"
>
<div
class=
"script-panel flex-1 bg-fff round-12 p-12 flex-col"
>
<div
class=
"flex-items-center gap-12 mb-8"
>
<div
class=
"fs-16 fw-700 color-000"
>
分镜1
</div>
<el-tag
size=
"small"
effect=
"plain"
>
ID
</el-tag>
<el-button
class=
"h-32 px-12 fs-12"
>
<el-icon>
<Aim
/>
</el-icon>
3D导演台
<span
class=
"beta fs-10 ml-4"
>
Beta
</span>
</el-button>
</div>
<div
class=
"flex-items-center gap-8 mb-8"
>
<el-button
class=
"h-30 px-12 fs-12"
>
<el-icon>
<FolderOpened
/>
</el-icon>
项目素材库
</el-button>
<el-button
class=
"h-30 px-12 fs-12"
>
<el-icon>
<Folder
/>
</el-icon>
资产库
</el-button>
</div>
<div
class=
"prompt-row flex-items-center gap-8 h-34 mb-10"
>
<el-button
class=
"wh-34 p-0"
plain
>
<el-icon>
<Plus
/>
</el-icon>
</el-button>
<el-button
class=
"wh-34 p-0"
plain
>
<el-icon>
<CopyDocument
/>
</el-icon>
</el-button>
<span
class=
"fs-14 color-aaa"
>
使用 @ 引用角色、场景、道具、音色及参考素材,编辑更灵活,分镜更精准
</span>
</div>
<div
class=
"script-scroll flex-1 over-y-auto hide-scroll pr-8"
>
<p
class=
"fs-14 color-000 lh-28 mb-18"
>
画风: 写实风格, 细节刻画逼真,参考院线电影,真人电影风格,影视大片,真实透视比例,细节清晰不过度锐化
</p>
<p
class=
"fs-14 color-000 lh-28 mb-18"
>
分镜具体动作描述:
<br
/>
整体视觉基调:明亮柔和的自然光影,画面干净,色调温暖,营造出舒适真实的居家氛围。
</p>
<p
v-for=
"item in shots"
:key=
"item.id"
class=
"fs-14 color-000 lh-28 mb-18"
>
镜头
{{
item
.
id
}}
(
{{
item
.
duration
}}
秒):【场景】居家客厅,室内,白天【机位】
{{
item
.
camera
}}
【站位】苏晓
{{
item
.
position
}}
【动作】
{{
item
.
action
}}
(画外音,苏晓解说):“
{{
item
.
speech
}}
”
</p>
</div>
<div
class=
"script-footer flex-between pt-8"
>
<div
class=
"flex-items-center gap-6"
>
<el-tag
effect=
"plain"
>
Doubao-Seedance-2.5
</el-tag>
<el-tag
effect=
"plain"
>
27s | 720p | 1个 | mov
</el-tag>
<el-tag
effect=
"plain"
>
@
</el-tag>
</div>
<div
class=
"flex-items-center gap-8"
>
<el-button
class=
"wh-32 p-0"
>
<el-icon>
<Pointer
/>
</el-icon>
</el-button>
<el-button
type=
"primary"
class=
"h-32 px-18 border-none"
>
<el-icon>
<MagicStick
/>
</el-icon>
6,129
</el-button>
</div>
</div>
</div>
<div
class=
"preview-panel flex-1 bg-fff round-12 p-12 flex-col"
>
<div
class=
"preview-tabs flex justify-end gap-26 fs-14 mb-8"
>
<span
class=
"preview-tab pointer"
:class=
"
{ active: previewMode === 'generated' }"
@click="previewMode = 'generated'">分镜生成
</span>
<span
class=
"preview-tab pointer"
:class=
"
{ active: previewMode === 'origin' }"
@click="previewMode = 'origin'">原视频
</span>
</div>
<div
v-if=
"previewMode === 'generated'"
class=
"preview-empty flex-1 flex-col flex-center round-8"
>
<el-icon
class=
"fs-36 color-ccc mb-8"
>
<VideoCamera
/>
</el-icon>
<span
class=
"fs-12 color-aaa"
>
请先完成分镜视频生成
</span>
</div>
<div
v-else
class=
"origin-preview flex-1 flex-center round-8 hidden relative"
>
<img
:src=
"peopleImg"
alt=
"原视频"
class=
"origin-img h-full cover"
/>
<span
class=
"play-btn absolute flex-center wh-64 round pointer"
>
<el-icon
class=
"fs-40 color-fff"
>
<VideoPlay
/>
</el-icon>
</span>
</div>
</div>
</section>
<footer
class=
"shot-strip bg-fff round-10 mx-12 my-12 px-14 py-14"
>
<div
class=
"fs-14 color-000 lh-24 mb-10"
>
1 分镜
</div>
<div
class=
"shot-card flex-col items-center pointer"
>
<div
class=
"shot-thumb flex-center w-130 h-90 round-6"
>
<el-icon
class=
"fs-28 color-ccc"
>
<VideoCamera
/>
</el-icon>
</div>
<div
class=
"fs-12 color-536 lh-22 mt-6"
>
分镜 01 · 待生成
</div>
</div>
</footer>
</main>
</div>
</section>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
onBeforeUnmount
,
onMounted
,
ref
}
from
'vue'
;
import
{
Aim
,
CopyDocument
,
Document
,
Folder
,
FolderOpened
,
List
,
MagicStick
,
Notebook
,
Picture
,
Plus
,
Pointer
,
Refresh
,
Star
,
VideoCamera
,
VideoPlay
,
}
from
'@element-plus/icons-vue'
;
import
peopleImg
from
'@/static/image/meinv.png'
;
type
FilterValue
=
'all'
|
'role'
|
'scene'
|
'prop'
;
type
PreviewMode
=
'generated'
|
'origin'
;
const
filters
:
{
label
:
string
;
value
:
FilterValue
}[]
=
[
{
label
:
'全部'
,
value
:
'all'
},
{
label
:
'角色'
,
value
:
'role'
},
{
label
:
'场景'
,
value
:
'scene'
},
{
label
:
'道具'
,
value
:
'prop'
},
];
const
shots
=
[
{
id
:
1
,
duration
:
'2.0'
,
camera
:
'位于苏晓正前方约1.5米,与她盘坐坐姿平齐的高度,固定不动。'
,
position
:
'盘腿坐在地面上,位于画面中央,面向镜头。'
,
action
:
'双手捧着一个白色包装盒,面带微笑地看向镜头。'
,
speech
:
'今天新到手了来蜜的热销款0.01,真的很清透。'
,
},
{
id
:
2
,
duration
:
'1.3'
,
camera
:
'位于苏晓正前方,略微俯拍她的手部和腿部,固定不动。'
,
position
:
'保持盘坐姿势,身体位于画面外,仅展示手部和膝盖区域。'
,
action
:
'打开包装盒,从里面拿出一团雾好的黑色丝袜。'
,
speech
:
'让我看看是怎么个超薄丝滑法。'
,
},
{
id
:
3
,
duration
:
'2.2'
,
camera
:
'与镜头2机位相同,固定不动。'
,
position
:
'保持盘坐姿势,身体位于画面外,仅展示手部和膝盖区域。'
,
action
:
'双手捏住黑色丝袜的两端,将其缓缓拉伸展开。'
,
speech
:
'颜色均匀通透。'
,
},
{
id
:
4
,
duration
:
'2.5'
,
camera
:
'位于苏晓右侧,平拍她的手部,固定不动。'
,
position
:
'苏晓的手臂举起,将丝袜置于镜头与窗帘之间。'
,
action
:
'双手撑开黑色丝袜的一部分,对着光亮处展示其通透质感。'
,
speech
:
'真的很清透。'
,
},
{
id
:
5
,
duration
:
'1.8'
,
camera
:
'机位位于苏晓正前方低位,略微仰拍,固定不动。'
,
position
:
'双腿交叉站立,身体位于画面中央。'
,
action
:
'穿着黑色丝袜和黑色高跟鞋,保持站姿,展示腿部穿着效果。'
,
speech
:
'是那种高级的黑丝质感。'
,
},
{
id
:
6
,
duration
:
'2.0'
,
camera
:
'机位在苏晓右前方,平拍,固定不动。'
,
position
:
'坐在黑色沙发上,跷着二郎腿,身体朝向镜头左前方约45度。'
,
action
:
'右手轻轻地从膝盖处向下抚摸穿着丝袜的小腿,面带微笑。'
,
speech
:
'穿上真的超舒适。'
,
},
{
id
:
7
,
duration
:
'1.2'
,
camera
:
'机位贴近苏晓的腿部,平拍特写,固定不动。'
,
position
:
'腿部占据画面大部分。'
,
action
:
'一只手在穿着黑色丝袜的小腿上缓慢地抚摸,展示丝袜的光泽和丝滑感。'
,
speech
:
'它的面料很亲肤。'
,
},
{
id
:
8
,
duration
:
'4.0'
,
camera
:
'机位在苏晓正前方,平拍,固定不动。'
,
position
:
'位于镜头前,身体被丝袜遮挡,仅手臂可见。'
,
action
:
'双手分别抓住黑色丝袜的腰部两端,用力向两侧反复拉伸。'
,
speech
:
'丝袜细腻,弹性也很大,各种身材的姐妹都能穿。'
,
},
{
id
:
9
,
duration
:
'1.8'
,
camera
:
'机位在苏晓右前方低位,略微仰拍她的腿部,固定不动。'
,
position
:
'穿着丝袜和高跟鞋,侧身坐在一个黑色吧台凳上,双腿交叠。'
,
action
:
'镜头停留在腿部线条,展示自然的光泽。'
,
speech
:
'显瘦又高级。'
,
},
];
const
isReady
=
ref
(
false
);
const
planningProgress
=
ref
(
0
);
const
activeFilter
=
ref
<
FilterValue
>
(
'all'
);
const
previewMode
=
ref
<
PreviewMode
>
(
'generated'
);
let
timer
:
number
|
undefined
;
const
runPlanning
=
()
=>
{
timer
=
window
.
setInterval
(()
=>
{
planningProgress
.
value
=
Math
.
min
(
100
,
planningProgress
.
value
+
20
);
if
(
planningProgress
.
value
>=
100
)
{
window
.
clearInterval
(
timer
);
timer
=
undefined
;
window
.
setTimeout
(()
=>
{
isReady
.
value
=
true
;
},
400
);
}
},
420
);
};
onMounted
(()
=>
{
runPlanning
();
});
onBeforeUnmount
(()
=>
{
if
(
timer
)
{
window
.
clearInterval
(
timer
);
}
});
</
script
>
<
style
scoped
lang=
"scss"
>
.step-three
{
background
:
#f8fafc
;
}
.planning-panel
{
min-height
:
calc
(
100vh
-
60px
);
}
.color-536
{
color
:
#536883
;
}
.planning-card
{
border
:
1px
solid
#e2e8f3
;
}
.episode-rail
{
flex
:
0
0
48px
;
background
:
#eef3ff
;
}
.episode-no
{
background
:
rgba
(
var
(
--
main-color
)
,
1
);
}
.asset-sidebar
{
flex
:
0
0
282px
;
min-height
:
0
;
}
.asset-filter
{
background
:
#f4f7fb
;
}
.filter-btn.active
{
background
:
#fff
;
box-shadow
:
0
1px
4px
rgba
(
17
,
36
,
62
,
.12
);
}
.asset-scroll
,
.script-scroll
,
.story-main
{
min-height
:
0
;
}
.role-img
,
.empty-card
,
.preview-empty
,
.shot-thumb
{
background
:
#f3f5f9
;
}
.story-content
{
min-height
:
0
;
}
.toolbar
{
flex
:
0
0
54px
;
border-bottom
:
1px
solid
#dce6f2
;
}
.script-panel
,
.preview-panel
,
.shot-strip
{
border
:
1px
solid
#e2e8f3
;
}
.script-scroll
{
border-top
:
1px
solid
#edf2f7
;
border-bottom
:
1px
solid
#edf2f7
;
}
.beta
{
color
:
#6a44ff
;
}
.preview-panel
{
min-width
:
420px
;
}
.preview-tab
{
position
:
relative
;
height
:
28px
;
line-height
:
28px
;
color
:
#536883
;
&
.active
{
color
:
#000
;
&
:
:
after
{
position
:
absolute
;
right
:
0
;
bottom
:
0
;
left
:
0
;
height
:
2px
;
background
:
rgba
(
var
(
--
main-color
)
,
1
);
content
:
''
;
}
}
}
.origin-preview
{
background
:
#000
;
}
.origin-img
{
max-width
:
74%
;
}
.play-btn
{
top
:
50%
;
left
:
50%
;
background
:
rgba
(
0
,
0
,
0
,
.38
);
transform
:
translate
(
-50%
,
-50%
);
}
.shot-strip
{
flex
:
0
0
164px
;
}
.shot-card
{
width
:
150px
;
}
.shot-thumb
{
border
:
2px
solid
rgba
(
var
(
--
main-color
)
,
1
);
}
:deep
(
.planning-card
.el-progress-bar__outer
)
{
background
:
#eef1f8
;
}
:deep
(
.planning-card
.el-progress-bar__inner
)
{
background
:
rgba
(
var
(
--
main-color
)
,
1
);
}
</
style
>
apps/web-vue/src/views/burst/components/Step-4.vue
0 → 100644
View file @
5d11284c
<
template
>
<div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
</
script
>
<
style
scoped
lang=
"scss"
>
</
style
>
\ No newline at end of file
apps/web-vue/src/views/burst/components/VoiceConfigDialog.vue
0 → 100644
View file @
5d11284c
<
template
>
<Dialog
v-model=
"dialogVisible"
bg-color=
"#fff"
width=
"720"
>
<div
class=
"voice-dialog bg-fff round-10 hidden flex-col"
>
<header
class=
"dialog-header flex-items-center gap-12 px-28 pt-28 pb-22"
>
<span
class=
"header-icon flex-center wh-48 round-10"
>
<el-icon
class=
"fs-24 color-536"
>
<Microphone
/>
</el-icon>
</span>
<h3
class=
"fs-18 fw-700 color-000"
>
配置音色
</h3>
</header>
<section
class=
"dialog-body flex-col px-32"
>
<div
class=
"tab-row flex-items-center h-44 p-4 round-6 mb-20"
>
<div
v-for=
"item in tabs"
:key=
"item.value"
class=
"tab-item flex-center h-36 fs-14 pointer"
:class=
"
{ active: activeTab === item.value }" @click="activeTab = item.value">
{{
item
.
label
}}
</div>
</div>
<div
v-if=
"activeTab === 'design'"
class=
"design-panel flex-1 flex hidden"
>
<div
class=
"design-form flex-1 pr-20"
>
<div
class=
"field-label flex-items-center gap-8 mb-8"
>
<span
class=
"index-dot flex-center wh-18 round fs-12"
>
1
</span>
<span
class=
"fs-14 color-666"
>
提示词
</span>
</div>
<el-input
v-model=
"voicePrompt"
class=
"voice-textarea mb-18"
type=
"textarea"
resize=
"none"
:rows=
"7"
placeholder=
"请输入音色提示词"
/>
<div
class=
"field-label flex-items-center gap-8 mb-8"
>
<span
class=
"index-dot flex-center wh-18 round fs-12"
>
2
</span>
<span
class=
"fs-14 color-666"
>
试听文本(可选)
</span>
</div>
<el-input
v-model=
"auditionText"
class=
"voice-textarea"
type=
"textarea"
resize=
"none"
:rows=
"3"
placeholder=
"请输入试听文本"
/>
<div
class=
"flex-center mt-42"
>
<el-button
type=
"primary"
class=
"generate-btn w-260 h-36 border-none fs-14"
:disabled=
"!voicePrompt.trim()"
>
<el-icon>
<MagicStick
/>
</el-icon>
<span>
100
</span>
</el-button>
</div>
</div>
<div
class=
"history-panel flex-1 pl-20"
>
<div
class=
"fs-14 color-666 mb-10"
>
历史
</div>
<div
class=
"history-empty flex-col flex-center h-full color-aaa fs-14"
>
<el-icon
class=
"fs-28 mb-8"
>
<Microphone
/>
</el-icon>
<span>
暂未生成角色音频
</span>
</div>
</div>
</div>
<div
v-else-if=
"activeTab === 'library'"
class=
"library-panel flex-1 flex-col hidden"
>
<div
class=
"filter-row flex-items-center gap-12 mb-20"
>
<el-input
v-model=
"searchKeyword"
class=
"search-input"
placeholder=
"搜索"
>
<template
#
prefix
>
<el-icon>
<Search
/>
</el-icon>
</
template
>
</el-input>
<el-select
v-model=
"genderValue"
class=
"filter-select"
placeholder=
"全部性别"
>
<el-option
v-for=
"item in genderOptions"
:key=
"item"
:label=
"item"
:value=
"item"
/>
</el-select>
<el-select
v-model=
"ageValue"
class=
"filter-select"
placeholder=
"全部年龄"
>
<el-option
v-for=
"item in ageOptions"
:key=
"item"
:label=
"item"
:value=
"item"
/>
</el-select>
<el-select
v-model=
"languageValue"
class=
"filter-select"
placeholder=
"全部语言"
>
<el-option
v-for=
"item in languageOptions"
:key=
"item"
:label=
"item"
:value=
"item"
/>
</el-select>
</div>
<div
class=
"voice-list flex flex-wrap over-y-auto hide-scroll"
>
<div
v-for=
"item in voiceList"
:key=
"item.id"
class=
"voice-item flex-items-center gap-10 p-10 round-8 pointer"
:class=
"{ active: selectedVoiceId === item.id }"
@
click=
"selectedVoiceId = item.id"
>
<div
class=
"voice-avatar relative wh-42 round hidden"
>
<img
:src=
"item.avatar"
:alt=
"item.name"
class=
"w-full h-full cover"
/>
<span
class=
"play-dot absolute flex-center wh-14 round color-fff"
>
<el-icon
class=
"fs-10"
>
<VideoPlay
/>
</el-icon>
</span>
</div>
<div
class=
"flex-1 hidden"
>
<div
class=
"fs-14 fw-700 color-000 lh-22"
>
{{ item.name }}
</div>
<div
class=
"fs-12 color-aaa lh-20 ellipsis"
>
{{ item.desc }}
</div>
</div>
</div>
</div>
</div>
<div
v-else-if=
"activeTab === 'local'"
class=
"local-panel flex-1"
>
<UploadFile
v-model=
"localFiles"
list-type=
"drap"
accept=
".mp3,.wav"
:max-size=
"10"
:show-file-list=
"true"
>
<
template
#
trigger
>
<div
class=
"upload-trigger flex-col flex-center"
>
<el-icon
class=
"fs-28 color-536 mb-14"
>
<Microphone
/>
</el-icon>
<div
class=
"fs-16 fw-700 color-333 lh-26"
>
点击或拖拽音频文件到此处上传
</div>
<div
class=
"fs-12 color-888 lh-22"
>
支持 MP3、WAV,单个文件不超过 10MB,超过 5 秒将自动裁剪
</div>
</div>
</
template
>
</UploadFile>
</div>
<div
v-else
class=
"audio-panel flex-1 flex-col"
>
<div
class=
"flex-between mb-12"
>
<el-select
v-model=
"teamValue"
class=
"team-select"
>
<el-option
v-for=
"item in teamOptions"
:key=
"item"
:label=
"item"
:value=
"item"
/>
</el-select>
<div
class=
"audio-tabs flex-items-center gap-24 fs-14"
>
<span
class=
"audio-tab active pointer"
>
所有音频
</span>
<span
class=
"audio-tab color-666 pointer"
>
文件夹
</span>
</div>
</div>
<el-input
v-model=
"audioKeyword"
class=
"mb-20"
placeholder=
"搜索"
>
<
template
#
prefix
>
<el-icon>
<Search
/>
</el-icon>
</
template
>
</el-input>
<div
class=
"audio-empty flex-1 flex-col flex-center color-aaa fs-14"
>
<span
class=
"empty-icon flex-center wh-48 round-10 mb-14"
>
<el-icon
class=
"fs-24"
>
<Microphone
/>
</el-icon>
</span>
<span>
暂无音频内容
</span>
</div>
</div>
</section>
<footer
class=
"dialog-footer flex justify-end gap-12 px-24 py-20"
>
<el-button
class=
"w-96 h-36 fs-14"
@
click=
"dialogVisible = false"
>
取消
</el-button>
<el-button
type=
"primary"
class=
"confirm-btn w-96 h-36 fs-14 border-none"
@
click=
"confirmVoice"
>
确认
</el-button>
</footer>
</div>
</Dialog>
</template>
<
script
setup
lang=
"ts"
>
import
{
computed
,
ref
}
from
'vue'
;
import
type
{
UploadUserFile
}
from
'element-plus'
;
import
{
MagicStick
,
Microphone
,
Search
,
VideoPlay
}
from
'@element-plus/icons-vue'
;
import
Dialog
from
'@/components/Dialog/index.vue'
;
import
UploadFile
from
'@/components/Upload/index.vue'
;
import
peopleImg
from
'@/static/image/meinv.png'
;
import
productImg
from
'@/static/image/huaban.png'
;
type
VoiceTab
=
'design'
|
'library'
|
'local'
|
'audio'
;
type
VoiceItem
=
{
id
:
string
;
name
:
string
;
desc
:
string
;
avatar
:
string
;
};
const
props
=
defineProps
<
{
modelValue
:
boolean
;
}
>
();
const
emit
=
defineEmits
<
{
'update:modelValue'
:
[
value
:
boolean
];
confirm
:
[
value
:
{
tab
:
VoiceTab
;
voiceId
?:
string
;
prompt
?:
string
}];
}
>
();
const
dialogVisible
=
computed
({
get
:
()
=>
props
.
modelValue
,
set
:
(
value
)
=>
emit
(
'update:modelValue'
,
value
),
});
const
tabs
:
{
label
:
string
;
value
:
VoiceTab
}[]
=
[
{
label
:
'智能音色设计'
,
value
:
'design'
},
{
label
:
'音色库选择'
,
value
:
'library'
},
{
label
:
'本地上传'
,
value
:
'local'
},
{
label
:
'音频库'
,
value
:
'audio'
},
];
const
genderOptions
=
[
'全部性别'
,
'男声'
,
'女声'
];
const
ageOptions
=
[
'全部年龄'
,
'青年'
,
'中年'
,
'老年'
];
const
languageOptions
=
[
'全部语言'
,
'中文'
,
'英文'
,
'粤语'
];
const
teamOptions
=
[
'团队资产'
,
'我的资产'
];
const
voiceList
:
VoiceItem
[]
=
[
{
id
:
'voice-1'
,
name
:
'婆婆'
,
desc
:
'语调舒缓、声线慈祥,自带岁月感的长辈音色'
,
avatar
:
peopleImg
},
{
id
:
'voice-2'
,
name
:
'幽默大爷'
,
desc
:
'墨迹沧桑的乐观爷爷,通透豁达又从容'
,
avatar
:
productImg
},
{
id
:
'voice-3'
,
name
:
'和蔼奶奶'
,
desc
:
'慈祥的老奶奶,耐心亲切,散发着岁月沉淀'
,
avatar
:
peopleImg
},
{
id
:
'voice-4'
,
name
:
'武则天'
,
desc
:
'声线威严、气场拉满,自带帝王霸气的御姐音'
,
avatar
:
productImg
},
{
id
:
'voice-5'
,
name
:
'邻居阿姨'
,
desc
:
'温暖成熟的中年阿姨,兼具知性气质'
,
avatar
:
peopleImg
},
{
id
:
'voice-6'
,
name
:
'女雷神'
,
desc
:
'声线雄浑、气场拉满,充满力量感的御姐音'
,
avatar
:
productImg
},
{
id
:
'voice-7'
,
name
:
'温柔妈妈'
,
desc
:
'语调舒缓、咬字温润,自带母性柔光的治愈音'
,
avatar
:
peopleImg
},
{
id
:
'voice-8'
,
name
:
'胡子叔叔'
,
desc
:
'历经风雨后变得沉稳的大叔,果敢让人信赖'
,
avatar
:
productImg
},
{
id
:
'voice-9'
,
name
:
'油腻大叔'
,
desc
:
'沧桑的大叔,性格张扬,十分自信爱吹牛'
,
avatar
:
peopleImg
},
{
id
:
'voice-10'
,
name
:
'诡异神秘'
,
desc
:
'诡异神秘的大叔,气质沧桑,自带压迫感'
,
avatar
:
productImg
},
];
const
activeTab
=
ref
<
VoiceTab
>
(
'design'
);
const
voicePrompt
=
ref
(
'女声,青年音色,音调偏高,质感清透柔亮,气息绵长,语速轻快,情绪底色亲切活泼'
);
const
auditionText
=
ref
(
'你好,我的声音,为你演尽喜怒哀乐。'
);
const
searchKeyword
=
ref
(
''
);
const
genderValue
=
ref
(
'全部性别'
);
const
ageValue
=
ref
(
'全部年龄'
);
const
languageValue
=
ref
(
'全部语言'
);
const
selectedVoiceId
=
ref
(
'voice-1'
);
const
localFiles
=
ref
<
UploadUserFile
[]
>
([]);
const
teamValue
=
ref
(
'团队资产'
);
const
audioKeyword
=
ref
(
''
);
const
confirmVoice
=
()
=>
{
emit
(
'confirm'
,
{
tab
:
activeTab
.
value
,
voiceId
:
selectedVoiceId
.
value
,
prompt
:
voicePrompt
.
value
,
});
dialogVisible
.
value
=
false
;
};
</
script
>
<
style
scoped
lang=
"scss"
>
.voice-dialog
{
height
:
660px
;
}
.dialog-header
{
flex
:
0
0
98px
;
}
.dialog-body
{
flex
:
1
;
min-height
:
0
;
}
.dialog-footer
{
flex
:
0
0
76px
;
}
.header-icon
{
box-shadow
:
0
2px
8px
rgba
(
22
,
36
,
58
,
.12
);
}
.color-536
{
color
:
#536883
;
}
.tab-row
{
background
:
#f5f7fa
;
}
.tab-item
{
flex
:
1
;
color
:
#000
;
border
:
1px
solid
transparent
;
border-radius
:
6px
;
&
.active
{
background
:
#fff
;
border-color
:
#d5ddeb
;
box-shadow
:
0
1px
4px
rgba
(
22
,
36
,
58
,
.1
);
}
}
.design-panel
,
.library-panel
,
.local-panel
,
.audio-panel
{
min-height
:
0
;
}
.design-form
{
border-right
:
1px
solid
#e7edf5
;
}
.index-dot
{
color
:
#8aa0bf
;
border
:
1px
solid
#ccd7e6
;
}
.voice-textarea
{
:deep
(
.el-textarea__inner
)
{
color
:
#000
;
line-height
:
24px
;
border-radius
:
4px
;
box-shadow
:
0
0
0
1px
#d5ddeb
inset
;
}
}
.generate-btn
{
background
:
linear-gradient
(
90deg
,
#2382ff
0%
,
#b947ee
100%
);
}
.history-empty
{
min-height
:
300px
;
}
.search-input
{
flex
:
1
;
}
.filter-select
{
width
:
112px
;
}
.voice-list
{
flex
:
1
;
gap
:
6px
24px
;
}
.voice-item
{
flex
:
0
0
calc
((
100%
-
24px
)
/
2
);
border
:
1px
solid
transparent
;
&
.active
,
&
:hover
{
border-color
:
rgba
(
var
(
--
main-color
)
,
1
);
background
:
rgba
(
var
(
--
main-color
)
,
.04
);
}
}
.play-dot
{
right
:
0
;
bottom
:
0
;
background
:
#6b7b93
;
}
.local-panel
{
:deep
(
.el-upload
),
:deep
(
.el-upload-dragger
)
{
width
:
100%
;
}
:deep
(
.el-upload-dragger
)
{
height
:
318px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
border-color
:
#d9e2f2
;
background
:
#fbfcff
;
}
}
.audio-tab
{
position
:
relative
;
height
:
34px
;
line-height
:
34px
;
&
.active
{
color
:
#000
;
&
:
:
after
{
position
:
absolute
;
right
:
0
;
bottom
:
0
;
left
:
0
;
height
:
2px
;
background
:
rgba
(
var
(
--
main-color
)
,
1
);
content
:
''
;
}
}
}
.team-select
{
width
:
136px
;
}
.audio-empty
{
min-height
:
280px
;
}
.empty-icon
{
box-shadow
:
0
2px
8px
rgba
(
22
,
36
,
58
,
.1
);
}
.confirm-btn
{
background
:
#a5adf7
;
}
:deep
(
.el-input__wrapper
),
:deep
(
.el-select__wrapper
)
{
border-radius
:
4px
;
box-shadow
:
0
0
0
1px
#d5ddeb
inset
;
}
</
style
>
apps/web-vue/src/views/burst/index copy.vue
0 → 100644
View file @
5d11284c
<
template
>
<div
class=
"vh100 hidden flex-col"
>
<div
class=
"flex-center bg-fff py-10"
>
<div
class=
"flex-center gap-10 mr-100 bg-eee round-100 p-4"
>
<div
v-for=
"(item, index) in steps"
:key=
"index"
class=
"flex-items-center gap-10 default"
:class=
"
{ finish: item.stepn
<
currentStep
,
active:
item
.
stepn =
==
currentStep
,
select:
selectStep =
==
item
.
stepn
}"
@
click=
"lookStep(item)"
>
<div
class=
"flex-items-center px-10 py-2 gap-6 round-100 fs-12 pointer color-888 name"
>
<el-icon>
<component
:is=
"item.icon"
/>
</el-icon>
<span>
{{
item
.
name
}}
</span>
</div>
<div
v-if=
"index
<
steps
.
length
-
1
"
class=
"w-30 h-2 line"
></div>
</div>
</div>
</div>
<div
class=
"flex-1 p-20 hidden"
>
<Step1
v-if=
"selectStep === 1"
/>
<!--
<div
class=
"bg-fff h-full over-y-auto round-12"
>
<div
v-for=
"value in 100"
>
发的发生大是
</div>
</div>
-->
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
'vue'
;
import
{
Edit
}
from
'@element-plus/icons-vue'
;
import
Step1
from
'./components/Step-1.vue'
;
import
Step2
from
'./components/Step-2.vue'
;
import
Step3
from
'./components/Step-3.vue'
;
import
Step4
from
'./components/Step-4.vue'
;
const
title
=
ref
(
'商品分析信息'
)
const
currentStep
=
ref
(
4
)
const
selectStep
=
ref
(
1
)
const
steps
=
ref
([
{
name
:
'原片'
,
icon
:
Edit
,
stepn
:
1
,
desc
:
'商品分析信息'
},
{
name
:
'设定'
,
icon
:
Edit
,
stepn
:
2
,
desc
:
'创意与分镜'
},
{
name
:
'分镜'
,
icon
:
Edit
,
stepn
:
3
,
desc
:
'视频预览'
},
{
name
:
'视频'
,
icon
:
Edit
,
stepn
:
4
,
desc
:
'视频成片'
},
])
const
lookStep
=
(
item
:
any
)
=>
{
if
(
currentStep
.
value
<
item
.
stepn
)
return
selectStep
.
value
=
item
.
stepn
title
.
value
=
item
.
desc
}
</
script
>
<
style
scoped
lang=
"scss"
>
.default
{
.name
{
&
:hover
{
background-color
:
#eee
;
}
}
.line
{
background
:
#ddd
;
}
&
.finish
{
.name
{
color
:
#01b95d
;
}
}
&
.active
{
.name
{
color
:
rgba
(
var
(
--
main-color
)
,
1
);
}
}
&
.select
{
.name
{
color
:
#fff
;
background-color
:
rgba
(
var
(
--
main-color
)
,
1
);
}
}
}
</
style
>
\ No newline at end of file
apps/web-vue/src/views/burst/index.vue
0 → 100644
View file @
5d11284c
<
template
>
<div
class=
"vh100"
style=
"padding: 0 20%"
>
<div
class=
"mb-30"
style=
"margin-top: 20%;"
>
<div
class=
"text-center fs-18 fw-600"
>
创意复刻,让创意焕发新生
</div>
<div
class=
"text-center color-888 fs-12 mt-20"
>
上传您需要转换风格、语言或人物角色的视频片断,通过 AI 大模型极速复刻整个故事片断。
</div>
</div>
<div
class=
"w-full"
>
<div
class=
"bg-fff p-20 round-12"
>
<UploadFile
listType=
"drap"
bgClass=
"xxxx"
>
<template
#
trigger
>
<div
class=
"flex-center flex-col"
>
<div
class=
"w-40 h-40 round bg-eee flex-center color-888"
>
<el-icon
size=
"20"
>
<Upload
/>
</el-icon>
</div>
<div
class=
"color-333 fs-12 fw-600 mt-10"
>
点击上传 或 拖拽视频文件到此区域
</div>
<div
class=
"color-888 fs-12 mt-10"
>
视频格式支持mp4/mov,大小不超过500M,时长不超过3分钟
</div>
</div>
</
template
>
</UploadFile>
</div>
</div>
<div
class=
"setting-card w-full flex-between gap-18 px-20 py-14 mt-24 round-14 bg-fff"
>
<div
class=
"setting-left flex-items-center"
>
<el-select
v-model=
"language"
class=
"language-select"
size=
"large"
>
<
template
#
prefix
>
<el-icon>
<Monitor
/>
</el-icon>
<span
class=
"fs-12 color-666 ml-4"
>
目标语种
</span>
</
template
>
<el-option
v-for=
"item in languageOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
</div>
<div
class=
"setting-right flex-items-center gap-8"
>
<el-popover
v-model:visible=
"ratioVisible"
placement=
"bottom"
:width=
"158"
trigger=
"click"
popper-class=
"burst-setting-popper"
:hide-after=
"0"
>
<
template
#
reference
>
<div
class=
"setting-trigger flex-center gap-8 h-34 px-12 round-8 pointer"
:class=
"
{ active: ratioVisible }">
<span
class=
"ratio-icon"
:class=
"selectedRatio.iconClass"
></span>
<span
class=
"fs-12 fw-700 color-000"
>
{{
selectedRatio
.
value
}}
</span>
<el-icon
class=
"fs-12 color-666"
>
<ArrowUp
v-if=
"ratioVisible"
/>
<ArrowDown
v-else
/>
</el-icon>
</div>
</
template
>
<div
class=
"select-panel p-6"
>
<div
class=
"fs-12 color-888 px-8 py-6"
>
比例
</div>
<div
v-for=
"item in ratioOptions"
:key=
"item.value"
class=
"select-option flex-items-center gap-10 h-34 px-8 round-4 pointer"
:class=
"{ active: ratio === item.value }"
@
click=
"selectRatio(item.value)"
>
<span
class=
"ratio-icon"
:class=
"item.iconClass"
></span>
<span
class=
"fs-12 fw-700 color-000"
>
{{ item.value }}
</span>
</div>
</div>
</el-popover>
<el-popover
v-model:visible=
"resolutionVisible"
placement=
"bottom"
:width=
"158"
trigger=
"click"
popper-class=
"burst-setting-popper"
:hide-after=
"0"
>
<
template
#
reference
>
<div
class=
"setting-trigger flex-between gap-12 h-34 px-12 round-8 pointer w-80"
:class=
"
{ active: resolutionVisible }">
<span
class=
"fs-12 fw-700 color-000"
>
{{
resolution
}}
</span>
<el-icon
class=
"fs-12 color-666"
>
<ArrowUp
v-if=
"resolutionVisible"
/>
<ArrowDown
v-else
/>
</el-icon>
</div>
</
template
>
<div
class=
"select-panel p-6"
>
<div
class=
"fs-12 color-888 px-8 py-6"
>
分辨率
</div>
<div
v-for=
"item in resolutionOptions"
:key=
"item.value"
class=
"select-option h-34 lh-34 px-8 round-4 fs-12 fw-700 pointer"
:class=
"{ active: resolution === item.value }"
@
click=
"selectResolution(item.value)"
>
{{ item.value }}
</div>
</div>
</el-popover>
<el-popover
v-model:visible=
"styleVisible"
placement=
"bottom-end"
:width=
"600"
trigger=
"click"
popper-class=
"burst-style-popper"
:hide-after=
"0"
>
<
template
#
reference
>
<div
class=
"style-trigger flex-items-center gap-8 h-34 px-8 round-8 pointer"
:class=
"
{ active: styleVisible }">
<img
:src=
"selectedStyle.image"
:alt=
"selectedStyle.name"
class=
"style-thumb wh-28 round-6 cover"
/>
<span
class=
"fs-12 fw-700 color-000"
>
{{
selectedStyle
.
name
}}
</span>
<el-icon
class=
"fs-12 color-666"
>
<ArrowUp
v-if=
"styleVisible"
/>
<ArrowDown
v-else
/>
</el-icon>
</div>
</
template
>
<div
class=
"style-panel p-24 hide-scroll"
>
<div
class=
"fs-16 fw-700 color-000 mb-18"
>
风格库
</div>
<div
class=
"flex-between mb-20"
>
<div
class=
"style-tabs flex-items-center h-34 p-4 round-8"
>
<span
v-for=
"item in styleTabs"
:key=
"item.value"
class=
"style-tab h-26 lh-26 px-14 round-6 fs-12 pointer"
:class=
"{ active: activeStyleTab === item.value }"
@
click=
"activeStyleTab = item.value"
>
{{ item.label }}
<span
class=
"color-888"
>
{{ item.count }}
</span>
</span>
</div>
<el-input
v-model=
"styleKeyword"
class=
"style-search"
placeholder=
"搜索"
>
<
template
#
prefix
>
<el-icon>
<Search
/>
</el-icon>
</
template
>
</el-input>
</div>
<div
class=
"style-list flex flex-wrap gap-10"
>
<div
class=
"style-card custom-style flex-col flex-center round-12 pointer"
:class=
"{ active: style === 'custom' }"
@
click=
"selectStyle('custom')"
>
<el-icon
class=
"fs-24 color-666 mb-10"
>
<Picture
/>
</el-icon>
<span
class=
"fs-12 color-666"
>
自定义风格
</span>
</div>
<div
v-for=
"item in filteredStyles"
:key=
"item.value"
class=
"style-card relative round-12 hidden pointer"
:class=
"{ active: style === item.value }"
@
click=
"selectStyle(item.value)"
>
<img
:src=
"item.image"
:alt=
"item.name"
class=
"w-full h-full cover"
/>
<div
class=
"style-name absolute left-0 right-0 bottom-0 h-32 lh-32 text-center fs-12 fw-700 color-fff"
>
{{ item.name }}
</div>
<span
v-if=
"style === item.value"
class=
"style-check absolute flex-center wh-18 round color-fff"
>
<el-icon
class=
"fs-12"
>
<Check
/>
</el-icon>
</span>
</div>
</div>
</div>
</el-popover>
</div>
</div>
<div
class=
"flex-center"
>
<el-button
type=
"primary"
class=
"start-btn h-48 px-88 round-14 border-none fs-16 fw-700 mt-24"
@
click=
"goReplica"
>
<el-icon>
<MagicStick
/>
</el-icon>
<span>
开始智能复刻
</span>
<el-icon>
<ArrowRight
/>
</el-icon>
</el-button>
</div>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
computed
,
ref
}
from
'vue'
;
import
{
useRouter
}
from
'vue-router'
;
import
{
ArrowDown
,
ArrowRight
,
ArrowUp
,
Check
,
MagicStick
,
Monitor
,
Picture
,
Search
,
Upload
,
}
from
'@element-plus/icons-vue'
;
import
UploadFile
from
'@/components/Upload/index.vue'
import
productImg
from
'@/static/image/huaban.png'
;
import
peopleImg
from
'@/static/image/meinv.png'
;
const
router
=
useRouter
();
const
language
=
ref
(
'zh'
);
const
ratio
=
ref
(
'16:9'
);
const
resolution
=
ref
(
'4k'
);
const
style
=
ref
(
'anime'
);
const
ratioVisible
=
ref
(
false
);
const
resolutionVisible
=
ref
(
false
);
const
styleVisible
=
ref
(
false
);
const
activeStyleTab
=
ref
(
'all'
);
const
styleKeyword
=
ref
(
''
);
const
languageOptions
=
[
{
label
:
'中文'
,
value
:
'zh'
},
{
label
:
'English'
,
value
:
'en'
},
];
const
ratioOptions
=
[
{
value
:
'16:9'
,
iconClass
:
'wide'
},
{
value
:
'4:3'
,
iconClass
:
'wide'
},
{
value
:
'3:4'
,
iconClass
:
'vertical'
},
{
value
:
'9:16'
,
iconClass
:
'vertical'
},
{
value
:
'21:9'
,
iconClass
:
'cinema'
},
];
const
resolutionOptions
=
[
{
value
:
'480p'
},
{
value
:
'720p'
},
{
value
:
'1080p'
},
{
value
:
'4k'
},
];
const
styleTabs
=
[
{
label
:
'全部'
,
value
:
'all'
,
count
:
21
},
{
label
:
'真人'
,
value
:
'real'
,
count
:
10
},
{
label
:
'3D'
,
value
:
'3d'
,
count
:
5
},
{
label
:
'2D'
,
value
:
'2d'
,
count
:
6
},
{
label
:
'自定义'
,
value
:
'custom'
,
count
:
0
},
];
const
styleOptions
=
[
{
value
:
'modern'
,
name
:
'现代都市通用'
,
type
:
'real'
,
image
:
peopleImg
},
{
value
:
'film'
,
name
:
'电影感'
,
type
:
'real'
,
image
:
productImg
},
{
value
:
'anime'
,
name
:
'2D日漫'
,
type
:
'2d'
,
image
:
peopleImg
},
{
value
:
'korean-city'
,
name
:
'2D韩漫都市'
,
type
:
'2d'
,
image
:
productImg
},
{
value
:
'chinese'
,
name
:
'3D国风'
,
type
:
'3d'
,
image
:
peopleImg
},
{
value
:
'xianxia'
,
name
:
'3D仙侠'
,
type
:
'3d'
,
image
:
productImg
},
{
value
:
'korean'
,
name
:
'2D韩漫'
,
type
:
'2d'
,
image
:
peopleImg
},
{
value
:
'girl'
,
name
:
'2D乙女'
,
type
:
'2d'
,
image
:
productImg
},
{
value
:
'dream'
,
name
:
'2D国漫'
,
type
:
'2d'
,
image
:
peopleImg
},
{
value
:
'cg'
,
name
:
'CG风格'
,
type
:
'3d'
,
image
:
productImg
},
{
value
:
'cartoon'
,
name
:
'3D卡通'
,
type
:
'3d'
,
image
:
peopleImg
},
{
value
:
'dark'
,
name
:
'CG赛博朋克'
,
type
:
'3d'
,
image
:
productImg
},
{
value
:
'ink'
,
name
:
'工笔画'
,
type
:
'2d'
,
image
:
peopleImg
},
{
value
:
'ancient'
,
name
:
'古韵写实雅致风'
,
type
:
'real'
,
image
:
productImg
},
];
const
selectedRatio
=
computed
(()
=>
ratioOptions
.
find
((
item
)
=>
item
.
value
===
ratio
.
value
)
||
ratioOptions
[
0
]);
const
selectedStyle
=
computed
(()
=>
styleOptions
.
find
((
item
)
=>
item
.
value
===
style
.
value
)
||
styleOptions
[
2
]);
const
filteredStyles
=
computed
(()
=>
{
const
keyword
=
styleKeyword
.
value
.
trim
();
return
styleOptions
.
filter
((
item
)
=>
{
const
isMatchedTab
=
activeStyleTab
.
value
===
'all'
||
item
.
type
===
activeStyleTab
.
value
;
const
isMatchedKeyword
=
!
keyword
||
item
.
name
.
includes
(
keyword
);
return
isMatchedTab
&&
isMatchedKeyword
;
});
});
const
selectRatio
=
(
value
:
string
)
=>
{
ratio
.
value
=
value
;
ratioVisible
.
value
=
false
;
};
const
selectResolution
=
(
value
:
string
)
=>
{
resolution
.
value
=
value
;
resolutionVisible
.
value
=
false
;
};
const
selectStyle
=
(
value
:
string
)
=>
{
style
.
value
=
value
;
if
(
value
===
'custom'
)
{
return
;
}
styleVisible
.
value
=
false
;
};
const
goReplica
=
()
=>
{
router
.
push
({
name
:
'BurstReplica'
});
};
</
script
>
<
style
scoped
lang=
"scss"
>
.setting-card
{
box-shadow
:
0
8px
22px
rgba
(
20
,
38
,
70
,
.08
);
}
.language-select
{
width
:
160px
;
}
.setting-trigger
,
.style-trigger
{
border
:
1px
solid
#dce6f2
;
background
:
#f5f7fb
;
&
.active
{
border-color
:
rgba
(
var
(
--
main-color
)
,
1
);
box-shadow
:
0
0
0
1px
rgba
(
var
(
--
main-color
)
,
.45
);
}
}
.ratio-icon
{
display
:
inline-block
;
width
:
18px
;
height
:
12px
;
border
:
2px
solid
#56657a
;
border-radius
:
2px
;
&
.vertical
{
width
:
12px
;
height
:
18px
;
}
&
.cinema
{
width
:
22px
;
height
:
8px
;
}
}
.start-btn
{
min-width
:
400px
;
background
:
linear-gradient
(
135deg
,
#b212ff
0%
,
#573cff
100%
);
box-shadow
:
0
14px
28px
rgba
(
125
,
40
,
255
,
.28
);
}
:deep
(
.el-select__wrapper
)
{
min-height
:
34px
;
border-radius
:
10px
;
background
:
#f5f7fb
;
box-shadow
:
0
0
0
1px
#edf2f7
inset
;
}
.select-option
{
&
:hover
,
&
.active
{
background
:
#f5f7fb
;
}
}
.style-panel
{
max-height
:
500px
;
overflow-y
:
auto
;
}
.style-tabs
{
background
:
#f5f7fb
;
}
.style-tab
{
color
:
#334b68
;
&
.active
{
background
:
#fff
;
color
:
#111827
;
box-shadow
:
0
2px
8px
rgba
(
20
,
38
,
70
,
.06
);
}
}
.style-search
{
width
:
156px
;
}
.style-card
{
width
:
102px
;
height
:
102px
;
border
:
2px
solid
transparent
;
background
:
#f5f7fb
;
&
.active
{
border-color
:
rgba
(
var
(
--
main-color
)
,
1
);
}
}
.custom-style
{
border
:
1px
solid
#dce6f2
;
}
.style-name
{
background
:
linear-gradient
(
to
top
,
rgba
(
0
,
0
,
0
,
.72
)
,
rgba
(
0
,
0
,
0
,
0
));
}
.style-check
{
top
:
6px
;
right
:
6px
;
background
:
rgba
(
var
(
--
main-color
)
,
1
);
border
:
2px
solid
#fff
;
}
</
style
>
<
style
lang=
"scss"
>
.burst-setting-popper
,
.burst-style-popper
{
padding
:
0
!
important
;
border-radius
:
12px
!
important
;
}
</
style
>
apps/web-vue/src/views/burst/replica.vue
0 → 100644
View file @
5d11284c
<
template
>
<div
class=
"replica-page vh100 flex-col bgmr hidden"
>
<header
class=
"replica-header flex-between bg-fff px-28"
>
<div
class=
"project-info flex-items-center gap-14"
>
<el-icon
class=
"fs-18 color-333 pointer"
@
click=
"goBack"
>
<Back
/>
</el-icon>
<div>
<div
class=
"flex-items-center gap-8"
>
<span
class=
"fs-14 fw-700 color-000"
>
新项目
</span>
<el-icon
class=
"fs-14 color-888 pointer"
>
<Edit
/>
</el-icon>
</div>
<div
class=
"flex-items-center gap-12 fs-12 color-666"
>
<span
class=
"flex-items-center gap-4"
>
<span
class=
"ratio-icon wide"
></span>
16:9
</span>
<span
class=
"divider"
></span>
<span>
720p
</span>
<span
class=
"divider"
></span>
<span
class=
"flex-items-center gap-4"
>
<img
:src=
"styleImg"
alt=
"现代都市通用"
class=
"style-thumb wh-16 round-4 cover"
/>
现代都市通用
</span>
<span
class=
"divider"
></span>
<span>
目标语言:中文
</span>
</div>
</div>
</div>
<div
class=
"step-nav flex-1 flex-center"
>
<template
v-for=
"(item, index) in steps"
:key=
"item.stepn"
>
<div
class=
"step-item flex-col flex-center pointer"
:class=
"
{
active: item.stepn === selectStep,
disabled: item.stepn > currentStep,
}" @click="lookStep(item)">
<div
class=
"step-icon flex-center h-34 w-60 round-100"
>
<el-icon
class=
"fs-16"
>
<component
:is=
"item.icon"
/>
</el-icon>
<span
class=
"fs-12 lh-20 fw-700"
>
{{
item
.
name
}}
</span>
</div>
</div>
<div
v-if=
"index
<
steps
.
length
-
1
"
class=
"step-line w-28"
></div>
</
template
>
</div>
<div
class=
"point-box flex-items-center gap-12 h-34 px-14 round-8"
>
<span
class=
"flex-items-center gap-4 color-main fs-12 fw-700"
>
<el-icon>
<MagicStick
/>
</el-icon>
5,010
</span>
<span
class=
"flex-items-center gap-4 fs-12 color-666"
>
<el-icon>
<Clock
/>
</el-icon>
265小时13分9秒
</span>
<el-icon
class=
"fs-12 color-888"
>
<InfoFilled
/>
</el-icon>
</div>
</header>
<main
class=
"flex-1 over-y-auto hide-scroll"
>
<Step1
v-if=
"selectStep === 1"
@
next=
"goStep(2)"
/>
<Step2
v-else-if=
"selectStep === 2"
@
next=
"goStep(3)"
/>
<Step3
v-else-if=
"selectStep === 3"
/>
<Step4
v-else
/>
</main>
</div>
</template>
<
script
setup
lang=
"ts"
>
import
{
ref
}
from
'vue'
;
import
{
useRouter
}
from
'vue-router'
;
import
{
Back
,
Clock
,
Document
,
Edit
,
Files
,
InfoFilled
,
MagicStick
,
Setting
,
VideoCamera
}
from
'@element-plus/icons-vue'
;
import
Step1
from
'./components/Step-1.vue'
;
import
Step2
from
'./components/Step-2.vue'
;
import
Step3
from
'./components/Step-3.vue'
;
import
Step4
from
'./components/Step-4.vue'
;
import
styleImg
from
'@/static/image/meinv.png'
;
type
StepItem
=
{
name
:
string
;
icon
:
typeof
VideoCamera
;
stepn
:
number
;
};
const
router
=
useRouter
();
const
currentStep
=
ref
(
1
);
const
selectStep
=
ref
(
1
);
const
steps
:
StepItem
[]
=
[
{
name
:
'原片'
,
icon
:
VideoCamera
,
stepn
:
1
},
{
name
:
'设定'
,
icon
:
Setting
,
stepn
:
2
},
{
name
:
'分镜'
,
icon
:
Files
,
stepn
:
3
},
{
name
:
'视频'
,
icon
:
Document
,
stepn
:
4
},
];
const
goBack
=
()
=>
{
router
.
push
(
'/burst'
);
};
const
lookStep
=
(
item
:
StepItem
)
=>
{
if
(
currentStep
.
value
<
item
.
stepn
)
{
return
;
}
selectStep
.
value
=
item
.
stepn
;
};
const
goStep
=
(
step
:
number
)
=>
{
currentStep
.
value
=
Math
.
max
(
currentStep
.
value
,
step
);
selectStep
.
value
=
step
;
};
</
script
>
<
style
scoped
lang=
"scss"
>
.replica-header
{
flex
:
0
0
60px
;
border-bottom
:
1px
solid
#dce6f2
;
}
.divider
{
width
:
1px
;
height
:
12px
;
background
:
#dce6f2
;
}
.ratio-icon
{
display
:
inline-block
;
width
:
14px
;
height
:
8px
;
border
:
1px
solid
#536883
;
border-radius
:
2px
;
}
.step-item
{
color
:
#b8c1d2
;
&
.active
{
color
:
rgba
(
var
(
--
main-color
)
,
1
);
.step-icon
{
border-color
:
rgba
(
var
(
--
main-color
)
,
1
);
background
:
rgba
(
var
(
--
main-color
)
,
.06
);
}
}
&
.disabled
{
cursor
:
not
-
allowed
;
}
}
.step-icon
{
border
:
1px
solid
#edf2f7
;
background
:
#f8fafc
;
}
.step-line
{
height
:
1px
;
margin
:
0
8px
0px
;
background
:
#dce6f2
;
}
.point-box
{
justify-content
:
flex-end
;
background
:
#f8fafc
;
}
</
style
>
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