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
f44c8668
Commit
f44c8668
authored
Sep 03, 2026
by
黄同智
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
重新冲冲冲
parent
5d11284c
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
1096 additions
and
191 deletions
+1096
-191
Step-3 copy.vue
apps/web-vue/src/views/burst/components/Step-3 copy.vue
+493
-0
Step-3.vue
apps/web-vue/src/views/burst/components/Step-3.vue
+603
-191
No files found.
apps/web-vue/src/views/burst/components/Step-3 copy.vue
0 → 100644
View file @
f44c8668
<
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
60px
;
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-3.vue
View file @
f44c8668
...
...
@@ -13,14 +13,14 @@
</div>
</div>
<div
v-else
class=
"storyboard-workspace h-full flex
hidden
"
>
<div
v-else
class=
"storyboard-workspace h-full flex
gap-12 hidden pr-12
"
>
<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 hidd
en"
>
<div
class=
"flex-items-center gap-8 fs-16 fw-700 color-000 lh-24 mb-16
"
>
<div
class=
"flex-1 flex-col"
>
<div
class=
"toolbar flex-betwe
en"
>
<div
class=
"flex-items-center gap-8 fw-700 color-000
"
>
<el-icon
class=
"color-aaa"
>
<Document
/>
</el-icon>
...
...
@@ -30,168 +30,256 @@
</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">
<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>
</div>
<div
class=
"flex-1 flex pb-12 h-full hidden gap-12"
>
<div
class=
"flex-col round-10 bg-fff asset-sidebar"
>
<div
class=
"px-30 py-10"
>
<div
class=
"asset-filter flex-items-center h-34 p-4 round-8"
>
<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>
<div
class=
"flex-1 over-y-auto hide-scroll px-12"
>
<div
class=
"asset-scroll flex-1 over-y-auto hide-scroll"
>
<div
v-if=
"isFilterVisible('role')"
class=
"asset-section"
>
<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
v-if=
"currentShot.assets.roles.length"
class=
"asset-card-list flex flex-wrap gap-10 mb-18"
>
<div
v-for=
"item in currentShot.assets.roles"
:key=
"item.id"
class=
"role-card"
>
<img
:src=
"item.image"
:alt=
"item.name"
class=
"role-img w-118 h-118 round-8 cover"
/>
<div
class=
"fs-14 color-000 lh-22 mt-6"
>
{{
item
.
name
}}
</div>
<div
class=
"fs-12 color-536 lh-20"
>
{{
item
.
desc
}}
</div>
<el-button
class=
"w-118 h-32 mt-8 fs-12"
>
<el-icon>
<VideoPlay
/>
</el-icon>
自定义音色
</el-button>
</div>
</div>
<div
v-else
class=
"empty-card flex-col flex-center h-80 round-8 mb-18"
>
<el-icon
class=
"fs-26 color-ccc mb-8"
>
<User
/>
</el-icon>
<span
class=
"fs-12 color-aaa"
>
暂未选择角色
</span>
</div>
</div>
<div
v-if=
"isFilterVisible('scene')"
class=
"asset-section"
>
<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"
>
<div
v-if=
"currentShot.assets.scenes.length"
class=
"mb-18"
>
<div
v-for=
"item in currentShot.assets.scenes"
:key=
"item.id"
class=
"scene-card"
>
<img
:src=
"item.image"
:alt=
"item.name"
class=
"scene-img w-full h-128 round-8 cover"
/>
<div
class=
"fs-14 color-000 lh-22 mt-8"
>
{{
item
.
name
}}
</div>
<div
class=
"fs-12 color-536 lh-20"
>
{{
item
.
desc
}}
</div>
</div>
</div>
<div
v-else
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>
<div
v-if=
"isFilterVisible('prop')"
class=
"asset-section"
>
<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"
>
<div
v-if=
"currentShot.assets.props.length"
class=
"asset-card-list flex flex-wrap gap-10 mb-18"
>
<div
v-for=
"item in currentShot.assets.props"
:key=
"item.id"
class=
"role-card"
>
<img
:src=
"item.image"
:alt=
"item.name"
class=
"role-img w-118 h-118 round-8 cover"
/>
<div
class=
"fs-14 color-000 lh-22 mt-6"
>
{{
item
.
name
}}
</div>
<div
class=
"fs-12 color-536 lh-20"
>
{{
item
.
desc
}}
</div>
</div>
</div>
<div
v-else
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>
</div>
<div
class=
"story-editor flex-1 flex-col gap-12"
>
<div
class=
"flex-1 bg-fff round-12 p-10 flex gap-12"
>
<div
class=
"flex-col"
style=
"width: 60%;"
>
<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>
<el-icon
size=
"18"
>
<Film
/>
</el-icon>
<div
class=
"fw-700 color-000"
>
分镜
{{
currentShotIndex
+
1
}}
</div>
<span
class=
"bg-main px-4 fs-10 round-6 color-fff"
>
ID
</span>
<div
class=
"h-30 px-10 fs-12 bg-eee round-8 flex-items-center"
>
<el-icon
size=
"16"
>
<Aim
/>
</el-icon>
3D导演台
<span
class=
"beta fs-10 ml-4"
>
Beta
</span>
</el-button>
<span
class=
"ml-10"
>
3D导演台
</span>
<span
class=
"beta fs-10 ml-4 fw-600"
>
Beta
</span>
</div>
<div
class=
"flex-items-center gap-8 mb-8"
>
<el-button
class=
"h-30 px-12 fs-12"
>
</div>
<div
class=
"round-12 border-eee flex-1 flex-col"
>
<div
class=
"p-10 pt-16"
>
<div
class=
"mb-10"
>
<el-button
class=
"h-26 px-12 round-6 color-333"
size=
"small"
>
<el-icon>
<FolderOpened
/>
</el-icon>
项目素材库
<span
class=
"ml-4"
>
项目素材库
</span>
</el-button>
<el-button
class=
"h-30 px-12 fs-12
"
>
<el-button
class=
"h-26 px-12 round-6 color-333"
size=
"small
"
>
<el-icon>
<Folder
/>
</el-icon>
资产库
<span
class=
"ml-4"
>
资产库
</span>
</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
/>
<div
class=
"flex-items-center gap-10"
>
<UploadFile
:width=
"36"
/>
<div
class=
"h-36 w-36 round-6 bg-f5 flex-center"
style=
"border: 1px dashed #bbb;"
></div>
<span
class=
"fs-12 color-999"
>
使用 @ 引用角色、场景、道具、音色及参考素材,编辑更灵活,分镜更精准
</span>
</div>
</div>
<div
class=
"flex-1"
>
<el-input
v-model=
"currentShotPrompt"
type=
"textarea"
resize=
"none"
placeholder=
"请输入分镜视频提示词"
class=
"textarea"
style=
"height: 100%;"
/>
</div>
<div
class=
"h-60 flex-between px-10 py-8"
>
<div
class=
"flex-items-center gap-10"
>
<el-select
v-model=
"aiVal"
placeholder=
"Select"
style=
"width: 190px"
class=
"select"
>
<el-option
v-for=
"item in aiOptions"
:key=
"item.value"
:label=
"item.label"
:value=
"item.value"
/>
</el-select>
<el-popover
v-model:visible=
"specVisible"
placement=
"top-start"
:width=
"214"
trigger=
"click"
popper-class=
"burst-spec-popper"
:teleported=
"false"
:show-arrow=
"false"
:hide-after=
"0"
>
<template
#
reference
>
<div
class=
"spec-trigger flex-center gap-6 h-32 px-10 round-8 pointer"
:class=
"
{ active: specVisible }">
<el-icon
class=
"fs-14 color-536"
>
<Clock
/>
</el-icon>
</el-button>
<el-button
type=
"primary"
class=
"h-32 px-18 border-none"
>
<span
class=
"fs-12 color-000"
>
{{
specSummary
}}
</span>
</div>
</
template
>
<div
class=
"spec-panel bg-fff"
>
<div
class=
"spec-section"
>
<div
class=
"spec-label"
>
视频时长
</div>
<div
class=
"duration-row flex-items-center gap-10"
>
<el-slider
v-model=
"currentVideoDuration"
class=
"spec-slider flex-1"
:min=
"1"
:max=
"60"
:show-tooltip=
"false"
/>
<div
class=
"duration-value flex-center h-34 px-12 round-10"
>
{{ currentVideoDuration }}s
</div>
</div>
</div>
<div
class=
"spec-section"
>
<div
class=
"spec-label"
>
视频数量
</div>
<div
class=
"spec-segment grid-4"
>
<button
v-for=
"item in videoCountOptions"
:key=
"item"
type=
"button"
class=
"spec-option"
:class=
"{ active: currentVideoCount === item }"
@
click=
"currentVideoCount = item"
>
{{ item }}个
</button>
</div>
</div>
<div
class=
"spec-section"
>
<div
class=
"spec-label"
>
视频清晰度
</div>
<div
class=
"spec-segment grid-3"
>
<button
v-for=
"item in resolutionOptions"
:key=
"item"
type=
"button"
class=
"spec-option"
:class=
"{ active: currentResolution === item }"
@
click=
"currentResolution = item"
>
{{ item }}
</button>
</div>
</div>
<div
class=
"spec-section"
>
<div
class=
"spec-label"
>
视频格式
</div>
<div
class=
"spec-segment grid-2"
>
<button
v-for=
"item in formatOptions"
:key=
"item"
type=
"button"
class=
"spec-option"
:class=
"{ active: currentFormat === item }"
@
click=
"currentFormat = item"
>
{{ item }}
</button>
</div>
</div>
</div>
</el-popover>
<div
class=
"h-32 w-32 flex-center border-eee round-8 pointer"
style=
"border: 1px solid #dce6f2;"
>
@
</div>
</div>
<el-button
type=
"primary"
class=
"h-30 px-18 round-8 border-none"
>
<el-icon>
<MagicStick
/>
</el-icon>
6,129
{{ currentShot.points.toLocaleString() }}
</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"
>
<div
class=
"preview-panel round-12 flex-col"
style=
"width: 40%;"
>
<div
class=
"preview-tabs flex-center 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"
>
<div
v-if=
"previewMode === 'generated' && currentShot.status === 'pending'"
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"
/>
<img
:src=
"currentShot.cover || peopleImg"
:alt=
"`分镜${currentShotIndex + 1}`"
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
/>
...
...
@@ -199,29 +287,55 @@
</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"
>
</div>
<footer
class=
"shot-strip bg-fff round-12 px-14 py-14"
>
<div
class=
"color-000 lh-24 mb-10"
>
{{ shotList.length }} 分镜
</div>
<div
class=
"shot-list flex-items-center over-x-auto hide-scroll"
>
<
template
v-for=
"(shot, index) in shotList"
:key=
"shot.id"
>
<div
class=
"shot-card flex-col items-center pointer"
:class=
"
{ active: activeShotId === shot.id }" @click="selectShot(shot.id)">
<div
class=
"shot-thumb relative flex-center w-130 h-90 round-6 hidden"
>
<img
v-if=
"shot.cover"
:src=
"shot.cover"
:alt=
"`分镜$
{index + 1}`"
class="w-full h-full cover" />
<el-icon
v-else
class=
"fs-28 color-ccc"
>
<VideoCamera
/>
</el-icon>
<button
v-if=
"shotList.length > 1"
type=
"button"
class=
"shot-delete absolute flex-center wh-24 round-6 pointer"
@
click
.
stop=
"deleteShot(shot.id)"
>
<el-icon>
<Delete
/>
</el-icon>
</button>
</div>
<div
class=
"fs-12 color-536 lh-22 mt-6"
>
分镜
{{
padShotNo
(
index
+
1
)
}}
·
{{
shot
.
status
===
'generated'
?
`${shot.duration
}
s`
:
'待生成'
}}
<
/div
>
<
/div
>
<div
class=
"fs-12 color-536 lh-22 mt-6"
>
分镜 01 · 待生成
</div>
<
el
-
tooltip
v
-
if
=
"index < shotList.length - 1"
content
=
"新增分镜"
placement
=
"top"
:
show
-
after
=
"120"
>
<
button
type
=
"button"
class
=
"shot-insert flex-center pointer"
@
click
.
stop
=
"insertShotAfter(index)"
>
<
span
class
=
"insert-line"
><
/span
>
<
span
class
=
"insert-dot flex-center"
>+<
/span
>
<
/button
>
<
/el-tooltip
>
<
/template
>
<
/div
>
<
/footer
>
</main>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/section
>
<
/template
>
<
script
setup
lang
=
"ts"
>
import
{
onBeforeUnmount
,
onMounted
,
ref
}
from
'vue'
;
import
{
computed
,
onBeforeUnmount
,
onMounted
,
ref
}
from
'vue'
;
import
{
Aim
,
C
opyDocument
,
C
lock
,
Document
,
Folder
,
FolderOpened
,
...
...
@@ -230,16 +344,47 @@ import {
Notebook
,
Picture
,
Plus
,
Pointer
,
Refresh
,
Star
,
User
,
Delete
,
VideoCamera
,
VideoPlay
,
Film
,
}
from
'@element-plus/icons-vue'
;
import
peopleImg
from
'@/static/image/meinv.png'
;
import
UploadFile
from
'@/components/Upload/index.vue'
;
type
FilterValue
=
'all'
|
'role'
|
'scene'
|
'prop'
;
type
AssetFilterValue
=
Exclude
<
FilterValue
,
'all'
>
;
type
PreviewMode
=
'generated'
|
'origin'
;
type
ResolutionValue
=
'480p'
|
'720p'
|
'1080p'
;
type
FormatValue
=
'mp4'
|
'mov'
;
type
ShotStatus
=
'generated'
|
'pending'
;
type
ShotAsset
=
{
id
:
string
;
name
:
string
;
desc
:
string
;
image
:
string
;
}
;
type
ShotItem
=
{
id
:
string
;
status
:
ShotStatus
;
duration
:
number
;
count
:
number
;
resolution
:
ResolutionValue
;
format
:
FormatValue
;
points
:
number
;
prompt
:
string
;
cover
?:
string
;
assets
:
{
roles
:
ShotAsset
[];
scenes
:
ShotAsset
[];
props
:
ShotAsset
[];
}
;
}
;
const
filters
:
{
label
:
string
;
value
:
FilterValue
}
[]
=
[
{
label
:
'全部'
,
value
:
'all'
}
,
...
...
@@ -247,87 +392,148 @@ const filters: { label: string; value: FilterValue }[] = [
{
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
:
'它的面料很亲肤。'
,
const
aiVal
=
ref
(
'1'
)
const
aiOptions
:
any
=
[
{
label
:
'Doubao-Seedance-2.5'
,
value
:
'1'
}
,
{
label
:
'Doubao-Seedance-2-0'
,
value
:
'2'
}
,
];
const
filledShotPrompt
=
`画风: 写实风格, 细节刻画逼真,参考院线电影,真人电影风格,影视大片,真实透视比例,细节清晰不过度锐化
分镜场景设定在:现代白天·轻奢简约风·居家客厅,室内,白天。
分镜具体动作描述:
整体视觉基调:画面明亮,色彩饱和,质感细腻,景深较浅以突出人物和产品。
镜头1(0-2秒):【现代白天·轻奢简约风·居家客厅,室内,白天】
【机位】机位与美妆博主胸部齐平,平拍朝向美妆博主,固定不动,美妆博主坐在客厅沙发上。
【动作】中近景,美妆博主面部神态鲜活自然,眉眼灵动,带有自然小幅度肢体微晃,右手持产品,左手对准产品瓶盖用手指将产品瓶盖弹飞,随后左手拿出紫色手套涂抹工具,嘴角上扬,眼神明亮自信。`
;
let
nextShotId
=
7
;
const
createPendingShot
=
(
id
=
`shot-${nextShotId++
}
`
):
ShotItem
=>
({
id
,
status
:
'pending'
,
duration
:
5
,
count
:
1
,
resolution
:
'720p'
,
format
:
'mov'
,
points
:
1135
,
prompt
:
''
,
assets
:
{
roles
:
[],
scenes
:
[],
props
:
[],
}
,
}
);
const
shotList
=
ref
<
ShotItem
[]
>
([
{
id
:
8
,
duration
:
'4.0'
,
camera
:
'机位在苏晓正前方,平拍,固定不动。'
,
position
:
'位于镜头前,身体被丝袜遮挡,仅手臂可见。'
,
action
:
'双手分别抓住黑色丝袜的腰部两端,用力向两侧反复拉伸。'
,
speech
:
'丝袜细腻,弹性也很大,各种身材的姐妹都能穿。'
,
id
:
'shot-1'
,
status
:
'generated'
,
duration
:
15
,
count
:
1
,
resolution
:
'720p'
,
format
:
'mp4'
,
points
:
3405
,
prompt
:
filledShotPrompt
,
cover
:
peopleImg
,
assets
:
{
roles
:
[
{
id
:
'role-1'
,
name
:
'美妆博主'
,
desc
:
'主形象'
,
image
:
peopleImg
}
,
{
id
:
'role-2'
,
name
:
'路人甲'
,
desc
:
'变装1'
,
image
:
peopleImg
}
,
{
id
:
'role-3'
,
name
:
'路人乙'
,
desc
:
'变装2'
,
image
:
peopleImg
}
,
],
scenes
:
[
{
id
:
'scene-1'
,
name
:
'Modern Living Room Streaming Area'
,
desc
:
'主形象'
,
image
:
peopleImg
}
,
],
props
:
[],
}
,
{
id
:
9
,
duration
:
'1.8'
,
camera
:
'机位在苏晓右前方低位,略微仰拍她的腿部,固定不动。'
,
position
:
'穿着丝袜和高跟鞋,侧身坐在一个黑色吧台凳上,双腿交叠。'
,
action
:
'镜头停留在腿部线条,展示自然的光泽。'
,
speech
:
'显瘦又高级。'
,
}
,
];
createPendingShot
(
'shot-2'
),
createPendingShot
(
'shot-3'
),
createPendingShot
(
'shot-4'
),
createPendingShot
(
'shot-5'
),
createPendingShot
(
'shot-6'
),
]);
const
isReady
=
ref
(
false
);
const
planningProgress
=
ref
(
0
);
const
activeFilter
=
ref
<
FilterValue
>
(
'all'
);
const
previewMode
=
ref
<
PreviewMode
>
(
'generated'
);
const
specVisible
=
ref
(
false
);
const
activeShotId
=
ref
(
shotList
.
value
[
0
].
id
);
const
videoCountOptions
=
[
1
,
2
,
3
,
4
];
const
resolutionOptions
:
ResolutionValue
[]
=
[
'480p'
,
'720p'
,
'1080p'
];
const
formatOptions
:
FormatValue
[]
=
[
'mp4'
,
'mov'
];
const
currentShotIndex
=
computed
(()
=>
Math
.
max
(
0
,
shotList
.
value
.
findIndex
((
item
)
=>
item
.
id
===
activeShotId
.
value
)));
const
currentShot
=
computed
(()
=>
shotList
.
value
[
currentShotIndex
.
value
]
||
shotList
.
value
[
0
]);
const
currentShotPrompt
=
computed
({
get
:
()
=>
currentShot
.
value
.
prompt
,
set
:
(
value
:
string
)
=>
{
currentShot
.
value
.
prompt
=
value
;
}
,
}
);
const
currentVideoDuration
=
computed
({
get
:
()
=>
currentShot
.
value
.
duration
,
set
:
(
value
:
number
)
=>
{
currentShot
.
value
.
duration
=
value
;
}
,
}
);
const
currentVideoCount
=
computed
({
get
:
()
=>
currentShot
.
value
.
count
,
set
:
(
value
:
number
)
=>
{
currentShot
.
value
.
count
=
value
;
}
,
}
);
const
currentResolution
=
computed
({
get
:
()
=>
currentShot
.
value
.
resolution
,
set
:
(
value
:
ResolutionValue
)
=>
{
currentShot
.
value
.
resolution
=
value
;
}
,
}
);
const
currentFormat
=
computed
({
get
:
()
=>
currentShot
.
value
.
format
,
set
:
(
value
:
FormatValue
)
=>
{
currentShot
.
value
.
format
=
value
;
}
,
}
);
const
specSummary
=
computed
(()
=>
`${currentShot.value.duration
}
s | ${currentShot.value.resolution
}
| ${currentShot.value.count
}
个 | ${currentShot.value.format
}
`
);
let
timer
:
number
|
undefined
;
const
isFilterVisible
=
(
value
:
AssetFilterValue
)
=>
{
return
activeFilter
.
value
===
'all'
||
activeFilter
.
value
===
value
;
}
;
const
padShotNo
=
(
value
:
number
)
=>
String
(
value
).
padStart
(
2
,
'0'
);
const
selectShot
=
(
id
:
string
)
=>
{
activeShotId
.
value
=
id
;
specVisible
.
value
=
false
;
}
;
const
insertShotAfter
=
(
index
:
number
)
=>
{
const
shot
=
createPendingShot
();
shotList
.
value
.
splice
(
index
+
1
,
0
,
shot
);
selectShot
(
shot
.
id
);
}
;
const
deleteShot
=
(
id
:
string
)
=>
{
if
(
shotList
.
value
.
length
<=
1
)
{
return
;
}
const
index
=
shotList
.
value
.
findIndex
((
item
)
=>
item
.
id
===
id
);
if
(
index
===
-
1
)
{
return
;
}
shotList
.
value
.
splice
(
index
,
1
);
if
(
activeShotId
.
value
===
id
)
{
const
nextIndex
=
Math
.
min
(
index
,
shotList
.
value
.
length
-
1
);
selectShot
(
shotList
.
value
[
nextIndex
].
id
);
}
}
;
const
runPlanning
=
()
=>
{
timer
=
window
.
setInterval
(()
=>
{
planningProgress
.
value
=
Math
.
min
(
100
,
planningProgress
.
value
+
20
);
...
...
@@ -355,7 +561,7 @@ onBeforeUnmount(() => {
<
style
scoped
lang
=
"scss"
>
.
step
-
three
{
background
:
#f8f
afc
;
background
:
#
f8f
9fa
;
}
.
planning
-
panel
{
...
...
@@ -371,8 +577,8 @@ onBeforeUnmount(() => {
}
.
episode
-
rail
{
flex
:
0
0
48
px
;
background
:
#
eef3ff
;
flex
:
0
0
60
px
;
background
:
#
f3f4fa
;
}
.
episode
-
no
{
...
...
@@ -380,7 +586,7 @@ onBeforeUnmount(() => {
}
.
asset
-
sidebar
{
flex
:
0
0
2
82
px
;
flex
:
0
0
2
70
px
;
min
-
height
:
0
;
}
...
...
@@ -400,25 +606,31 @@ onBeforeUnmount(() => {
}
.
role
-
img
,
.
scene
-
img
,
.
empty
-
card
,
.
preview
-
empty
,
.
shot
-
thumb
{
background
:
#
f3f5f9
;
}
.
role
-
card
{
width
:
118
px
;
}
.
scene
-
card
{
width
:
100
%
;
}
.
story
-
content
{
min
-
height
:
0
;
}
.toolbar
{
flex
:
0
0
54px
;
border-bottom
:
1px
solid
#dce6f2
;
.
story
-
editor
{
min
-
width
:
0
;
}
.script-panel
,
.preview-panel
,
.shot-strip
{
border
:
1px
solid
#e2e8f3
;
.
toolbar
{
flex
:
0
0
54
px
;
}
.
script
-
scroll
{
...
...
@@ -472,14 +684,91 @@ onBeforeUnmount(() => {
.
shot
-
strip
{
flex
:
0
0
164
px
;
min
-
width
:
0
;
overflow
:
hidden
;
border
:
1
px
solid
#
edf2f7
;
}
.
shot
-
list
{
width
:
100
%
;
min
-
width
:
0
;
overflow
-
y
:
hidden
;
gap
:
2
px
;
}
.
shot
-
card
{
width
:
150px
;
flex
:
0
0
130
px
;
&
.
active
{
.
shot
-
thumb
{
border
-
color
:
#
4
c45ff
;
}
}
}
.
shot
-
thumb
{
border
:
2px
solid
rgba
(
var
(
--
main-color
)
,
1
);
border
:
2
px
solid
transparent
;
transition
:
border
-
color
.
18
s
ease
;
}
.
shot
-
delete
{
top
:
6
px
;
right
:
6
px
;
border
:
0
;
color
:
#
fff
;
background
:
rgba
(
85
,
92
,
106
,
.
72
);
opacity
:
0
;
transition
:
opacity
.
18
s
ease
;
}
.
shot
-
card
:
hover
{
.
shot
-
delete
{
opacity
:
1
;
}
}
.
shot
-
insert
{
position
:
relative
;
flex
:
0
0
16
px
;
align
-
self
:
flex
-
start
;
width
:
16
px
;
height
:
90
px
;
margin
-
top
:
0
;
border
:
0
;
background
:
transparent
;
.
insert
-
line
{
width
:
2
px
;
height
:
90
px
;
border
-
radius
:
2
px
;
background
:
transparent
;
}
.
insert
-
dot
{
position
:
absolute
;
top
:
39
px
;
left
:
50
%
;
width
:
18
px
;
height
:
18
px
;
border
-
radius
:
50
%
;
color
:
#
fff
;
background
:
#
4
c45ff
;
font
-
size
:
14
px
;
line
-
height
:
18
px
;
opacity
:
0
;
transform
:
translateX
(
-
50
%
);
transition
:
opacity
.
18
s
ease
;
}
&
:
hover
{
.
insert
-
line
{
background
:
#
4
c45ff
;
}
.
insert
-
dot
{
opacity
:
1
;
}
}
}
:
deep
(.
planning
-
card
.
el
-
progress
-
bar__outer
)
{
...
...
@@ -489,4 +778,127 @@ onBeforeUnmount(() => {
:
deep
(.
planning
-
card
.
el
-
progress
-
bar__inner
)
{
background
:
rgba
(
var
(
--
main
-
color
),
1
);
}
.
textarea
{
:
deep
(.
el
-
textarea__inner
)
{
height
:
100
%
;
box
-
shadow
:
none
;
}
}
.
select
{
:
deep
(.
el
-
select__wrapper
)
{
border
-
radius
:
8
px
;
}
}
:
deep
(.
burst
-
spec
-
popper
)
{
padding
:
0
!
important
;
border
:
0
!
important
;
border
-
radius
:
14
px
!
important
;
box
-
shadow
:
0
12
px
34
px
rgba
(
19
,
35
,
64
,
.
14
)
!
important
;
}
.
spec
-
trigger
{
min
-
width
:
168
px
;
border
:
1
px
solid
#
dce6f2
;
background
:
#
fff
;
white
-
space
:
nowrap
;
&
.
active
,
&
:
hover
{
border
-
color
:
#
cdd7e6
;
background
:
#
f8fafc
;
}
}
.
spec
-
panel
{
padding
:
14
px
12
px
10
px
;
}
.
spec
-
section
+
.
spec
-
section
{
margin
-
top
:
12
px
;
}
.
spec
-
label
{
margin
-
bottom
:
8
px
;
color
:
#
8
b97aa
;
font
-
size
:
12
px
;
line
-
height
:
18
px
;
}
.
duration
-
row
{
height
:
34
px
;
}
.
duration
-
value
{
min
-
width
:
44
px
;
border
:
1
px
solid
#
dbe4f0
;
background
:
#
fff
;
color
:
#
111827
;
font
-
size
:
12
px
;
font
-
weight
:
700
;
box
-
shadow
:
0
4
px
12
px
rgba
(
37
,
56
,
88
,
.
08
);
}
.
spec
-
slider
{
:
deep
(.
el
-
slider__runway
)
{
height
:
2
px
;
margin
:
16
px
0
;
background
:
#
121212
;
}
:
deep
(.
el
-
slider__bar
)
{
height
:
2
px
;
background
:
#
121212
;
}
:
deep
(.
el
-
slider__button
)
{
width
:
13
px
;
height
:
13
px
;
border
:
1
px
solid
#
d7dfeb
;
background
:
#
fff
;
box
-
shadow
:
0
2
px
7
px
rgba
(
25
,
43
,
72
,
.
2
);
}
}
.
spec
-
segment
{
display
:
grid
;
height
:
34
px
;
padding
:
3
px
;
border
-
radius
:
10
px
;
background
:
#
f1f3f8
;
gap
:
2
px
;
}
.
grid
-
2
{
grid
-
template
-
columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
}
.
grid
-
3
{
grid
-
template
-
columns
:
repeat
(
3
,
minmax
(
0
,
1
fr
));
}
.
grid
-
4
{
grid
-
template
-
columns
:
repeat
(
4
,
minmax
(
0
,
1
fr
));
}
.
spec
-
option
{
min
-
width
:
0
;
border
:
0
;
border
-
radius
:
9
px
;
background
:
transparent
;
color
:
#
68758
a
;
font
-
size
:
12
px
;
font
-
weight
:
600
;
line
-
height
:
28
px
;
cursor
:
pointer
;
&
.
active
{
background
:
#
fff
;
color
:
#
111827
;
font
-
weight
:
800
;
box
-
shadow
:
0
3
px
10
px
rgba
(
31
,
46
,
76
,
.
1
);
}
}
<
/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