mirror of
https://github.com/yangjian102621/geekai.git
synced 2026-05-09 19:25:20 +08:00
视频页面整合完成
This commit is contained in:
@@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
## v4.2.5
|
## v4.2.5
|
||||||
|
|
||||||
- 功能优化:在代码右下角增加复制代码功能按钮,增加收起和展开代码功能。
|
- 功能优化:在代码右下角增加复制代码功能按钮,增加收起和展开代码功能
|
||||||
- Bug 修复:修复 Shift + Enter 不换行的 Bug
|
- Bug 修复:修复 Shift + Enter 不换行的 Bug
|
||||||
- Bug 修复:修复管理后台菜单添加页面的文本错误
|
- Bug 修复:修复管理后台菜单添加页面的文本错误
|
||||||
|
- Bug 修复:解决聊天页面异常退出不断重连的 bug
|
||||||
|
- 功能优化:把 Luma 和可灵视频生成页面整合成一个视频创作中心页面,统一管理视频任务
|
||||||
|
- 功能新增:增加即梦 AI 专题页面,支持即梦官方原生 API 的图片和视频生成 🎉🎉🎉
|
||||||
|
|
||||||
## v4.2.4
|
## v4.2.4
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,18 @@ html, body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-popper.is-customized {
|
||||||
|
/* 设置内边距以保证高度为32px */
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: linear-gradient(180deg, #e1bee7, #7e57c2);
|
||||||
|
color #fff
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-popper.is-customized .el-popper__arrow::before {
|
||||||
|
background: linear-gradient(180deg, #b39ddb, #7e57c2);
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 省略显示 */
|
/* 省略显示 */
|
||||||
.ellipsis {
|
.ellipsis {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -1,363 +0,0 @@
|
|||||||
.page-keling
|
|
||||||
display flex
|
|
||||||
min-height 100vh
|
|
||||||
:deep(.el-form-item__label)
|
|
||||||
color var(--text-theme-color)
|
|
||||||
.grid-content
|
|
||||||
// background-color #383838
|
|
||||||
background var(--card-bg)
|
|
||||||
border-radius 8px
|
|
||||||
padding 8px 14px
|
|
||||||
display flex
|
|
||||||
cursor pointer
|
|
||||||
margin-bottom 10px
|
|
||||||
// border 1px solid #383838
|
|
||||||
border 1px solid var(--chat-bg)
|
|
||||||
&:hover
|
|
||||||
border 1px solid var(--theme-border-hover)
|
|
||||||
.icon
|
|
||||||
width 20px
|
|
||||||
height 20px
|
|
||||||
margin-bottom 5px
|
|
||||||
.texts
|
|
||||||
margin-left 5px
|
|
||||||
margin-top 2px
|
|
||||||
color var(--text-theme-color)
|
|
||||||
.param-line.pt
|
|
||||||
padding-top 5px
|
|
||||||
padding-bottom 5px
|
|
||||||
.grid-content.active
|
|
||||||
// color #47fff1
|
|
||||||
// background-color #585858
|
|
||||||
border 1px solid var(--theme-border-hover)
|
|
||||||
.h-20
|
|
||||||
height 4rem !important
|
|
||||||
.main-content
|
|
||||||
padding-right 1.5rem
|
|
||||||
padding-left 1.5rem
|
|
||||||
padding-bottom 1rem
|
|
||||||
flex 1
|
|
||||||
background var(--chat-bg)
|
|
||||||
// width: 100%;
|
|
||||||
// padding 0 10px 10px 10px
|
|
||||||
color var(--text-theme-color)
|
|
||||||
overflow-x hidden
|
|
||||||
.camera-control
|
|
||||||
padding 10px
|
|
||||||
border-radius 4px
|
|
||||||
background var(--card-bg)
|
|
||||||
:deep(.el-form-item:last-child)
|
|
||||||
margin-bottom 0 !important
|
|
||||||
.title-tabs
|
|
||||||
:deep(.el-tabs__item.is-active)
|
|
||||||
color var(--theme-textcolor-normal)
|
|
||||||
font-size 18px
|
|
||||||
:deep(.el-tabs__item)
|
|
||||||
color var(--text-theme-color)
|
|
||||||
font-size 18px
|
|
||||||
.el-tabs
|
|
||||||
--el-tabs-header-height 55px
|
|
||||||
.el-tabs__item
|
|
||||||
color var(--text-theme-color)
|
|
||||||
font-size 18px
|
|
||||||
.el-tabs__item.is-active, .title-tabs .el-tabs__item.is-active
|
|
||||||
.title-tabs .el-tabs__active-bar
|
|
||||||
background-color var(--theme-textcolor-normal)
|
|
||||||
:deep(.el-textarea)
|
|
||||||
--el-input-focus-border-color var(--el-color-primary)
|
|
||||||
:deep(.el-textarea__inner)
|
|
||||||
background transparent
|
|
||||||
color var(--text-theme-color)
|
|
||||||
.el-input__wrapper
|
|
||||||
background transparent
|
|
||||||
padding 5px
|
|
||||||
.text
|
|
||||||
margin-bottom 10px
|
|
||||||
color #6b778c
|
|
||||||
font-size 15px
|
|
||||||
.param-line.pt
|
|
||||||
padding-top 5px
|
|
||||||
padding-bottom 5px
|
|
||||||
.form-item-inner
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
.el-icon
|
|
||||||
margin-left 10px
|
|
||||||
.el-form-item__label
|
|
||||||
color var(--text-theme-color)
|
|
||||||
// 图片上传样式
|
|
||||||
.img-inline
|
|
||||||
display flex
|
|
||||||
gap 20px
|
|
||||||
align-items center
|
|
||||||
.img-uploader
|
|
||||||
text-align center
|
|
||||||
:deep(.el-upload)
|
|
||||||
border 1px dashed var(--el-border-color)
|
|
||||||
border-radius 6px
|
|
||||||
cursor pointer
|
|
||||||
position relative
|
|
||||||
overflow hidden
|
|
||||||
width 120px
|
|
||||||
height 120px
|
|
||||||
line-height 120px
|
|
||||||
transition var(--el-transition-duration-fast)
|
|
||||||
margin-bottom 20px
|
|
||||||
&:hover
|
|
||||||
border-color var(--el-color-primary)
|
|
||||||
.el-icon.uploader-icon
|
|
||||||
font-size 28px
|
|
||||||
color #8c939d
|
|
||||||
width 100%
|
|
||||||
height 120px
|
|
||||||
text-align center
|
|
||||||
.img-list-box
|
|
||||||
display flex
|
|
||||||
.img-item
|
|
||||||
width 120px
|
|
||||||
position relative
|
|
||||||
margin-right 10px
|
|
||||||
.el-image
|
|
||||||
width 120px
|
|
||||||
height 120px
|
|
||||||
border-radius 5px
|
|
||||||
.el-button
|
|
||||||
position absolute
|
|
||||||
right 5px
|
|
||||||
top 5px
|
|
||||||
width 20px
|
|
||||||
height 20px
|
|
||||||
.el-row.text-info
|
|
||||||
width 100%
|
|
||||||
padding 10px 0
|
|
||||||
.el-tag
|
|
||||||
margin-right 10px
|
|
||||||
// 提交按钮
|
|
||||||
.submit-btn
|
|
||||||
display flex
|
|
||||||
margin 20px 0
|
|
||||||
.el-button
|
|
||||||
width 200px
|
|
||||||
.video-list
|
|
||||||
.btn
|
|
||||||
margin-right 10px
|
|
||||||
border none
|
|
||||||
border-radius 5px
|
|
||||||
padding 5px 10px
|
|
||||||
cursor pointer
|
|
||||||
color var(--theme-text-color-primary)
|
|
||||||
background-color var(--btn-bg)
|
|
||||||
&:hover
|
|
||||||
opacity 0.7
|
|
||||||
.list-box
|
|
||||||
padding 0
|
|
||||||
.item
|
|
||||||
display flex
|
|
||||||
flex-flow row
|
|
||||||
align-items center
|
|
||||||
min-height 100px
|
|
||||||
padding 10px 15px
|
|
||||||
border-radius 10px
|
|
||||||
cursor pointer
|
|
||||||
margin-bottom 20px
|
|
||||||
background var(--chat-bg)
|
|
||||||
.left
|
|
||||||
.container
|
|
||||||
width 160px
|
|
||||||
position relative
|
|
||||||
max-height 120px
|
|
||||||
overflow hidden
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
align-items center
|
|
||||||
.video
|
|
||||||
width 160px
|
|
||||||
border-radius 5px
|
|
||||||
.el-image
|
|
||||||
width 160px
|
|
||||||
height 90px
|
|
||||||
border-radius 5px
|
|
||||||
.duration
|
|
||||||
position absolute
|
|
||||||
bottom 0
|
|
||||||
right 0
|
|
||||||
background-color rgba(14, 8, 8, 0.7)
|
|
||||||
padding 0 3px
|
|
||||||
font-family 'Input Sans'
|
|
||||||
font-size 14px
|
|
||||||
font-weight 700
|
|
||||||
border-radius 0.125rem
|
|
||||||
.play
|
|
||||||
position absolute
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
top 0
|
|
||||||
left 50%
|
|
||||||
border none
|
|
||||||
border-radius 5px
|
|
||||||
background rgba(100, 100, 100, 0.3)
|
|
||||||
cursor pointer
|
|
||||||
color var(--text-theme-color)
|
|
||||||
opacity 0
|
|
||||||
transform translate(-50%, 0px)
|
|
||||||
transition opacity 0.3s ease 0s
|
|
||||||
&:hover
|
|
||||||
.play
|
|
||||||
opacity 1
|
|
||||||
// display block
|
|
||||||
.center
|
|
||||||
width 100%
|
|
||||||
// border 1px solid saddlebrown
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
align-items flex-start
|
|
||||||
flex-flow column
|
|
||||||
padding 0 20px
|
|
||||||
.prompt, .failed
|
|
||||||
padding 0
|
|
||||||
font-size 16px
|
|
||||||
max-height 60px
|
|
||||||
line-height 28px
|
|
||||||
overflow hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
.prompt
|
|
||||||
color var(--text-fb)
|
|
||||||
cursor text
|
|
||||||
.failed
|
|
||||||
color #E4696B
|
|
||||||
.right
|
|
||||||
display flex
|
|
||||||
justify-content right
|
|
||||||
min-width 200px
|
|
||||||
font-size 14px
|
|
||||||
padding 0
|
|
||||||
.tools
|
|
||||||
display flex
|
|
||||||
justify-content left
|
|
||||||
align-items center
|
|
||||||
flex-flow row
|
|
||||||
height 90px
|
|
||||||
.btn-publish
|
|
||||||
padding 2px 10px
|
|
||||||
.text
|
|
||||||
margin-right 10px
|
|
||||||
.btn-icon
|
|
||||||
background none
|
|
||||||
padding 6px
|
|
||||||
transition background 0.6s ease 0s
|
|
||||||
color #919191
|
|
||||||
&:hover
|
|
||||||
// background #5f5958
|
|
||||||
// color #e1e1e1
|
|
||||||
color var(--el-color-primary)
|
|
||||||
.downloading
|
|
||||||
width 16px
|
|
||||||
.pagination
|
|
||||||
margin-top 20px
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
.inner
|
|
||||||
display flex
|
|
||||||
width 100%
|
|
||||||
.mj-box
|
|
||||||
margin 10px
|
|
||||||
// background-color #262626
|
|
||||||
// border 1px solid #454545
|
|
||||||
// height: calc(100vh - 50px)
|
|
||||||
// overflow: scroll
|
|
||||||
min-width 300px
|
|
||||||
max-width 300px
|
|
||||||
padding 20px
|
|
||||||
border-radius 10px
|
|
||||||
color var(--text-theme-color)
|
|
||||||
font-size 14px
|
|
||||||
overflow auto
|
|
||||||
h2
|
|
||||||
font-weight bold
|
|
||||||
font-size 20px
|
|
||||||
text-align center
|
|
||||||
color var(--theme-textcolor-normal)
|
|
||||||
// 隐藏滚动条
|
|
||||||
::-webkit-scrollbar
|
|
||||||
width 0
|
|
||||||
height 0
|
|
||||||
background-color transparent
|
|
||||||
.mj-params
|
|
||||||
margin-top 10px
|
|
||||||
overflow auto
|
|
||||||
.param-line
|
|
||||||
padding 0 10px
|
|
||||||
.el-icon
|
|
||||||
position relative
|
|
||||||
.model
|
|
||||||
background var(--card-bg)
|
|
||||||
// border 1px solid #454545
|
|
||||||
border-radius 8px
|
|
||||||
padding 5px
|
|
||||||
margin-bottom 10px
|
|
||||||
display flex
|
|
||||||
flex-flow column
|
|
||||||
align-items center
|
|
||||||
cursor pointer
|
|
||||||
border 1px solid var(--chat-bg)
|
|
||||||
&:hover
|
|
||||||
border 1px solid var(--theme-border-hover)
|
|
||||||
.el-image
|
|
||||||
height 40px
|
|
||||||
width 100%
|
|
||||||
.text
|
|
||||||
margin-top 4px
|
|
||||||
font-size 12px
|
|
||||||
.model.active
|
|
||||||
// color #47fff1
|
|
||||||
// background-color #585858
|
|
||||||
border 1px solid var(--theme-border-hover)
|
|
||||||
.form-item-inner
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
.el-select
|
|
||||||
--el-select-input-focus-border-color var(--el-color-primary)
|
|
||||||
--el-input-focus-border-color var(--el-color-primary)
|
|
||||||
.el-input__wrapper
|
|
||||||
background var(--chat-bg)
|
|
||||||
.el-input__inner
|
|
||||||
color var(--text-theme-color)
|
|
||||||
.el-icon
|
|
||||||
margin-left 10px
|
|
||||||
.img-uploader
|
|
||||||
.el-upload
|
|
||||||
border 1px dashed var(--el-border-color)
|
|
||||||
border-radius 6px
|
|
||||||
cursor pointer
|
|
||||||
position relative
|
|
||||||
overflow hidden
|
|
||||||
width 100%
|
|
||||||
transition var(--el-transition-duration-fast)
|
|
||||||
&:hover
|
|
||||||
border-color var(--el-color-primary)
|
|
||||||
.el-icon.uploader-icon
|
|
||||||
font-size 28px
|
|
||||||
color #8c939d
|
|
||||||
width 100%
|
|
||||||
height 120px
|
|
||||||
text-align center
|
|
||||||
.param-line.pt
|
|
||||||
display flex
|
|
||||||
align-items center
|
|
||||||
padding-top 5px
|
|
||||||
padding-bottom 5px
|
|
||||||
.el-form
|
|
||||||
.el-form-item__label
|
|
||||||
color var(--text-theme-color)
|
|
||||||
.el-input, .el-slider
|
|
||||||
width 180px
|
|
||||||
.uploader-icon
|
|
||||||
font-size 24px
|
|
||||||
position relative
|
|
||||||
top 3px
|
|
||||||
.no-more-data
|
|
||||||
text-align center
|
|
||||||
padding 30px
|
|
||||||
.generate-btn
|
|
||||||
.iconfont
|
|
||||||
margin-right 5px
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
.page-luma {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #0e0808;
|
|
||||||
overflow: auto;
|
|
||||||
flex-flow: column;
|
|
||||||
align-items: center;
|
|
||||||
background: linear-gradient(180deg, rgba(75,62,53,0.8), rgba(144,50,181,0.3));
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box {
|
|
||||||
display: flex;
|
|
||||||
max-width: 56rem;
|
|
||||||
width: 100%;
|
|
||||||
padding: 20px;
|
|
||||||
flex-flow: column;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .images {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .images .item {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .images .item .el-image {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .images .item .el-icon {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 20px;
|
|
||||||
color: #545454;
|
|
||||||
right: 10px;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .images .item .el-icon:hover {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .prompt-container {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container {
|
|
||||||
background: linear-gradient(90deg, rgba(75,62,53,0.8), rgba(144,50,181,0.3));
|
|
||||||
border-radius: 28px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container .prompt-input {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
resize: none;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
line-height: 24px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
scrollbar-width: none; /* 隐藏滚动条 */
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container .prompt-input::placeholder {
|
|
||||||
color: rgba(255,255,255,0.6);
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container .prompt-input::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container .upload-icon,
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container .send-icon {
|
|
||||||
color: #e1e1e1;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container .upload-icon .iconfont,
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container .send-icon .iconfont {
|
|
||||||
font-size: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.page-luma .prompt-box .prompt-container .input-container .upload-icon {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.page-luma .video-container {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 40px;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .h-title {
|
|
||||||
color: #fff;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 36px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .videos .item {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .videos .item .video-box {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .videos .item .video-box video,
|
|
||||||
.page-luma .video-container .videos .item .video-box img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .videos .item .video-name {
|
|
||||||
color: #e1e1e1;
|
|
||||||
font-size: 16px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
padding: 6px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .videos .item .opts {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .videos .item .opts .btn {
|
|
||||||
margin-right: 10px;
|
|
||||||
background-color: rgba(255,255,255,0.15);
|
|
||||||
border: none;
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 3px 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .videos .item .opts .btn .iconfont {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.page-luma .video-container .videos .item .opts .btn:hover {
|
|
||||||
background-color: rgba(255,255,255,0.2);
|
|
||||||
}
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
.page-luma {
|
|
||||||
display flex
|
|
||||||
height 100%
|
|
||||||
// background-color #0E0808
|
|
||||||
// background: var(--chat-bg);
|
|
||||||
|
|
||||||
overflow auto
|
|
||||||
//justify-content center
|
|
||||||
flex-flow column
|
|
||||||
align-items center
|
|
||||||
// background: linear-gradient(180deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3));
|
|
||||||
|
|
||||||
|
|
||||||
.prompt-box {
|
|
||||||
display flex
|
|
||||||
max-width 56rem
|
|
||||||
width 100%
|
|
||||||
padding 20px
|
|
||||||
flex-flow column
|
|
||||||
|
|
||||||
.images {
|
|
||||||
display flex
|
|
||||||
flex-flow row
|
|
||||||
padding-bottom 10px
|
|
||||||
justify-content center
|
|
||||||
align-items center
|
|
||||||
|
|
||||||
.item {
|
|
||||||
position relative
|
|
||||||
|
|
||||||
.el-image {
|
|
||||||
width 100px
|
|
||||||
height 100px
|
|
||||||
border-radius 6px
|
|
||||||
margin-right 10px
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
position absolute
|
|
||||||
cursor pointer
|
|
||||||
font-size 20px
|
|
||||||
color #545454
|
|
||||||
right 10px
|
|
||||||
top 0
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color #888888
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-swap {
|
|
||||||
margin-right 10px
|
|
||||||
.icon-exchange{
|
|
||||||
color var(--text-theme-color)
|
|
||||||
cursor pointer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.prompt-container {
|
|
||||||
width: 100%;
|
|
||||||
.input-container {
|
|
||||||
background: var(--chat-bg);
|
|
||||||
// background: linear-gradient(90deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3));
|
|
||||||
border-radius: 28px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
// box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
||||||
|
|
||||||
.prompt-input {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
color var(--text-theme-color);
|
|
||||||
font-size: 14px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
resize: none;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
line-height 24px
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
|
|
||||||
scrollbar-width: none; /* 隐藏滚动条 */
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload-icon, .send-icon {
|
|
||||||
color var( --el-color-primary)
|
|
||||||
.iconfont {
|
|
||||||
font-size 20px
|
|
||||||
cursor pointer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.upload-icon {
|
|
||||||
position relative
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.params {
|
|
||||||
display flex
|
|
||||||
justify-content right
|
|
||||||
color var(--text-theme-color);
|
|
||||||
font-size 14px
|
|
||||||
padding 10px 30px
|
|
||||||
|
|
||||||
.item-group {
|
|
||||||
margin-left 20px
|
|
||||||
.label {
|
|
||||||
margin-right 5px
|
|
||||||
position relative
|
|
||||||
top 1px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.video-container {
|
|
||||||
display flex
|
|
||||||
flex-flow column
|
|
||||||
width 100%
|
|
||||||
padding 0 40px
|
|
||||||
|
|
||||||
.h-title {
|
|
||||||
color var(--text-theme-color)
|
|
||||||
width 100%
|
|
||||||
// font-size 36px
|
|
||||||
text-align left
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-box {
|
|
||||||
padding 0
|
|
||||||
.item {
|
|
||||||
display flex
|
|
||||||
flex-flow row
|
|
||||||
align-items center
|
|
||||||
min-height 100px
|
|
||||||
padding 10px 15px
|
|
||||||
border-radius 10px
|
|
||||||
cursor pointer
|
|
||||||
margin-bottom 20px
|
|
||||||
background: var(--chat-bg);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.left {
|
|
||||||
.container {
|
|
||||||
width 160px
|
|
||||||
position relative
|
|
||||||
|
|
||||||
.video{
|
|
||||||
width 160px
|
|
||||||
border-radius 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-image {
|
|
||||||
width 160px
|
|
||||||
height 90px
|
|
||||||
border-radius 5px
|
|
||||||
}
|
|
||||||
|
|
||||||
.duration {
|
|
||||||
position absolute
|
|
||||||
bottom 0
|
|
||||||
right 0
|
|
||||||
background-color rgba(14,8,8,.7)
|
|
||||||
padding 0 3px
|
|
||||||
font-family 'Input Sans'
|
|
||||||
font-size 14px
|
|
||||||
font-weight 700
|
|
||||||
border-radius .125rem
|
|
||||||
}
|
|
||||||
|
|
||||||
.play {
|
|
||||||
position absolute
|
|
||||||
width: 100%
|
|
||||||
height 100%
|
|
||||||
top: 0;
|
|
||||||
left: 50%;
|
|
||||||
border none
|
|
||||||
border-radius 5px
|
|
||||||
background rgba(100, 100, 100, 0.3)
|
|
||||||
cursor pointer
|
|
||||||
color var(--text-theme-color)
|
|
||||||
|
|
||||||
opacity 0
|
|
||||||
transform: translate(-50%, 0px);
|
|
||||||
transition opacity 0.3s ease 0s
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.play {
|
|
||||||
opacity 1
|
|
||||||
//display block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
width 100%
|
|
||||||
//border 1px solid saddlebrown
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
align-items flex-start
|
|
||||||
flex-flow column
|
|
||||||
padding 0 20px
|
|
||||||
|
|
||||||
.prompt,.failed {
|
|
||||||
padding 0
|
|
||||||
font-size 16px
|
|
||||||
max-height 80px
|
|
||||||
line-height 28px
|
|
||||||
overflow hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
}
|
|
||||||
.prompt {
|
|
||||||
color var( --text-fb)
|
|
||||||
cursor: text
|
|
||||||
}
|
|
||||||
.failed {
|
|
||||||
color #E4696B
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
display flex
|
|
||||||
justify-content right
|
|
||||||
min-width 200px;
|
|
||||||
font-size 14px
|
|
||||||
padding 0
|
|
||||||
|
|
||||||
.tools {
|
|
||||||
display flex
|
|
||||||
justify-content left
|
|
||||||
align-items center
|
|
||||||
flex-flow row
|
|
||||||
height 90px
|
|
||||||
|
|
||||||
.btn-publish {
|
|
||||||
padding 2px 10px
|
|
||||||
|
|
||||||
.text {
|
|
||||||
margin-right 10px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-icon {
|
|
||||||
background none
|
|
||||||
padding 6px
|
|
||||||
transition background 0.6s ease 0s
|
|
||||||
color #919191
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
// background #5f5958
|
|
||||||
// color #e1e1e1
|
|
||||||
color:var(--el-color-primary)
|
|
||||||
}
|
|
||||||
|
|
||||||
.downloading {
|
|
||||||
width 16px
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination {
|
|
||||||
margin-top 20px
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
}
|
|
||||||
|
|
||||||
//.videos {
|
|
||||||
// .item {
|
|
||||||
// margin-bottom 20px
|
|
||||||
//
|
|
||||||
// .video-box {
|
|
||||||
// width 100%
|
|
||||||
// aspect-ratio: 16/9;
|
|
||||||
// border-radius 10px
|
|
||||||
// video,img {
|
|
||||||
// width: 100%;
|
|
||||||
// height: 100%;
|
|
||||||
// object-fit: cover;
|
|
||||||
// border-radius 10px
|
|
||||||
// cursor pointer
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// .video-name {
|
|
||||||
// color #e1e1e1
|
|
||||||
// font-size 16px
|
|
||||||
// white-space nowrap
|
|
||||||
// overflow hidden
|
|
||||||
// text-overflow ellipsis
|
|
||||||
// padding 6px 0
|
|
||||||
// text-align center
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// .opts {
|
|
||||||
// display flex
|
|
||||||
// justify-content center
|
|
||||||
// .btn {
|
|
||||||
// margin-right 10px
|
|
||||||
// background-color hsla(0,0%,100%,.15)
|
|
||||||
// border none
|
|
||||||
// border-radius 20px
|
|
||||||
// padding 3px 15px
|
|
||||||
// cursor pointer
|
|
||||||
// color var(--text-theme-color)
|
|
||||||
// font-size 14px
|
|
||||||
//
|
|
||||||
// .iconfont {
|
|
||||||
// font-size 11px
|
|
||||||
// position relative
|
|
||||||
// margin-right 5px
|
|
||||||
// top -2px
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// .el-image {
|
|
||||||
// width 14px
|
|
||||||
// height 14px
|
|
||||||
// margin-right 5px
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// &:hover {
|
|
||||||
// background-color hsla(0,0%,100%,.2)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin-right 10px
|
|
||||||
border none
|
|
||||||
border-radius 5px
|
|
||||||
padding 5px 10px
|
|
||||||
cursor pointer
|
|
||||||
color: var(--theme-text-color-primary)
|
|
||||||
background-color var(--btn-bg)
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
--text-fb:#000;
|
--text-fb:#000;
|
||||||
--text-color: #5b62ce; // 主要的文本颜色
|
--text-color: #5b62ce; // 主要的文本颜色
|
||||||
--normal-color: rgba(43, 54, 116, 1); // 普通颜色
|
--normal-color: rgba(43, 54, 116, 1); // 普通颜色
|
||||||
|
--theme-textcolor-normal:#5b62ce;;
|
||||||
p, h1, h2, h3, h4, h5, h6, article {
|
p, h1, h2, h3, h4, h5, h6, article {
|
||||||
font-family: $font-regular;
|
font-family: $font-regular;
|
||||||
}
|
}
|
||||||
|
|||||||
567
web/src/assets/css/video.styl
Normal file
567
web/src/assets/css/video.styl
Normal file
@@ -0,0 +1,567 @@
|
|||||||
|
// 视频生成页面统一样式
|
||||||
|
.page-video {
|
||||||
|
display: flex;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: var(--chat-bg);
|
||||||
|
|
||||||
|
// Element Plus 样式覆盖
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-textarea) {
|
||||||
|
--el-input-focus-border-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-textarea__inner) {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__wrapper {
|
||||||
|
background: transparent;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左侧参数面板
|
||||||
|
.params-panel {
|
||||||
|
min-width: 320px;
|
||||||
|
max-width: 320px;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 0 15px 20px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
font-size: 14px;
|
||||||
|
overflow: auto;
|
||||||
|
background: var(--card-bg);
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--theme-textcolor-normal);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐藏滚动条
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标签页样式
|
||||||
|
.video-type-tabs {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
:deep(.el-tabs__item.is-active) {
|
||||||
|
color: var(--theme-textcolor-normal);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs__item) {
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-tabs__active-bar) {
|
||||||
|
background-color: var(--theme-textcolor-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs {
|
||||||
|
--el-tabs-header-height: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数行
|
||||||
|
.param-line {
|
||||||
|
padding: 5px 0;
|
||||||
|
|
||||||
|
&.pt {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-right: 5px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-inner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单项样式
|
||||||
|
.el-form {
|
||||||
|
.el-form-item__label {
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input, .el-slider {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
width: 100%;
|
||||||
|
--el-select-input-focus-border-color: var(--el-color-primary);
|
||||||
|
--el-input-focus-border-color: var(--el-color-primary);
|
||||||
|
|
||||||
|
.el-input__wrapper {
|
||||||
|
background: var(--chat-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__inner {
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 网格内容项
|
||||||
|
.grid-content {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid var(--chat-bg);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid var(--theme-border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border: 1px solid var(--theme-border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
|
||||||
|
&.proportion {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.texts {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 2px;
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运镜控制
|
||||||
|
.camera-control {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
|
||||||
|
:deep(.el-form-item:last-child) {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成按钮
|
||||||
|
.generate-btn {
|
||||||
|
.iconfont {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 项目组样式
|
||||||
|
.item-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片模式切换样式
|
||||||
|
.image-mode-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-right: 10px;
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过渡动画
|
||||||
|
.slide-fade-enter-active {
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-leave-active {
|
||||||
|
transition: all 0.3s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-enter-from {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-fade-leave-to {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// KeLing 参数面板特有样式
|
||||||
|
.params-container {
|
||||||
|
// 任务类型标签页
|
||||||
|
.task-type-tabs {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #6b778c;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片上传样式
|
||||||
|
.img-inline {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.img-uploader {
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
:deep(.el-upload) {
|
||||||
|
border: 1px dashed var(--el-border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
transition: var(--el-transition-duration-fast);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.removeimg {
|
||||||
|
position: absolute;
|
||||||
|
right: -5px;
|
||||||
|
top: -5px;
|
||||||
|
z-index: 10;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #f56c6c;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-swap {
|
||||||
|
.icon-exchange {
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交按钮
|
||||||
|
.submit-btn {
|
||||||
|
display: flex;
|
||||||
|
margin: 20px 0;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 算力信息
|
||||||
|
.text-info {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 0;
|
||||||
|
|
||||||
|
.el-tag {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧主要内容区域
|
||||||
|
.main-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
flex: 1;
|
||||||
|
background: var(--chat-bg);
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
// 作品标题栏
|
||||||
|
.works-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.h-title {
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .filter-buttons {
|
||||||
|
// .el-button-group {
|
||||||
|
// .el-button {
|
||||||
|
// --el-button-bg-color: var(--card-bg);
|
||||||
|
// --el-button-border-color: var(--chat-bg);
|
||||||
|
// --el-button-text-color: var(--text-theme-color);
|
||||||
|
// --el-button-hover-bg-color: var(--theme-border-hover);
|
||||||
|
// --el-button-hover-border-color: var(--theme-border-hover);
|
||||||
|
// --el-button-active-bg-color: var(--el-color-primary);
|
||||||
|
// --el-button-active-border-color: var(--el-color-primary);
|
||||||
|
|
||||||
|
// &.is-type-primary {
|
||||||
|
// --el-button-bg-color: var(--el-color-primary);
|
||||||
|
// --el-button-border-color: var(--el-color-primary);
|
||||||
|
// --el-button-text-color: #ffffff;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务列表
|
||||||
|
.video-list {
|
||||||
|
.list-box {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
|
||||||
|
.left {
|
||||||
|
.container {
|
||||||
|
width: 160px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.video {
|
||||||
|
width: 160px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--el-fill-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-image {
|
||||||
|
width: 160px;
|
||||||
|
height: 90px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duration {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: rgba(14, 8, 8, 0.7);
|
||||||
|
padding: 0 3px;
|
||||||
|
font-family: 'Input Sans';
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.play {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: rgba(100, 100, 100, 0.3);
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, 0px);
|
||||||
|
transition: opacity 0.3s ease 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.play {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-flow: column;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
.prompt, .failed {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
max-height: 80px;
|
||||||
|
line-height: 28px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt {
|
||||||
|
color: var(--text-fb);
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.failed {
|
||||||
|
color: #E4696B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pb-2 {
|
||||||
|
padding-bottom: 8px;
|
||||||
|
|
||||||
|
.el-tag {
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
display: flex;
|
||||||
|
justify-content: right;
|
||||||
|
min-width: 200px;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.tools {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
flex-flow: row;
|
||||||
|
height: 90px;
|
||||||
|
|
||||||
|
.btn-publish {
|
||||||
|
padding: 2px 10px;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
background: none;
|
||||||
|
padding: 6px;
|
||||||
|
transition: background 0.6s ease 0s;
|
||||||
|
color: #919191;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloading {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-error {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
.pagination {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通用按钮样式
|
||||||
|
.btn {
|
||||||
|
margin-right: 10px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--theme-text-color-primary);
|
||||||
|
background-color: var(--btn-bg);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无数据样式
|
||||||
|
.no-data {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: var(--text-theme-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式设计
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.page-video {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.params-panel {
|
||||||
|
min-width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
.video-list .list-box .item {
|
||||||
|
.left .container {
|
||||||
|
width: 120px;
|
||||||
|
|
||||||
|
.video, .el-image {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4125778 */
|
font-family: "iconfont"; /* Project id 4125778 */
|
||||||
src: url('iconfont.woff2?t=1740279975534') format('woff2'),
|
src: url('iconfont.woff2?t=1752731646117') format('woff2'),
|
||||||
url('iconfont.woff?t=1740279975534') format('woff'),
|
url('iconfont.woff?t=1752731646117') format('woff'),
|
||||||
url('iconfont.ttf?t=1740279975534') format('truetype');
|
url('iconfont.ttf?t=1752731646117') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,142 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-video:before {
|
||||||
|
content: "\e63f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-empty-box:before {
|
||||||
|
content: "\e638";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-check2:before {
|
||||||
|
content: "\e7e2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-creator:before {
|
||||||
|
content: "\e6a1";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-withdraw:before {
|
||||||
|
content: "\e689";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-withdraw-log:before {
|
||||||
|
content: "\e635";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-money:before {
|
||||||
|
content: "\e831";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-doller:before {
|
||||||
|
content: "\e633";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wallet:before {
|
||||||
|
content: "\e64d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-check:before {
|
||||||
|
content: "\e810";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-refuse:before {
|
||||||
|
content: "\e629";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-Reject:before {
|
||||||
|
content: "\e70d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-clock:before {
|
||||||
|
content: "\e65d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-eye-close:before {
|
||||||
|
content: "\e7aa";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-eye-open:before {
|
||||||
|
content: "\e7ab";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-list:before {
|
||||||
|
content: "\e650";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-categroy:before {
|
||||||
|
content: "\e620";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zhankai:before {
|
||||||
|
content: "\e632";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wechat-mini:before {
|
||||||
|
content: "\e63d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-niutou:before {
|
||||||
|
content: "\e64c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-qiniu:before {
|
||||||
|
content: "\e62c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-storage:before {
|
||||||
|
content: "\e69a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-localstorage:before {
|
||||||
|
content: "\ea8d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-minio:before {
|
||||||
|
content: "\e855";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-aliyun:before {
|
||||||
|
content: "\e672";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sms:before {
|
||||||
|
content: "\e82c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-duanxin:before {
|
||||||
|
content: "\e65c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yanzm:before {
|
||||||
|
content: "\e625";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yaoqm:before {
|
||||||
|
content: "\e66e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-epay:before {
|
||||||
|
content: "\e628";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-coze:before {
|
||||||
|
content: "\e61b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-token:before {
|
||||||
|
content: "\e68e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-reset:before {
|
||||||
|
content: "\e649";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-stats:before {
|
||||||
|
content: "\e878";
|
||||||
|
}
|
||||||
|
|
||||||
.icon-keling:before {
|
.icon-keling:before {
|
||||||
content: "\eab7";
|
content: "\eab7";
|
||||||
}
|
}
|
||||||
@@ -289,7 +425,7 @@
|
|||||||
content: "\e6c4";
|
content: "\e6c4";
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-mp1:before {
|
.icon-mp4:before {
|
||||||
content: "\e647";
|
content: "\e647";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,244 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "1283",
|
||||||
|
"name": "视频",
|
||||||
|
"font_class": "video",
|
||||||
|
"unicode": "e63f",
|
||||||
|
"unicode_decimal": 58943
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "35224131",
|
||||||
|
"name": "empty-box",
|
||||||
|
"font_class": "empty-box",
|
||||||
|
"unicode": "e638",
|
||||||
|
"unicode_decimal": 58936
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9143175",
|
||||||
|
"name": "审核",
|
||||||
|
"font_class": "check2",
|
||||||
|
"unicode": "e7e2",
|
||||||
|
"unicode_decimal": 59362
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "15450788",
|
||||||
|
"name": "创作者中心",
|
||||||
|
"font_class": "creator",
|
||||||
|
"unicode": "e6a1",
|
||||||
|
"unicode_decimal": 59041
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1134341",
|
||||||
|
"name": "提现",
|
||||||
|
"font_class": "withdraw",
|
||||||
|
"unicode": "e689",
|
||||||
|
"unicode_decimal": 59017
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10887127",
|
||||||
|
"name": "提现记录",
|
||||||
|
"font_class": "withdraw-log",
|
||||||
|
"unicode": "e635",
|
||||||
|
"unicode_decimal": 58933
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "34452904",
|
||||||
|
"name": "money-rmb",
|
||||||
|
"font_class": "money",
|
||||||
|
"unicode": "e831",
|
||||||
|
"unicode_decimal": 59441
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "34467697",
|
||||||
|
"name": "doller",
|
||||||
|
"font_class": "doller",
|
||||||
|
"unicode": "e633",
|
||||||
|
"unicode_decimal": 58931
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9512709",
|
||||||
|
"name": "钱包¥",
|
||||||
|
"font_class": "wallet",
|
||||||
|
"unicode": "e64d",
|
||||||
|
"unicode_decimal": 58957
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "8365142",
|
||||||
|
"name": "check",
|
||||||
|
"font_class": "check",
|
||||||
|
"unicode": "e810",
|
||||||
|
"unicode_decimal": 59408
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10213506",
|
||||||
|
"name": "refuse",
|
||||||
|
"font_class": "refuse",
|
||||||
|
"unicode": "e629",
|
||||||
|
"unicode_decimal": 58921
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "19393806",
|
||||||
|
"name": "Reject",
|
||||||
|
"font_class": "Reject",
|
||||||
|
"unicode": "e70d",
|
||||||
|
"unicode_decimal": 59149
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "248916",
|
||||||
|
"name": "clock",
|
||||||
|
"font_class": "clock",
|
||||||
|
"unicode": "e65d",
|
||||||
|
"unicode_decimal": 58973
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6151096",
|
||||||
|
"name": "eye-close",
|
||||||
|
"font_class": "eye-close",
|
||||||
|
"unicode": "e7aa",
|
||||||
|
"unicode_decimal": 59306
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6151097",
|
||||||
|
"name": "eye-open",
|
||||||
|
"font_class": "eye-open",
|
||||||
|
"unicode": "e7ab",
|
||||||
|
"unicode_decimal": 59307
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6145570",
|
||||||
|
"name": "list",
|
||||||
|
"font_class": "list",
|
||||||
|
"unicode": "e650",
|
||||||
|
"unicode_decimal": 58960
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "13127646",
|
||||||
|
"name": "categroy",
|
||||||
|
"font_class": "categroy",
|
||||||
|
"unicode": "e620",
|
||||||
|
"unicode_decimal": 58912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1613505",
|
||||||
|
"name": "展开",
|
||||||
|
"font_class": "zhankai",
|
||||||
|
"unicode": "e632",
|
||||||
|
"unicode_decimal": 58930
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10905663",
|
||||||
|
"name": "微信小程序",
|
||||||
|
"font_class": "wechat-mini",
|
||||||
|
"unicode": "e63d",
|
||||||
|
"unicode_decimal": 58941
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "21530643",
|
||||||
|
"name": "牛头",
|
||||||
|
"font_class": "niutou",
|
||||||
|
"unicode": "e64c",
|
||||||
|
"unicode_decimal": 58956
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "24877229",
|
||||||
|
"name": "七牛云",
|
||||||
|
"font_class": "qiniu",
|
||||||
|
"unicode": "e62c",
|
||||||
|
"unicode_decimal": 58924
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "3717493",
|
||||||
|
"name": "存储服务",
|
||||||
|
"font_class": "storage",
|
||||||
|
"unicode": "e69a",
|
||||||
|
"unicode_decimal": 59034
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7133059",
|
||||||
|
"name": "本地存储",
|
||||||
|
"font_class": "localstorage",
|
||||||
|
"unicode": "ea8d",
|
||||||
|
"unicode_decimal": 60045
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9360420",
|
||||||
|
"name": "minio",
|
||||||
|
"font_class": "minio",
|
||||||
|
"unicode": "e855",
|
||||||
|
"unicode_decimal": 59477
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "21053628",
|
||||||
|
"name": "阿里云",
|
||||||
|
"font_class": "aliyun",
|
||||||
|
"unicode": "e672",
|
||||||
|
"unicode_decimal": 58994
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "30046100",
|
||||||
|
"name": "comment-sms",
|
||||||
|
"font_class": "sms",
|
||||||
|
"unicode": "e82c",
|
||||||
|
"unicode_decimal": 59436
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "4893414",
|
||||||
|
"name": "短信",
|
||||||
|
"font_class": "duanxin",
|
||||||
|
"unicode": "e65c",
|
||||||
|
"unicode_decimal": 58972
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "553324",
|
||||||
|
"name": "验证码",
|
||||||
|
"font_class": "yanzm",
|
||||||
|
"unicode": "e625",
|
||||||
|
"unicode_decimal": 58917
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1264836",
|
||||||
|
"name": "邀请码",
|
||||||
|
"font_class": "yaoqm",
|
||||||
|
"unicode": "e66e",
|
||||||
|
"unicode_decimal": 58990
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "24827618",
|
||||||
|
"name": "网易支付",
|
||||||
|
"font_class": "epay",
|
||||||
|
"unicode": "e628",
|
||||||
|
"unicode_decimal": 58920
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "43863501",
|
||||||
|
"name": "Coze",
|
||||||
|
"font_class": "coze",
|
||||||
|
"unicode": "e61b",
|
||||||
|
"unicode_decimal": 58907
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "11551884",
|
||||||
|
"name": "token",
|
||||||
|
"font_class": "token",
|
||||||
|
"unicode": "e68e",
|
||||||
|
"unicode_decimal": 59022
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "38795534",
|
||||||
|
"name": "reset",
|
||||||
|
"font_class": "reset",
|
||||||
|
"unicode": "e649",
|
||||||
|
"unicode_decimal": 58953
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "5838820",
|
||||||
|
"name": "统计",
|
||||||
|
"font_class": "stats",
|
||||||
|
"unicode": "e878",
|
||||||
|
"unicode_decimal": 59512
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "42692844",
|
"icon_id": "42692844",
|
||||||
"name": "可灵大模型",
|
"name": "可灵大模型",
|
||||||
@@ -491,7 +729,7 @@
|
|||||||
{
|
{
|
||||||
"icon_id": "12600802",
|
"icon_id": "12600802",
|
||||||
"name": "mp4",
|
"name": "mp4",
|
||||||
"font_class": "mp1",
|
"font_class": "mp4",
|
||||||
"unicode": "e647",
|
"unicode": "e647",
|
||||||
"unicode_decimal": 58951
|
"unicode_decimal": 58951
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -104,16 +104,10 @@ const routes = [
|
|||||||
component: () => import('@/views/Song.vue'),
|
component: () => import('@/views/Song.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'luma',
|
name: 'video',
|
||||||
path: '/luma',
|
path: '/video',
|
||||||
meta: { title: 'Luma视频创作' },
|
meta: { title: '视频创作中心' },
|
||||||
component: () => import('@/views/Luma.vue'),
|
component: () => import('@/views/Video.vue'),
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'keling',
|
|
||||||
path: '/keling',
|
|
||||||
meta: { title: 'KeLing视频创作' },
|
|
||||||
component: () => import('@/views/KeLing.vue'),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
590
web/src/store/video.js
Normal file
590
web/src/store/video.js
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
|
||||||
|
// * Use of this source code is governed by a Apache-2.0 license
|
||||||
|
// * that can be found in the LICENSE file.
|
||||||
|
// * @Author yangjian102621@163.com
|
||||||
|
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
import nodata from '@/assets/img/no-data.png'
|
||||||
|
import { checkSession, getSystemInfo } from '@/store/cache'
|
||||||
|
import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog'
|
||||||
|
import { httpDownload, httpGet, httpPost } from '@/utils/http'
|
||||||
|
import { replaceImg, substr } from '@/utils/libs'
|
||||||
|
import Clipboard from 'clipboard'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { computed, reactive, ref } from 'vue'
|
||||||
|
|
||||||
|
export const useVideoStore = defineStore('video', () => {
|
||||||
|
// 当前活跃的视频类型
|
||||||
|
const activeVideoType = ref('luma')
|
||||||
|
|
||||||
|
// 共同状态
|
||||||
|
const loading = ref(false)
|
||||||
|
const list = ref([])
|
||||||
|
const noData = ref(true)
|
||||||
|
const page = ref(1)
|
||||||
|
const pageSize = ref(10)
|
||||||
|
const total = ref(0)
|
||||||
|
const taskPulling = ref(true)
|
||||||
|
const pullHandler = ref(null)
|
||||||
|
const clipboard = ref(null)
|
||||||
|
|
||||||
|
// 视频预览
|
||||||
|
const showDialog = ref(false)
|
||||||
|
const currentVideoUrl = ref('')
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
const isLogin = ref(false)
|
||||||
|
const availablePower = ref(100)
|
||||||
|
|
||||||
|
// 任务筛选
|
||||||
|
const taskFilter = ref('all') // 'all', 'luma', 'keling'
|
||||||
|
|
||||||
|
// Luma 相关状态
|
||||||
|
const lumaUseImageMode = ref(false) // 是否使用图片辅助生成
|
||||||
|
const lumaParams = reactive({
|
||||||
|
prompt: '',
|
||||||
|
expand_prompt: false,
|
||||||
|
loop: false,
|
||||||
|
image: '', // 起始帧
|
||||||
|
image_tail: '', // 结束帧
|
||||||
|
})
|
||||||
|
|
||||||
|
// KeLing 相关状态
|
||||||
|
const isGenerating = ref(false)
|
||||||
|
const generating = ref(false)
|
||||||
|
const kelingPowerCost = ref(10)
|
||||||
|
const lumaPowerCost = ref(10)
|
||||||
|
const showCameraControl = ref(false)
|
||||||
|
const keLingPowers = ref({})
|
||||||
|
|
||||||
|
const models = ref([
|
||||||
|
{ text: '可灵 1.6', value: 'kling-v1-6' },
|
||||||
|
{ text: '可灵 1.5', value: 'kling-v1-5' },
|
||||||
|
{ text: '可灵 1.0', value: 'kling-v1' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const rates = [
|
||||||
|
{ css: 'square', value: '1:1', text: '1:1', img: '/images/mj/rate_1_1.png' },
|
||||||
|
{ css: 'size16-9', value: '16:9', text: '16:9', img: '/images/mj/rate_16_9.png' },
|
||||||
|
{ css: 'size9-16', value: '9:16', text: '9:16', img: '/images/mj/rate_9_16.png' },
|
||||||
|
]
|
||||||
|
|
||||||
|
// KeLing 相关状态
|
||||||
|
const kelingUseImageMode = ref(false) // 是否使用图片辅助生成
|
||||||
|
const kelingParams = reactive({
|
||||||
|
model: 'kling-v1-6',
|
||||||
|
prompt: '',
|
||||||
|
negative_prompt: '',
|
||||||
|
cfg_scale: 0.7,
|
||||||
|
mode: 'std',
|
||||||
|
aspect_ratio: '16:9',
|
||||||
|
duration: '5',
|
||||||
|
camera_control: {
|
||||||
|
type: '',
|
||||||
|
config: {
|
||||||
|
horizontal: 0,
|
||||||
|
vertical: 0,
|
||||||
|
pan: 0,
|
||||||
|
tilt: 0,
|
||||||
|
roll: 0,
|
||||||
|
zoom: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
image: '',
|
||||||
|
image_tail: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const currentList = computed(() => {
|
||||||
|
return list.value.filter((item) => {
|
||||||
|
if (taskFilter.value === 'all') {
|
||||||
|
return true
|
||||||
|
} else if (taskFilter.value === 'luma') {
|
||||||
|
return item.type === 'luma' || !item.type // 兼容旧数据
|
||||||
|
} else if (taskFilter.value === 'keling') {
|
||||||
|
return item.type === 'keling'
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化方法
|
||||||
|
const init = async () => {
|
||||||
|
try {
|
||||||
|
const user = await checkSession()
|
||||||
|
isLogin.value = true
|
||||||
|
availablePower.value = user.power
|
||||||
|
|
||||||
|
// 初始化剪贴板
|
||||||
|
if (clipboard.value) {
|
||||||
|
clipboard.value.destroy()
|
||||||
|
}
|
||||||
|
clipboard.value = new Clipboard('.copy-prompt')
|
||||||
|
clipboard.value.on('success', () => {
|
||||||
|
ElMessage.success('复制成功!')
|
||||||
|
})
|
||||||
|
clipboard.value.on('error', () => {
|
||||||
|
ElMessage.error('复制失败!')
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取系统信息
|
||||||
|
const sysInfo = await getSystemInfo()
|
||||||
|
lumaPowerCost.value = sysInfo.data.luma_power
|
||||||
|
keLingPowers.value = sysInfo.data.keling_powers
|
||||||
|
updateModelPower()
|
||||||
|
|
||||||
|
// 获取数据并开始轮询
|
||||||
|
await fetchData(1)
|
||||||
|
startPolling()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('初始化失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理方法
|
||||||
|
const cleanup = () => {
|
||||||
|
if (clipboard.value) {
|
||||||
|
clipboard.value.destroy()
|
||||||
|
}
|
||||||
|
stopPolling()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始轮询
|
||||||
|
const startPolling = () => {
|
||||||
|
if (pullHandler.value) {
|
||||||
|
clearInterval(pullHandler.value)
|
||||||
|
}
|
||||||
|
pullHandler.value = setInterval(() => {
|
||||||
|
if (taskPulling.value) {
|
||||||
|
fetchData(page.value)
|
||||||
|
}
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止轮询
|
||||||
|
const stopPolling = () => {
|
||||||
|
if (pullHandler.value) {
|
||||||
|
clearInterval(pullHandler.value)
|
||||||
|
pullHandler.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取任务列表
|
||||||
|
const fetchData = async (_page) => {
|
||||||
|
if (_page) {
|
||||||
|
page.value = _page
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await httpGet('/api/video/list', {
|
||||||
|
page: page.value,
|
||||||
|
page_size: pageSize.value,
|
||||||
|
type: taskFilter.value === 'all' ? '' : taskFilter.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
total.value = res.data.total
|
||||||
|
let needPull = false
|
||||||
|
const items = []
|
||||||
|
|
||||||
|
for (let v of res.data.items) {
|
||||||
|
if (v.progress === 0 || v.progress === 102) {
|
||||||
|
needPull = true
|
||||||
|
}
|
||||||
|
items.push({
|
||||||
|
...v,
|
||||||
|
downloading: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = false
|
||||||
|
taskPulling.value = needPull
|
||||||
|
|
||||||
|
if (JSON.stringify(list.value) !== JSON.stringify(items)) {
|
||||||
|
list.value = items
|
||||||
|
}
|
||||||
|
noData.value = list.value.length === 0
|
||||||
|
} catch (error) {
|
||||||
|
loading.value = false
|
||||||
|
noData.value = true
|
||||||
|
console.error('获取任务列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Luma 相关方法
|
||||||
|
const uploadLumaStartImage = async (file) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file.file)
|
||||||
|
|
||||||
|
try {
|
||||||
|
showLoading('图片上传中...')
|
||||||
|
const res = await httpPost('/api/upload', formData)
|
||||||
|
lumaParams.image = res.data.url
|
||||||
|
ElMessage.success('上传成功')
|
||||||
|
closeLoading()
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('上传失败: ' + error.message)
|
||||||
|
closeLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadLumaEndImage = async (file) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file.file)
|
||||||
|
|
||||||
|
try {
|
||||||
|
showLoading('图片上传中...')
|
||||||
|
const res = await httpPost('/api/upload', formData)
|
||||||
|
lumaParams.image_tail = res.data.url
|
||||||
|
ElMessage.success('上传成功')
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('上传失败: ' + error.message)
|
||||||
|
} finally {
|
||||||
|
closeLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeLumaImage = (type) => {
|
||||||
|
if (type === 'start') {
|
||||||
|
lumaParams.image = ''
|
||||||
|
} else if (type === 'end') {
|
||||||
|
lumaParams.image_tail = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchLumaImages = () => {
|
||||||
|
;[lumaParams.image, lumaParams.image_tail] = [lumaParams.image_tail, lumaParams.image]
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleLumaImageMode = (enabled) => {
|
||||||
|
lumaUseImageMode.value = enabled
|
||||||
|
// 关闭时清空图片
|
||||||
|
if (!enabled) {
|
||||||
|
lumaParams.image = ''
|
||||||
|
lumaParams.image_tail = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLumaVideo = async () => {
|
||||||
|
if (!lumaParams.prompt?.trim()) {
|
||||||
|
return ElMessage.error('请输入视频描述')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lumaUseImageMode.value && !lumaParams.image) {
|
||||||
|
return ElMessage.error('请上传起始帧图片')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理参数
|
||||||
|
const requestData = {
|
||||||
|
...lumaParams,
|
||||||
|
task_type: lumaUseImageMode.value ? 'image2video' : 'text2video',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理图片链接
|
||||||
|
if (requestData.image) {
|
||||||
|
requestData.first_frame_img = replaceImg(requestData.image)
|
||||||
|
}
|
||||||
|
if (requestData.image_tail) {
|
||||||
|
requestData.end_frame_img = replaceImg(requestData.image_tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await httpPost('/api/video/luma/create', requestData)
|
||||||
|
await fetchData(1)
|
||||||
|
taskPulling.value = true
|
||||||
|
showMessageOK('创建任务成功')
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('创建任务失败:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeLing 相关方法
|
||||||
|
const changeRate = (item) => {
|
||||||
|
kelingParams.aspect_ratio = item.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateModelPower = () => {
|
||||||
|
showCameraControl.value = kelingParams.model === 'kling-v1-5' && kelingParams.mode === 'pro'
|
||||||
|
kelingPowerCost.value =
|
||||||
|
keLingPowers.value[`${kelingParams.model}_${kelingParams.mode}_${kelingParams.duration}`] ||
|
||||||
|
10
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleKelingImageMode = (enabled) => {
|
||||||
|
kelingUseImageMode.value = enabled
|
||||||
|
// 关闭时清空图片
|
||||||
|
if (!enabled) {
|
||||||
|
kelingParams.image = ''
|
||||||
|
kelingParams.image_tail = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadKelingStartImage = async (file) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file.file)
|
||||||
|
|
||||||
|
try {
|
||||||
|
showLoading('图片上传中...')
|
||||||
|
const res = await httpPost('/api/upload', formData)
|
||||||
|
kelingParams.image = res.data.url
|
||||||
|
ElMessage.success('上传成功')
|
||||||
|
closeLoading()
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('上传失败: ' + error.message)
|
||||||
|
closeLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadKelingEndImage = async (file) => {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file.file)
|
||||||
|
|
||||||
|
try {
|
||||||
|
showLoading('图片上传中...')
|
||||||
|
const res = await httpPost('/api/upload', formData)
|
||||||
|
kelingParams.image_tail = res.data.url
|
||||||
|
ElMessage.success('上传成功')
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('上传失败: ' + error.message)
|
||||||
|
} finally {
|
||||||
|
closeLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeKelingImage = (type) => {
|
||||||
|
if (type === 'start') {
|
||||||
|
kelingParams.image = ''
|
||||||
|
} else if (type === 'end') {
|
||||||
|
kelingParams.image_tail = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const switchKelingImages = () => {
|
||||||
|
;[kelingParams.image, kelingParams.image_tail] = [kelingParams.image_tail, kelingParams.image]
|
||||||
|
}
|
||||||
|
|
||||||
|
const createKelingVideo = async () => {
|
||||||
|
if (generating.value) return
|
||||||
|
|
||||||
|
if (!kelingParams.prompt?.trim()) {
|
||||||
|
return ElMessage.error('请输入视频描述')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kelingParams.prompt.length > 500) {
|
||||||
|
return ElMessage.error('视频描述不能超过 500 个字符')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kelingUseImageMode.value && !kelingParams.image) {
|
||||||
|
return ElMessage.error('请上传起始帧图片')
|
||||||
|
}
|
||||||
|
|
||||||
|
generating.value = true
|
||||||
|
|
||||||
|
// 处理参数
|
||||||
|
const requestData = {
|
||||||
|
...kelingParams,
|
||||||
|
task_type: kelingUseImageMode.value ? 'image2video' : 'text2video',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理图片链接
|
||||||
|
if (requestData.image) {
|
||||||
|
requestData.image = replaceImg(requestData.image)
|
||||||
|
}
|
||||||
|
if (requestData.image_tail) {
|
||||||
|
requestData.image_tail = replaceImg(requestData.image_tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await httpPost('/api/video/keling/create', requestData)
|
||||||
|
showMessageOK('任务创建成功')
|
||||||
|
|
||||||
|
// 新增重置
|
||||||
|
page.value = 1
|
||||||
|
list.value.unshift({
|
||||||
|
progress: 0,
|
||||||
|
prompt: requestData.prompt,
|
||||||
|
raw_data: {
|
||||||
|
task_type: requestData.task_type,
|
||||||
|
model: requestData.model,
|
||||||
|
duration: requestData.duration,
|
||||||
|
mode: requestData.mode,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
taskPulling.value = true
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('创建失败: ' + error.message)
|
||||||
|
} finally {
|
||||||
|
generating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提示词生成
|
||||||
|
const generatePrompt = async () => {
|
||||||
|
if (isGenerating.value) return
|
||||||
|
|
||||||
|
const prompt = activeVideoType.value === 'luma' ? lumaParams.prompt : kelingParams.prompt
|
||||||
|
if (!prompt) {
|
||||||
|
return showMessageError('请输入原始提示词')
|
||||||
|
}
|
||||||
|
|
||||||
|
isGenerating.value = true
|
||||||
|
showLoading('正在生成视频脚本...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await httpPost('/api/prompt/video', { prompt })
|
||||||
|
if (activeVideoType.value === 'luma') {
|
||||||
|
lumaParams.prompt = res.data
|
||||||
|
} else {
|
||||||
|
kelingParams.prompt = res.data
|
||||||
|
}
|
||||||
|
closeLoading()
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('生成提示词失败:' + error.message)
|
||||||
|
closeLoading()
|
||||||
|
} finally {
|
||||||
|
isGenerating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视频预览
|
||||||
|
const playVideo = (item) => {
|
||||||
|
currentVideoUrl.value = replaceImg(item.video_url)
|
||||||
|
showDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视频下载
|
||||||
|
const downloadVideo = async (item) => {
|
||||||
|
const url = replaceImg(item.video_url)
|
||||||
|
const downloadURL = `${import.meta.env.VITE_API_HOST}/api/download?url=${url}`
|
||||||
|
const urlObj = new URL(url)
|
||||||
|
const fileName = urlObj.pathname.split('/').pop()
|
||||||
|
|
||||||
|
item.downloading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await httpDownload(downloadURL)
|
||||||
|
const blob = new Blob([response.data])
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.href = URL.createObjectURL(blob)
|
||||||
|
link.download = fileName
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(link.href)
|
||||||
|
item.downloading = false
|
||||||
|
} catch (error) {
|
||||||
|
showMessageError('下载失败')
|
||||||
|
item.downloading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除任务
|
||||||
|
const removeJob = async (item) => {
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm('此操作将会删除任务相关文件,继续操作码?', '删除提示', {
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
})
|
||||||
|
|
||||||
|
await httpGet('/api/video/remove', { id: item.id })
|
||||||
|
ElMessage.success('任务删除成功')
|
||||||
|
await fetchData()
|
||||||
|
} catch (error) {
|
||||||
|
if (error !== 'cancel') {
|
||||||
|
ElMessage.error('任务删除失败:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布任务
|
||||||
|
const publishJob = async (item) => {
|
||||||
|
try {
|
||||||
|
await httpGet('/api/video/publish', { id: item.id, publish: item.publish })
|
||||||
|
ElMessage.success('操作成功')
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('操作失败:' + error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换视频类型
|
||||||
|
const switchVideoType = (type) => {
|
||||||
|
activeVideoType.value = type
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换任务筛选
|
||||||
|
const switchTaskFilter = (filter) => {
|
||||||
|
taskFilter.value = filter
|
||||||
|
page.value = 1
|
||||||
|
fetchData(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
activeVideoType,
|
||||||
|
loading,
|
||||||
|
list,
|
||||||
|
currentList,
|
||||||
|
noData,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
total,
|
||||||
|
taskPulling,
|
||||||
|
showDialog,
|
||||||
|
currentVideoUrl,
|
||||||
|
isLogin,
|
||||||
|
availablePower,
|
||||||
|
nodata,
|
||||||
|
taskFilter,
|
||||||
|
|
||||||
|
// Luma 状态
|
||||||
|
lumaUseImageMode,
|
||||||
|
lumaParams,
|
||||||
|
lumaPowerCost,
|
||||||
|
// KeLing 状态
|
||||||
|
kelingUseImageMode,
|
||||||
|
isGenerating,
|
||||||
|
generating,
|
||||||
|
kelingPowerCost,
|
||||||
|
showCameraControl,
|
||||||
|
keLingPowers,
|
||||||
|
models,
|
||||||
|
rates,
|
||||||
|
kelingParams,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
init,
|
||||||
|
cleanup,
|
||||||
|
fetchData,
|
||||||
|
switchVideoType,
|
||||||
|
switchTaskFilter,
|
||||||
|
|
||||||
|
// Luma 方法
|
||||||
|
toggleLumaImageMode,
|
||||||
|
uploadLumaStartImage,
|
||||||
|
uploadLumaEndImage,
|
||||||
|
removeLumaImage,
|
||||||
|
switchLumaImages,
|
||||||
|
createLumaVideo,
|
||||||
|
|
||||||
|
// KeLing 方法
|
||||||
|
toggleKelingImageMode,
|
||||||
|
changeRate,
|
||||||
|
updateModelPower,
|
||||||
|
uploadKelingStartImage,
|
||||||
|
uploadKelingEndImage,
|
||||||
|
removeKelingImage,
|
||||||
|
switchKelingImages,
|
||||||
|
createKelingVideo,
|
||||||
|
|
||||||
|
// 共同方法
|
||||||
|
generatePrompt,
|
||||||
|
playVideo,
|
||||||
|
downloadVideo,
|
||||||
|
removeJob,
|
||||||
|
publishJob,
|
||||||
|
substr,
|
||||||
|
replaceImg,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,745 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-keling">
|
|
||||||
<div class="inner custom-scroll">
|
|
||||||
<!-- 左侧参数设置面板 -->
|
|
||||||
<el-scrollbar max-height="100vh">
|
|
||||||
<div class="mj-box">
|
|
||||||
<h2>视频参数设置</h2>
|
|
||||||
<el-form :model="params" label-width="80px" label-position="left">
|
|
||||||
<!-- 画面比例 -->
|
|
||||||
<div class="param-line">
|
|
||||||
<div class="param-line pt">
|
|
||||||
<span>画面比例:</span>
|
|
||||||
<el-tooltip content="生成画面的尺寸比例" placement="right">
|
|
||||||
<el-icon>
|
|
||||||
<InfoFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="param-line pt">
|
|
||||||
<el-row :gutter="10">
|
|
||||||
<el-col :span="8" v-for="item in rates" :key="item.value">
|
|
||||||
<div
|
|
||||||
class="flex-col items-center"
|
|
||||||
:class="
|
|
||||||
item.value === params.aspect_ratio ? 'grid-content active' : 'grid-content'
|
|
||||||
"
|
|
||||||
@click="changeRate(item)"
|
|
||||||
>
|
|
||||||
<el-image class="icon proportion" :src="item.img" fit="cover"></el-image>
|
|
||||||
<div class="texts">{{ item.text }}</div>
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 模型选择 -->
|
|
||||||
<div class="param-line">
|
|
||||||
<el-form-item label="模型选择">
|
|
||||||
<el-select
|
|
||||||
v-model="params.model"
|
|
||||||
placeholder="请选择模型"
|
|
||||||
@change="updateModelPower"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in models"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.text"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 视频时长 -->
|
|
||||||
<div class="param-line">
|
|
||||||
<el-form-item label="视频时长">
|
|
||||||
<el-select
|
|
||||||
v-model="params.duration"
|
|
||||||
placeholder="请选择时长"
|
|
||||||
@change="updateModelPower"
|
|
||||||
>
|
|
||||||
<el-option label="5秒" value="5" />
|
|
||||||
<el-option label="10秒" value="10" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 生成模式 -->
|
|
||||||
<div class="param-line">
|
|
||||||
<el-form-item label="生成模式">
|
|
||||||
<el-select
|
|
||||||
v-model="params.mode"
|
|
||||||
placeholder="请选择模式"
|
|
||||||
@change="updateModelPower"
|
|
||||||
>
|
|
||||||
<el-option label="标准模式" value="std" />
|
|
||||||
<el-option label="专业模式" value="pro" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 创意程度 -->
|
|
||||||
<div class="param-line">
|
|
||||||
<el-form-item label="创意程度">
|
|
||||||
<el-slider v-model="params.cfg_scale" :min="0" :max="1" :step="0.1" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 运镜控制 -->
|
|
||||||
<div class="param-line" v-if="showCameraControl">
|
|
||||||
<div class="param-line pt">
|
|
||||||
<span>运镜控制:</span>
|
|
||||||
<el-tooltip content="生成画面的运镜效果,仅 1.5的高级模式可用" placement="right">
|
|
||||||
<el-icon>
|
|
||||||
<InfoFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 添加运镜类型选择 -->
|
|
||||||
<div class="param-line">
|
|
||||||
<el-select v-model="params.camera_control.type" placeholder="请选择运镜类型">
|
|
||||||
<el-option label="请选择" value="" />
|
|
||||||
<el-option label="简单运镜" value="simple" />
|
|
||||||
<el-option label="下移拉远" value="down_back" />
|
|
||||||
<el-option label="推进上移" value="forward_up" />
|
|
||||||
<el-option label="右旋推进" value="right_turn_forward" />
|
|
||||||
<el-option label="左旋推进" value="left_turn_forward" />
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 仅在simple模式下显示详细配置 -->
|
|
||||||
<div class="camera-control mt-2" v-if="params.camera_control.type === 'simple'">
|
|
||||||
<el-form-item label="水平移动">
|
|
||||||
<el-slider
|
|
||||||
v-model="params.camera_control.config.horizontal"
|
|
||||||
:min="-10"
|
|
||||||
:max="10"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="垂直移动">
|
|
||||||
<el-slider v-model="params.camera_control.config.vertical" :min="-10" :max="10" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="左右旋转">
|
|
||||||
<el-slider v-model="params.camera_control.config.pan" :min="-10" :max="10" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="上下旋转">
|
|
||||||
<el-slider v-model="params.camera_control.config.tilt" :min="-10" :max="10" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="横向翻转">
|
|
||||||
<el-slider v-model="params.camera_control.config.roll" :min="-10" :max="10" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="镜头缩放">
|
|
||||||
<el-slider v-model="params.camera_control.config.zoom" :min="-10" :max="10" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</el-scrollbar>
|
|
||||||
|
|
||||||
<!-- 右侧主内容区 -->
|
|
||||||
<div class="main-content task-list-inner">
|
|
||||||
<!-- 任务类型选择 -->
|
|
||||||
<div class="param-line">
|
|
||||||
<el-tabs v-model="params.task_type" @tab-change="tabChange" class="title-tabs">
|
|
||||||
<el-tab-pane label="文生视频" name="text2video">
|
|
||||||
<div class="text">使用文字描述想要生成视频的内容</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
<el-tab-pane label="图生视频" name="image2video">
|
|
||||||
<div class="text">
|
|
||||||
以某张图片为底稿参考来创作视频,生成类似风格或类型视频,支持 PNG /JPG/JPEG
|
|
||||||
格式图片;
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 生成操作区 -->
|
|
||||||
<div class="generation-area">
|
|
||||||
<div v-if="params.task_type === 'text2video'" class="text2video">
|
|
||||||
<el-input
|
|
||||||
v-model="params.prompt"
|
|
||||||
type="textarea"
|
|
||||||
maxlength="500"
|
|
||||||
:autosize="{ minRows: 4, maxRows: 6 }"
|
|
||||||
placeholder="请在此输入视频提示词,您也可以点击下面的提示词助手生成视频提示词"
|
|
||||||
/>
|
|
||||||
<el-row class="text-info">
|
|
||||||
<el-button
|
|
||||||
class="generate-btn"
|
|
||||||
@click="generatePrompt"
|
|
||||||
:loading="isGenerating"
|
|
||||||
size="small"
|
|
||||||
color="#5865f2"
|
|
||||||
>
|
|
||||||
<i class="iconfont icon-chuangzuo"></i>
|
|
||||||
生成专业视频提示词
|
|
||||||
</el-button>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-else class="image2video">
|
|
||||||
<div class="image-upload img-inline">
|
|
||||||
<div class="upload-box img-uploader video-img-box">
|
|
||||||
<el-icon v-if="params.image" @click="removeImage('start')" class="removeimg"
|
|
||||||
><CircleCloseFilled
|
|
||||||
/></el-icon>
|
|
||||||
|
|
||||||
<h4>起始帧</h4>
|
|
||||||
<el-upload
|
|
||||||
class="uploader img-uploader"
|
|
||||||
:auto-upload="true"
|
|
||||||
:show-file-list="false"
|
|
||||||
:http-request="uploadStartImage"
|
|
||||||
accept=".jpg,.png,.jpeg"
|
|
||||||
>
|
|
||||||
<img v-if="params.image" :src="params.image" class="preview" />
|
|
||||||
|
|
||||||
<el-icon v-else class="upload-icon"><Plus /></el-icon>
|
|
||||||
</el-upload>
|
|
||||||
</div>
|
|
||||||
<div class="btn-swap" v-if="params.image && params.image_tail">
|
|
||||||
<i class="iconfont icon-exchange" @click="switchReverse"></i>
|
|
||||||
</div>
|
|
||||||
<div class="upload-box img-uploader video-img-box">
|
|
||||||
<el-icon v-if="params.image_tail" @click="removeImage('end')" class="removeimg"
|
|
||||||
><CircleCloseFilled
|
|
||||||
/></el-icon>
|
|
||||||
<h4>结束帧</h4>
|
|
||||||
<el-upload
|
|
||||||
class="uploader"
|
|
||||||
:auto-upload="true"
|
|
||||||
:show-file-list="false"
|
|
||||||
:http-request="uploadEndImage"
|
|
||||||
accept=".jpg,.png,.jpeg"
|
|
||||||
>
|
|
||||||
<img v-if="params.image_tail" :src="params.image_tail" class="preview" />
|
|
||||||
<el-icon v-else class="upload-icon"><Plus /></el-icon>
|
|
||||||
</el-upload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="param-line pt">
|
|
||||||
<div class="flex-row justify-between items-center">
|
|
||||||
<div class="flex-row justify-start items-center">
|
|
||||||
<span>提示词:</span>
|
|
||||||
<el-tooltip content="输入你想要的内容,用逗号分割" placement="right">
|
|
||||||
<el-icon>
|
|
||||||
<InfoFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="param-line pt">
|
|
||||||
<el-input
|
|
||||||
v-model="params.prompt"
|
|
||||||
type="textarea"
|
|
||||||
:autosize="{ minRows: 4, maxRows: 6 }"
|
|
||||||
placeholder="描述视频画面细节"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 排除内容 -->
|
|
||||||
<div class="param-line pt">
|
|
||||||
<div class="flex-row justify-between items-center">
|
|
||||||
<div class="flex-row justify-start items-center">
|
|
||||||
<span>不希望出现的内容:(可选)</span>
|
|
||||||
<el-tooltip content="不想出现在图片上的元素(例如:树,建筑)" placement="right">
|
|
||||||
<el-icon>
|
|
||||||
<InfoFilled />
|
|
||||||
</el-icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="param-line pt">
|
|
||||||
<el-input
|
|
||||||
v-model="params.negative_prompt"
|
|
||||||
type="textarea"
|
|
||||||
:autosize="{ minRows: 4, maxRows: 6 }"
|
|
||||||
placeholder="请在此输入你不希望出现在视频上的内容"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 算力显示 -->
|
|
||||||
<el-row class="text-info">
|
|
||||||
<el-text type="primary"
|
|
||||||
>每次生成视频消耗 <el-text type="warning">{{ powerCost }}算力;</el-text> </el-text
|
|
||||||
>
|
|
||||||
<el-text type="primary"
|
|
||||||
>当前可用算力:<el-text type="warning">{{ availablePower }}</el-text></el-text
|
|
||||||
>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<!-- 生成按钮 -->
|
|
||||||
<div class="submit-btn">
|
|
||||||
<el-button type="primary" :dark="false" @click="generate" round>立即生成</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 任务列表区域 -->
|
|
||||||
<div class="video-list">
|
|
||||||
<h2 class="text-xl p-3">你的作品</h2>
|
|
||||||
|
|
||||||
<div class="list-box" v-if="!noData">
|
|
||||||
<div v-for="item in list" :key="item.id">
|
|
||||||
<div class="item">
|
|
||||||
<div class="left">
|
|
||||||
<div class="container">
|
|
||||||
<div v-if="item.progress === 100">
|
|
||||||
<video
|
|
||||||
class="video"
|
|
||||||
:src="item.video_url"
|
|
||||||
preload="auto"
|
|
||||||
loop="loop"
|
|
||||||
muted="muted"
|
|
||||||
>
|
|
||||||
您的浏览器不支持视频播放
|
|
||||||
</video>
|
|
||||||
<button
|
|
||||||
class="play flex justify-center items-center"
|
|
||||||
@click="previewVideo(item)"
|
|
||||||
>
|
|
||||||
<img src="/images/play.svg" alt="" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<el-image v-else-if="item.progress === 101" :src="item.cover_url" fit="cover" />
|
|
||||||
<generating message="正在生成视频" v-else />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="center">
|
|
||||||
<div class="pb-2">
|
|
||||||
<el-tag class="mr-1">{{ item.raw_data.task_type }}</el-tag>
|
|
||||||
<el-tag class="mr-1">{{ item.raw_data.model }}</el-tag>
|
|
||||||
<el-tag class="mr-1">{{ item.raw_data.duration }}秒</el-tag>
|
|
||||||
<el-tag class="mr-1">{{ item.raw_data.mode }}</el-tag>
|
|
||||||
</div>
|
|
||||||
<div class="failed" v-if="item.progress === 101">
|
|
||||||
任务执行失败:{{ item.err_msg }},任务提示词:{{ item.prompt }}
|
|
||||||
</div>
|
|
||||||
<div class="prompt" v-else>
|
|
||||||
{{ substr(item.prompt, 1000) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right" v-if="item.progress === 100">
|
|
||||||
<div class="tools">
|
|
||||||
<el-tooltip content="复制提示词" placement="top">
|
|
||||||
<button class="btn btn-icon copy-prompt" :data-clipboard-text="item.prompt">
|
|
||||||
<i class="iconfont icon-copy"></i>
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<!-- <button class="btn btn-publish">
|
|
||||||
<span class="text">发布</span>
|
|
||||||
<black-switch v-model:value="item.publish" @change="publishJob(item)" size="small" />
|
|
||||||
</button> -->
|
|
||||||
|
|
||||||
<el-tooltip content="下载视频" placement="top">
|
|
||||||
<button
|
|
||||||
class="btn btn-icon"
|
|
||||||
@click="downloadVideo(item)"
|
|
||||||
:disabled="item.downloading"
|
|
||||||
>
|
|
||||||
<i class="iconfont icon-download" v-if="!item.downloading"></i>
|
|
||||||
<el-image
|
|
||||||
src="/images/loading.gif"
|
|
||||||
class="downloading"
|
|
||||||
fit="cover"
|
|
||||||
v-else
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<el-tooltip content="删除" placement="top">
|
|
||||||
<button class="btn btn-icon" @click="removeJob(item)">
|
|
||||||
<i class="iconfont icon-remove"></i>
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-error" v-else>
|
|
||||||
<el-button type="danger" @click="removeJob(item)" circle>
|
|
||||||
<i class="iconfont icon-remove"></i>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-empty
|
|
||||||
:image-size="100"
|
|
||||||
:image="nodata"
|
|
||||||
description="没有任何作品,赶紧去创作吧!"
|
|
||||||
v-else
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="pagination">
|
|
||||||
<el-pagination
|
|
||||||
v-if="total > pageSize"
|
|
||||||
background
|
|
||||||
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
|
||||||
layout="total,prev, pager, next"
|
|
||||||
:hide-on-single-page="true"
|
|
||||||
v-model:current-page="page"
|
|
||||||
v-model:page-size="pageSize"
|
|
||||||
@current-change="fetchData"
|
|
||||||
:total="total"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 视频预览对话框 -->
|
|
||||||
<black-dialog
|
|
||||||
v-model:show="previewVisible"
|
|
||||||
title="视频预览"
|
|
||||||
hide-footer
|
|
||||||
@cancal="previewVisible = false"
|
|
||||||
width="auto"
|
|
||||||
>
|
|
||||||
<video
|
|
||||||
v-if="currentVideo"
|
|
||||||
:src="currentVideo"
|
|
||||||
controls
|
|
||||||
style="max-width: 90vw; max-height: 90vh"
|
|
||||||
:autoplay="true"
|
|
||||||
loop="loop"
|
|
||||||
muted="muted"
|
|
||||||
preload="auto"
|
|
||||||
>
|
|
||||||
您的浏览器不支持视频播放
|
|
||||||
</video>
|
|
||||||
</black-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import nodata from '@/assets/img/no-data.png'
|
|
||||||
import BlackDialog from '@/components/ui/BlackDialog.vue'
|
|
||||||
import Generating from '@/components/ui/Generating.vue'
|
|
||||||
import { checkSession, getSystemInfo } from '@/store/cache'
|
|
||||||
import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog'
|
|
||||||
import { httpDownload, httpGet, httpPost } from '@/utils/http'
|
|
||||||
import { replaceImg, substr } from '@/utils/libs'
|
|
||||||
import { CircleCloseFilled, InfoFilled, Plus } from '@element-plus/icons-vue'
|
|
||||||
import Clipboard from 'clipboard'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import { onMounted, onUnmounted, reactive, ref } from 'vue'
|
|
||||||
|
|
||||||
const models = ref([
|
|
||||||
{
|
|
||||||
text: '可灵 1.6',
|
|
||||||
value: 'kling-v1-6',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '可灵 1.5',
|
|
||||||
value: 'kling-v1-5',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: '可灵 1.0',
|
|
||||||
value: 'kling-v1',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
// 参数设置
|
|
||||||
const params = reactive({
|
|
||||||
task_type: 'text2video',
|
|
||||||
model: models.value[0].value,
|
|
||||||
prompt: '',
|
|
||||||
negative_prompt: '',
|
|
||||||
cfg_scale: 0.7,
|
|
||||||
mode: 'std',
|
|
||||||
aspect_ratio: '16:9',
|
|
||||||
duration: '5',
|
|
||||||
camera_control: {
|
|
||||||
type: '',
|
|
||||||
config: {
|
|
||||||
horizontal: 0,
|
|
||||||
vertical: 0,
|
|
||||||
pan: 0,
|
|
||||||
tilt: 0,
|
|
||||||
roll: 0,
|
|
||||||
zoom: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
image: '',
|
|
||||||
image_tail: '',
|
|
||||||
})
|
|
||||||
const rates = [
|
|
||||||
{ css: 'square', value: '1:1', text: '1:1', img: '/images/mj/rate_1_1.png' },
|
|
||||||
|
|
||||||
{
|
|
||||||
css: 'size16-9',
|
|
||||||
value: '16:9',
|
|
||||||
text: '16:9',
|
|
||||||
img: '/images/mj/rate_16_9.png',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
css: 'size9-16',
|
|
||||||
value: '9:16',
|
|
||||||
text: '9:16',
|
|
||||||
img: '/images/mj/rate_9_16.png',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// 切换图片比例
|
|
||||||
const changeRate = (item) => {
|
|
||||||
params.aspect_ratio = item.value
|
|
||||||
}
|
|
||||||
|
|
||||||
const generating = ref(false)
|
|
||||||
const isGenerating = ref(false)
|
|
||||||
const powerCost = ref(10)
|
|
||||||
const availablePower = ref(100)
|
|
||||||
const taskFilter = ref('all')
|
|
||||||
const loading = ref(false)
|
|
||||||
const list = ref([])
|
|
||||||
const noData = ref(true)
|
|
||||||
const page = ref(1)
|
|
||||||
const pageSize = ref(10)
|
|
||||||
const total = ref(0)
|
|
||||||
const taskPulling = ref(true)
|
|
||||||
const pullHandler = ref(null)
|
|
||||||
const previewVisible = ref(false)
|
|
||||||
const currentVideo = ref('')
|
|
||||||
const showCameraControl = ref(false)
|
|
||||||
const keLingPowers = ref({})
|
|
||||||
const isLogin = ref(false)
|
|
||||||
// 动态更新模型消耗的算力
|
|
||||||
const updateModelPower = () => {
|
|
||||||
showCameraControl.value = params.model === 'kling-v1-5' && params.mode === 'pro'
|
|
||||||
powerCost.value = keLingPowers.value[`${params.model}_${params.mode}_${params.duration}`] || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tab切换
|
|
||||||
const tabChange = (tab) => {
|
|
||||||
params.task_type = tab
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadStartImage = async (file) => {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('file', file.file)
|
|
||||||
try {
|
|
||||||
showLoading('图片上传中...')
|
|
||||||
const res = await httpPost('/api/upload', formData)
|
|
||||||
params.image = res.data.url
|
|
||||||
ElMessage.success('上传成功')
|
|
||||||
closeLoading()
|
|
||||||
} catch (e) {
|
|
||||||
showMessageError('上传失败: ' + e.message)
|
|
||||||
closeLoading()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//移除图片
|
|
||||||
const removeImage = (type) => {
|
|
||||||
if (type === 'start') {
|
|
||||||
params.image = ''
|
|
||||||
} else if (type === 'end') {
|
|
||||||
params.image_tail = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//图片交换方法
|
|
||||||
const switchReverse = () => {
|
|
||||||
;[params.image, params.image_tail] = [params.image_tail, params.image]
|
|
||||||
}
|
|
||||||
const uploadEndImage = async (file) => {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('file', file.file)
|
|
||||||
try {
|
|
||||||
const res = await httpPost('/api/upload', formData)
|
|
||||||
params.image_tail = res.data.url
|
|
||||||
ElMessage.success('上传成功')
|
|
||||||
} catch (e) {
|
|
||||||
showMessageError('上传失败: ' + e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const generatePrompt = async () => {
|
|
||||||
if (isGenerating.value) return
|
|
||||||
if (!params.prompt) {
|
|
||||||
return showMessageError('请输入视频描述')
|
|
||||||
}
|
|
||||||
isGenerating.value = true
|
|
||||||
try {
|
|
||||||
const res = await httpPost('/api/prompt/video', { prompt: params.prompt })
|
|
||||||
params.prompt = res.data
|
|
||||||
} catch (e) {
|
|
||||||
showMessageError('生成失败: ' + e.message)
|
|
||||||
} finally {
|
|
||||||
isGenerating.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const generate = async () => {
|
|
||||||
//增加防抖
|
|
||||||
if (generating.value) return
|
|
||||||
if (!params.prompt?.trim()) {
|
|
||||||
return ElMessage.error('请输入视频描述')
|
|
||||||
}
|
|
||||||
// 提示词长度不能超过 500
|
|
||||||
if (params.prompt.length > 500) {
|
|
||||||
return ElMessage.error('视频描述不能超过 500 个字符')
|
|
||||||
}
|
|
||||||
if (params.task_type === 'image2video' && !params.image) {
|
|
||||||
return ElMessage.error('请上传起始帧图片')
|
|
||||||
}
|
|
||||||
generating.value = true
|
|
||||||
// 处理图片链接
|
|
||||||
if (params.image) {
|
|
||||||
params.image = replaceImg(params.image)
|
|
||||||
}
|
|
||||||
if (params.image_tail) {
|
|
||||||
params.image_tail = replaceImg(params.image_tail)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await httpPost('/api/video/keling/create', params)
|
|
||||||
showMessageOK('任务创建成功')
|
|
||||||
// 新增重置
|
|
||||||
page.value = 1
|
|
||||||
list.value.unshift({
|
|
||||||
progress: 0,
|
|
||||||
prompt: params.prompt,
|
|
||||||
raw_data: {
|
|
||||||
task_type: params.task_type,
|
|
||||||
model: params.model,
|
|
||||||
duration: params.duration,
|
|
||||||
mode: params.mode,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
taskPulling.value = true
|
|
||||||
} catch (e) {
|
|
||||||
showMessageError('创建失败: ' + e.message)
|
|
||||||
} finally {
|
|
||||||
generating.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchData = (_page) => {
|
|
||||||
if (_page) {
|
|
||||||
page.value = _page
|
|
||||||
}
|
|
||||||
|
|
||||||
httpGet('/api/video/list', {
|
|
||||||
page: page.value,
|
|
||||||
page_size: pageSize.value,
|
|
||||||
type: 'keling',
|
|
||||||
task_type: taskFilter.value === 'all' ? '' : taskFilter.value,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
total.value = res.data.total
|
|
||||||
let needPull = false
|
|
||||||
const items = []
|
|
||||||
for (let v of res.data.items) {
|
|
||||||
if (v.progress === 0 || v.progress === 102) {
|
|
||||||
needPull = true
|
|
||||||
}
|
|
||||||
items.push({
|
|
||||||
...v,
|
|
||||||
downloading: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
loading.value = false
|
|
||||||
taskPulling.value = needPull
|
|
||||||
if (JSON.stringify(list.value) !== JSON.stringify(items)) {
|
|
||||||
list.value = items
|
|
||||||
}
|
|
||||||
noData.value = list.value.length === 0
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false
|
|
||||||
noData.value = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const previewVideo = (task) => {
|
|
||||||
currentVideo.value = task.video_url
|
|
||||||
previewVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const downloadVideo = async (task) => {
|
|
||||||
try {
|
|
||||||
const res = await httpDownload(`/api/download?url=${replaceImg(task.video_url)}`)
|
|
||||||
const blob = new Blob([res.data])
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = URL.createObjectURL(blob)
|
|
||||||
link.download = `video_${task.id}.mp4`
|
|
||||||
link.click()
|
|
||||||
URL.revokeObjectURL(link.href)
|
|
||||||
} catch (e) {
|
|
||||||
showMessageError('下载失败: ' + e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除任务
|
|
||||||
const removeJob = (item) => {
|
|
||||||
ElMessageBox.confirm('此操作将会删除任务相关文件,继续操作码?', '删除提示', {
|
|
||||||
confirmButtonText: '确认',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
httpGet('/api/video/remove', { id: item.id })
|
|
||||||
.then(() => {
|
|
||||||
ElMessage.success('任务删除成功')
|
|
||||||
fetchData(page.value)
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
ElMessage.error('任务删除失败:' + e.message)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
const clipboard = ref(null)
|
|
||||||
// 生命周期钩子
|
|
||||||
onMounted(() => {
|
|
||||||
checkSession()
|
|
||||||
.then((u) => {
|
|
||||||
isLogin.value = true
|
|
||||||
availablePower.value = u.power
|
|
||||||
fetchData(1)
|
|
||||||
// 设置轮询
|
|
||||||
pullHandler.value = setInterval(() => {
|
|
||||||
if (taskPulling.value) {
|
|
||||||
fetchData(page.value)
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
|
|
||||||
clipboard.value = new Clipboard('.copy-prompt')
|
|
||||||
clipboard.value.on('success', () => {
|
|
||||||
ElMessage.success('复制成功!')
|
|
||||||
})
|
|
||||||
clipboard.value.on('error', () => {
|
|
||||||
ElMessage.error('复制失败!')
|
|
||||||
})
|
|
||||||
|
|
||||||
getSystemInfo().then((res) => {
|
|
||||||
keLingPowers.value = res.data.keling_powers
|
|
||||||
updateModelPower()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
clipboard.value.destroy()
|
|
||||||
if (pullHandler.value) {
|
|
||||||
clearInterval(pullHandler.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
@import '../assets/css/keling.styl'
|
|
||||||
</style>
|
|
||||||
@@ -1,387 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="page-luma">
|
|
||||||
<div class="prompt-box">
|
|
||||||
<div class="images">
|
|
||||||
<template v-for="(img, index) in images" :key="img">
|
|
||||||
<div class="item">
|
|
||||||
<el-image :src="replaceImg(img)" fit="cover" />
|
|
||||||
<el-icon @click="remove(img)"><CircleCloseFilled /></el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="btn-swap" v-if="images.length === 2 && index === 0">
|
|
||||||
<i class="iconfont icon-exchange" @click="switchReverse"></i>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="prompt-container">
|
|
||||||
<div class="input-container">
|
|
||||||
<div class="upload-icon" v-if="images.length < 2">
|
|
||||||
<el-upload
|
|
||||||
class="avatar-uploader"
|
|
||||||
:auto-upload="true"
|
|
||||||
:show-file-list="false"
|
|
||||||
:http-request="upload"
|
|
||||||
accept=".jpg,.png,.jpeg"
|
|
||||||
>
|
|
||||||
<i class="iconfont icon-image"></i>
|
|
||||||
</el-upload>
|
|
||||||
</div>
|
|
||||||
<textarea
|
|
||||||
class="prompt-input"
|
|
||||||
:rows="row"
|
|
||||||
v-model="formData.prompt"
|
|
||||||
maxlength="2000"
|
|
||||||
placeholder="请输入提示词或者上传图片"
|
|
||||||
autofocus
|
|
||||||
>
|
|
||||||
</textarea>
|
|
||||||
<div class="send-icon" @click="create">
|
|
||||||
<i class="iconfont icon-send"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="params">
|
|
||||||
<div class="item-group">
|
|
||||||
<el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2">
|
|
||||||
<i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
|
|
||||||
<span>生成AI视频提示词</span>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
<div class="item-group">
|
|
||||||
<span class="label">循环参考图</span>
|
|
||||||
<el-switch v-model="formData.loop" size="small" />
|
|
||||||
</div>
|
|
||||||
<div class="item-group">
|
|
||||||
<span class="label">提示词优化</span>
|
|
||||||
<el-switch v-model="formData.expand_prompt" size="small" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-container
|
|
||||||
class="video-container"
|
|
||||||
v-loading="loading"
|
|
||||||
element-loading-background="rgba(100,100,100,0.3)"
|
|
||||||
>
|
|
||||||
<h2 class="h-title text-2xl mb-5 mt-2">你的作品</h2>
|
|
||||||
|
|
||||||
<div class="list-box" v-if="!noData">
|
|
||||||
<div v-for="item in list" :key="item.id">
|
|
||||||
<div class="item">
|
|
||||||
<div class="left">
|
|
||||||
<div class="container">
|
|
||||||
<div v-if="item.progress === 100">
|
|
||||||
<video
|
|
||||||
class="video"
|
|
||||||
:src="replaceImg(item.video_url)"
|
|
||||||
preload="auto"
|
|
||||||
loop="loop"
|
|
||||||
muted="muted"
|
|
||||||
>
|
|
||||||
您的浏览器不支持视频播放
|
|
||||||
</video>
|
|
||||||
<button class="play flex justify-center items-center" @click="play(item)">
|
|
||||||
<img src="/images/play.svg" alt="" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<el-image :src="item.cover_url" fit="cover" v-else-if="item.progress === 101" />
|
|
||||||
<generating message="正在生成视频" v-else />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="center">
|
|
||||||
<div class="failed" v-if="item.progress === 101">
|
|
||||||
任务执行失败:{{ item.err_msg }},任务提示词:{{ item.prompt }}
|
|
||||||
</div>
|
|
||||||
<div class="prompt" v-else>{{ item.prompt }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="right" v-if="item.progress === 100">
|
|
||||||
<div class="tools">
|
|
||||||
<!-- <button class="btn btn-publish">
|
|
||||||
<span class="text">发布</span>
|
|
||||||
<black-switch v-model:value="item.publish" @change="publishJob(item)" size="small" />
|
|
||||||
</button> -->
|
|
||||||
|
|
||||||
<el-tooltip content="复制提示词" placement="top">
|
|
||||||
<button class="btn btn-icon copy-prompt" :data-clipboard-text="item.prompt">
|
|
||||||
<i class="iconfont icon-copy"></i>
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
|
|
||||||
<el-tooltip content="下载视频" placement="top">
|
|
||||||
<button class="btn btn-icon" @click="download(item)" :disabled="item.downloading">
|
|
||||||
<i class="iconfont icon-download" v-if="!item.downloading"></i>
|
|
||||||
<el-image src="/images/loading.gif" class="downloading" fit="cover" v-else />
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="删除" placement="top">
|
|
||||||
<button class="btn btn-icon" @click="removeJob(item)">
|
|
||||||
<i class="iconfont icon-remove"></i>
|
|
||||||
</button>
|
|
||||||
</el-tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="right-error" v-else>
|
|
||||||
<el-button type="danger" @click="removeJob(item)" circle>
|
|
||||||
<i class="iconfont icon-remove"></i>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<el-empty
|
|
||||||
:image-size="100"
|
|
||||||
:image="nodata"
|
|
||||||
description="没有任何作品,赶紧去创作吧!"
|
|
||||||
v-else
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="pagination">
|
|
||||||
<el-pagination
|
|
||||||
v-if="total > pageSize"
|
|
||||||
background
|
|
||||||
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
|
||||||
layout="total,prev, pager, next"
|
|
||||||
:hide-on-single-page="true"
|
|
||||||
v-model:current-page="page"
|
|
||||||
v-model:page-size="pageSize"
|
|
||||||
@current-change="fetchData(page)"
|
|
||||||
:total="total"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</el-container>
|
|
||||||
<black-dialog
|
|
||||||
v-model:show="showDialog"
|
|
||||||
title="预览视频"
|
|
||||||
hide-footer
|
|
||||||
@cancal="showDialog = false"
|
|
||||||
width="auto"
|
|
||||||
>
|
|
||||||
<video
|
|
||||||
style="max-width: 90vw; max-height: 90vh"
|
|
||||||
:src="currentVideoUrl"
|
|
||||||
preload="auto"
|
|
||||||
:autoplay="true"
|
|
||||||
loop="loop"
|
|
||||||
muted="muted"
|
|
||||||
v-show="showDialog"
|
|
||||||
>
|
|
||||||
您的浏览器不支持视频播放
|
|
||||||
</video>
|
|
||||||
</black-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import nodata from '@/assets/img/no-data.png'
|
|
||||||
|
|
||||||
import BlackDialog from '@/components/ui/BlackDialog.vue'
|
|
||||||
import Generating from '@/components/ui/Generating.vue'
|
|
||||||
import { checkSession } from '@/store/cache'
|
|
||||||
import { closeLoading, showLoading, showMessageError, showMessageOK } from '@/utils/dialog'
|
|
||||||
import { httpDownload, httpGet, httpPost } from '@/utils/http'
|
|
||||||
import { replaceImg } from '@/utils/libs'
|
|
||||||
import { CircleCloseFilled } from '@element-plus/icons-vue'
|
|
||||||
import Clipboard from 'clipboard'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import { onMounted, onUnmounted, reactive, ref } from 'vue'
|
|
||||||
const showDialog = ref(false)
|
|
||||||
const currentVideoUrl = ref('')
|
|
||||||
const row = ref(1)
|
|
||||||
const images = ref([])
|
|
||||||
|
|
||||||
const formData = reactive({
|
|
||||||
prompt: '',
|
|
||||||
expand_prompt: false,
|
|
||||||
loop: false,
|
|
||||||
first_frame_img: '',
|
|
||||||
end_frame_img: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
const loading = ref(false)
|
|
||||||
const list = ref([])
|
|
||||||
const noData = ref(true)
|
|
||||||
const page = ref(1)
|
|
||||||
const pageSize = ref(10)
|
|
||||||
const total = ref(0)
|
|
||||||
const taskPulling = ref(true)
|
|
||||||
const clipboard = ref(null)
|
|
||||||
const pullHandler = ref(null)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
checkSession().then(() => {
|
|
||||||
fetchData(1)
|
|
||||||
// 设置轮询
|
|
||||||
pullHandler.value = setInterval(() => {
|
|
||||||
if (taskPulling.value) {
|
|
||||||
fetchData(1)
|
|
||||||
}
|
|
||||||
}, 5000)
|
|
||||||
})
|
|
||||||
|
|
||||||
clipboard.value = new Clipboard('.copy-prompt')
|
|
||||||
clipboard.value.on('success', () => {
|
|
||||||
ElMessage.success('复制成功!')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
clipboard.value.destroy()
|
|
||||||
if (pullHandler.value) {
|
|
||||||
clearInterval(pullHandler.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const download = (item) => {
|
|
||||||
const url = replaceImg(item.video_url)
|
|
||||||
const downloadURL = `${import.meta.env.VITE_API_HOST}/api/download?url=${url}`
|
|
||||||
const urlObj = new URL(url)
|
|
||||||
const fileName = urlObj.pathname.split('/').pop()
|
|
||||||
item.downloading = true
|
|
||||||
httpDownload(downloadURL)
|
|
||||||
.then((response) => {
|
|
||||||
const blob = new Blob([response.data])
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.href = URL.createObjectURL(blob)
|
|
||||||
link.download = fileName
|
|
||||||
document.body.appendChild(link)
|
|
||||||
link.click()
|
|
||||||
document.body.removeChild(link)
|
|
||||||
URL.revokeObjectURL(link.href)
|
|
||||||
item.downloading = false
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showMessageError('下载失败')
|
|
||||||
item.downloading = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const play = (item) => {
|
|
||||||
currentVideoUrl.value = replaceImg(item.video_url)
|
|
||||||
showDialog.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeJob = (item) => {
|
|
||||||
ElMessageBox.confirm('此操作将会删除任务相关文件,继续操作码?', '删除提示', {
|
|
||||||
confirmButtonText: '确认',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
httpGet('/api/video/remove', { id: item.id })
|
|
||||||
.then(() => {
|
|
||||||
ElMessage.success('任务删除成功')
|
|
||||||
fetchData()
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
ElMessage.error('任务删除失败:' + e.message)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
const publishJob = (item) => {
|
|
||||||
httpGet('/api/video/publish', { id: item.id, publish: item.publish })
|
|
||||||
.then(() => {
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
ElMessage.error('操作失败:' + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const upload = (file) => {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('file', file.file, file.name)
|
|
||||||
showLoading('正在上传文件...')
|
|
||||||
httpPost('/api/upload', formData)
|
|
||||||
.then((res) => {
|
|
||||||
images.value.push(res.data.url)
|
|
||||||
ElMessage.success({ message: '上传成功', duration: 500 })
|
|
||||||
closeLoading()
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
ElMessage.error('图片上传失败:' + e.message)
|
|
||||||
closeLoading()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const remove = (img) => {
|
|
||||||
images.value = images.value.filter((item) => item !== img)
|
|
||||||
}
|
|
||||||
|
|
||||||
const switchReverse = () => {
|
|
||||||
images.value = images.value.reverse()
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchData = (_page) => {
|
|
||||||
if (_page) {
|
|
||||||
page.value = _page
|
|
||||||
}
|
|
||||||
httpGet('/api/video/list', {
|
|
||||||
page: page.value,
|
|
||||||
page_size: pageSize.value,
|
|
||||||
type: 'luma',
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
total.value = res.data.total
|
|
||||||
let needPull = false
|
|
||||||
const items = []
|
|
||||||
for (let v of res.data.items) {
|
|
||||||
if (v.progress === 0 || v.progress === 102) {
|
|
||||||
needPull = true
|
|
||||||
}
|
|
||||||
items.push(v)
|
|
||||||
}
|
|
||||||
loading.value = false
|
|
||||||
taskPulling.value = needPull
|
|
||||||
if (JSON.stringify(list.value) !== JSON.stringify(items)) {
|
|
||||||
list.value = items
|
|
||||||
}
|
|
||||||
noData.value = list.value.length === 0
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
loading.value = false
|
|
||||||
noData.value = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const create = () => {
|
|
||||||
const len = images.value.length
|
|
||||||
if (len) {
|
|
||||||
formData.first_frame_img = replaceImg(images.value[0])
|
|
||||||
if (len === 2) {
|
|
||||||
formData.end_frame_img = replaceImg(images.value[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
httpPost('/api/video/luma/create', formData)
|
|
||||||
.then(() => {
|
|
||||||
fetchData(1)
|
|
||||||
taskPulling.value = true
|
|
||||||
showMessageOK('创建任务成功')
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
showMessageError('创建任务失败:' + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const generatePrompt = () => {
|
|
||||||
if (formData.prompt === '') {
|
|
||||||
return showMessageError('请输入原始提示词')
|
|
||||||
}
|
|
||||||
showLoading('正在生成视频脚本...')
|
|
||||||
httpPost('/api/prompt/video', { prompt: formData.prompt })
|
|
||||||
.then((res) => {
|
|
||||||
formData.prompt = res.data
|
|
||||||
closeLoading()
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
showMessageError('生成提示词失败:' + e.message)
|
|
||||||
closeLoading()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
|
||||||
@import "../assets/css/luma.styl"
|
|
||||||
</style>
|
|
||||||
645
web/src/views/Video.vue
Normal file
645
web/src/views/Video.vue
Normal file
@@ -0,0 +1,645 @@
|
|||||||
|
<template>
|
||||||
|
<div class="page-video">
|
||||||
|
<!-- 左侧参数设置面板 -->
|
||||||
|
<div class="params-panel">
|
||||||
|
<!-- 视频类型切换标签页 -->
|
||||||
|
<el-tabs
|
||||||
|
v-model="store.activeVideoType"
|
||||||
|
@tab-change="store.switchVideoType"
|
||||||
|
class="video-type-tabs"
|
||||||
|
>
|
||||||
|
<!-- Luma 视频参数 -->
|
||||||
|
<el-tab-pane label="Luma视频" name="luma">
|
||||||
|
<div class="params-container">
|
||||||
|
<div class="param-line">
|
||||||
|
<el-input
|
||||||
|
v-model="store.lumaParams.prompt"
|
||||||
|
type="textarea"
|
||||||
|
maxlength="2000"
|
||||||
|
:autosize="{ minRows: 4, maxRows: 6 }"
|
||||||
|
placeholder="请在此输入视频提示词,用逗号分割,您也可以点击下面的提示词助手生成视频提示词"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 提示词生成按钮 -->
|
||||||
|
<div class="param-line pt">
|
||||||
|
<el-button
|
||||||
|
class="generate-btn"
|
||||||
|
@click="store.generatePrompt"
|
||||||
|
:loading="store.isGenerating"
|
||||||
|
size="small"
|
||||||
|
color="#5865f2"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-chuangzuo"></i>
|
||||||
|
生成AI视频提示词
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片辅助生成开关 -->
|
||||||
|
<div class="param-line pt">
|
||||||
|
<div class="image-mode-toggle">
|
||||||
|
<span class="label">使用图片辅助生成</span>
|
||||||
|
<el-switch
|
||||||
|
v-model="store.lumaUseImageMode"
|
||||||
|
@change="store.toggleLumaImageMode"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片上传区域(可折叠) -->
|
||||||
|
<div v-if="store.lumaUseImageMode" class="img-inline">
|
||||||
|
<div class="img-uploader video-img-box mr-2">
|
||||||
|
<el-icon
|
||||||
|
v-if="store.lumaParams.image"
|
||||||
|
@click="store.removeLumaImage('start')"
|
||||||
|
class="removeimg"
|
||||||
|
>
|
||||||
|
<CircleCloseFilled />
|
||||||
|
</el-icon>
|
||||||
|
<el-upload
|
||||||
|
class="uploader img-uploader"
|
||||||
|
:auto-upload="true"
|
||||||
|
:show-file-list="false"
|
||||||
|
:http-request="store.uploadLumaStartImage"
|
||||||
|
accept=".jpg,.png,.jpeg"
|
||||||
|
>
|
||||||
|
<el-image
|
||||||
|
v-if="store.lumaParams.image"
|
||||||
|
:src="store.lumaParams.image"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col" v-else>
|
||||||
|
<el-icon class="mb-1 text-base"><Plus /></el-icon>
|
||||||
|
<span>起始帧</span>
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex items-center h-[120px] cursor-pointer"
|
||||||
|
v-if="store.lumaParams.image && store.lumaParams.image_tail"
|
||||||
|
>
|
||||||
|
<el-tooltip content="交换图片" placement="top">
|
||||||
|
<i class="iconfont icon-exchange" @click="store.switchLumaImages"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="img-uploader video-img-box ml-2">
|
||||||
|
<el-icon
|
||||||
|
v-if="store.lumaParams.image_tail"
|
||||||
|
@click="store.removeLumaImage('end')"
|
||||||
|
class="removeimg"
|
||||||
|
>
|
||||||
|
<CircleCloseFilled />
|
||||||
|
</el-icon>
|
||||||
|
<el-upload
|
||||||
|
class="uploader img-uploader"
|
||||||
|
:auto-upload="true"
|
||||||
|
:show-file-list="false"
|
||||||
|
:http-request="store.uploadLumaEndImage"
|
||||||
|
accept=".jpg,.png,.jpeg"
|
||||||
|
>
|
||||||
|
<el-image
|
||||||
|
v-if="store.lumaParams.image_tail"
|
||||||
|
:src="store.lumaParams.image_tail"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col" v-else>
|
||||||
|
<el-icon class="mb-1 text-base"><Plus /></el-icon>
|
||||||
|
<span>结束帧</span>
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Luma 特有参数设置 -->
|
||||||
|
<div class="item-group">
|
||||||
|
<span class="label">循环参考图</span>
|
||||||
|
<el-switch v-model="store.lumaParams.loop" size="small" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-group">
|
||||||
|
<span class="label">提示词优化</span>
|
||||||
|
<el-switch v-model="store.lumaParams.expand_prompt" size="small" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 算力显示 -->
|
||||||
|
<el-row class="text-info">
|
||||||
|
<el-text type="primary"
|
||||||
|
>当前可用算力:<el-text type="warning">{{ store.availablePower }}</el-text></el-text
|
||||||
|
>
|
||||||
|
</el-row>
|
||||||
|
<!-- 生成按钮 -->
|
||||||
|
<div class="submit-btn">
|
||||||
|
<el-button type="primary" :dark="false" @click="store.createLumaVideo" round>
|
||||||
|
立即生成 ({{ store.lumaPowerCost }}<i class="iconfont icon-vip2"></i>)
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- KeLing 视频参数 -->
|
||||||
|
<el-tab-pane label="可灵视频" name="keling">
|
||||||
|
<div class="params-container">
|
||||||
|
<el-form :model="store.kelingParams" label-width="80px" label-position="left">
|
||||||
|
<!-- 画面比例 -->
|
||||||
|
<div class="param-line">
|
||||||
|
<div class="param-line pt">
|
||||||
|
<span>画面比例:</span>
|
||||||
|
<el-tooltip content="生成画面的尺寸比例" placement="right">
|
||||||
|
<el-icon><InfoFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="param-line pt">
|
||||||
|
<el-row :gutter="10">
|
||||||
|
<el-col :span="8" v-for="item in store.rates" :key="item.value">
|
||||||
|
<div
|
||||||
|
class="flex-col items-center"
|
||||||
|
:class="
|
||||||
|
item.value === store.kelingParams.aspect_ratio
|
||||||
|
? 'grid-content active'
|
||||||
|
: 'grid-content'
|
||||||
|
"
|
||||||
|
@click="store.changeRate(item)"
|
||||||
|
>
|
||||||
|
<el-image class="icon proportion" :src="item.img" fit="cover"></el-image>
|
||||||
|
<div class="texts">{{ item.text }}</div>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 模型选择 -->
|
||||||
|
<div class="param-line">
|
||||||
|
<el-form-item label="模型选择">
|
||||||
|
<el-select
|
||||||
|
v-model="store.kelingParams.model"
|
||||||
|
placeholder="请选择模型"
|
||||||
|
@change="store.updateModelPower"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in store.models"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.text"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 视频时长 -->
|
||||||
|
<div class="param-line">
|
||||||
|
<el-form-item label="视频时长">
|
||||||
|
<el-select
|
||||||
|
v-model="store.kelingParams.duration"
|
||||||
|
placeholder="请选择时长"
|
||||||
|
@change="store.updateModelPower"
|
||||||
|
>
|
||||||
|
<el-option label="5秒" value="5" />
|
||||||
|
<el-option label="10秒" value="10" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 生成模式 -->
|
||||||
|
<div class="param-line">
|
||||||
|
<el-form-item label="生成模式">
|
||||||
|
<el-select
|
||||||
|
v-model="store.kelingParams.mode"
|
||||||
|
placeholder="请选择模式"
|
||||||
|
@change="store.updateModelPower"
|
||||||
|
>
|
||||||
|
<el-option label="标准模式" value="std" />
|
||||||
|
<el-option label="专业模式" value="pro" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 创意程度 -->
|
||||||
|
<div class="param-line">
|
||||||
|
<el-form-item label="创意程度">
|
||||||
|
<el-slider v-model="store.kelingParams.cfg_scale" :min="0" :max="1" :step="0.1" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 运镜控制 -->
|
||||||
|
<div class="param-line" v-if="store.showCameraControl">
|
||||||
|
<div class="param-line pt">
|
||||||
|
<span>运镜控制:</span>
|
||||||
|
<el-tooltip content="生成画面的运镜效果,仅 1.5的高级模式可用" placement="right">
|
||||||
|
<el-icon><InfoFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="param-line">
|
||||||
|
<el-select
|
||||||
|
v-model="store.kelingParams.camera_control.type"
|
||||||
|
placeholder="请选择运镜类型"
|
||||||
|
>
|
||||||
|
<el-option label="请选择" value="" />
|
||||||
|
<el-option label="简单运镜" value="simple" />
|
||||||
|
<el-option label="下移拉远" value="down_back" />
|
||||||
|
<el-option label="推进上移" value="forward_up" />
|
||||||
|
<el-option label="右旋推进" value="right_turn_forward" />
|
||||||
|
<el-option label="左旋推进" value="left_turn_forward" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 仅在simple模式下显示详细配置 -->
|
||||||
|
<div
|
||||||
|
class="camera-control mt-2"
|
||||||
|
v-if="store.kelingParams.camera_control.type === 'simple'"
|
||||||
|
>
|
||||||
|
<el-form-item label="水平移动">
|
||||||
|
<el-slider
|
||||||
|
v-model="store.kelingParams.camera_control.config.horizontal"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="垂直移动">
|
||||||
|
<el-slider
|
||||||
|
v-model="store.kelingParams.camera_control.config.vertical"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="左右旋转">
|
||||||
|
<el-slider
|
||||||
|
v-model="store.kelingParams.camera_control.config.pan"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="上下旋转">
|
||||||
|
<el-slider
|
||||||
|
v-model="store.kelingParams.camera_control.config.tilt"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="横向翻转">
|
||||||
|
<el-slider
|
||||||
|
v-model="store.kelingParams.camera_control.config.roll"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="镜头缩放">
|
||||||
|
<el-slider
|
||||||
|
v-model="store.kelingParams.camera_control.config.zoom"
|
||||||
|
:min="-10"
|
||||||
|
:max="10"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 图片辅助生成开关 -->
|
||||||
|
<div class="param-line pt">
|
||||||
|
<div class="image-mode-toggle">
|
||||||
|
<span class="label">使用图片辅助生成</span>
|
||||||
|
<el-switch
|
||||||
|
v-model="store.kelingUseImageMode"
|
||||||
|
@change="store.toggleKelingImageMode"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片上传区域(可折叠) -->
|
||||||
|
<div v-if="store.kelingUseImageMode" class="img-inline">
|
||||||
|
<div class="img-uploader video-img-box mr-2">
|
||||||
|
<el-icon
|
||||||
|
v-if="store.kelingParams.image"
|
||||||
|
@click="store.removeKelingImage('start')"
|
||||||
|
class="removeimg"
|
||||||
|
>
|
||||||
|
<CircleCloseFilled />
|
||||||
|
</el-icon>
|
||||||
|
<el-upload
|
||||||
|
class="uploader img-uploader"
|
||||||
|
:auto-upload="true"
|
||||||
|
:show-file-list="false"
|
||||||
|
:http-request="store.uploadKelingStartImage"
|
||||||
|
accept=".jpg,.png,.jpeg"
|
||||||
|
>
|
||||||
|
<el-image
|
||||||
|
v-if="store.kelingParams.image"
|
||||||
|
:src="store.kelingParams.image"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col" v-else>
|
||||||
|
<el-icon class="mb-1 text-base"><Plus /></el-icon>
|
||||||
|
<span>起始帧</span>
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center h-[120px] cursor-pointer"
|
||||||
|
v-if="store.kelingParams.image && store.kelingParams.image_tail"
|
||||||
|
>
|
||||||
|
<el-tooltip content="交换图片" placement="top">
|
||||||
|
<i class="iconfont icon-exchange" @click="store.switchKelingImages"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="img-uploader video-img-box ml-2">
|
||||||
|
<el-icon
|
||||||
|
v-if="store.kelingParams.image_tail"
|
||||||
|
@click="store.removeKelingImage('end')"
|
||||||
|
class="removeimg"
|
||||||
|
>
|
||||||
|
<CircleCloseFilled />
|
||||||
|
</el-icon>
|
||||||
|
<el-upload
|
||||||
|
class="uploader img-uploader"
|
||||||
|
:auto-upload="true"
|
||||||
|
:show-file-list="false"
|
||||||
|
:http-request="store.uploadKelingEndImage"
|
||||||
|
accept=".jpg,.png,.jpeg"
|
||||||
|
>
|
||||||
|
<el-image
|
||||||
|
v-if="store.kelingParams.image_tail"
|
||||||
|
:src="store.kelingParams.image_tail"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col" v-else>
|
||||||
|
<el-icon class="mb-1 text-base"><Plus /></el-icon>
|
||||||
|
<span>结束帧</span>
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 提示词输入 -->
|
||||||
|
<div class="param-line pt">
|
||||||
|
<span>提示词:</span>
|
||||||
|
<el-tooltip content="输入你想要的内容,用逗号分割" placement="right">
|
||||||
|
<el-icon><InfoFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="param-line pt">
|
||||||
|
<el-input
|
||||||
|
v-model="store.kelingParams.prompt"
|
||||||
|
type="textarea"
|
||||||
|
maxlength="500"
|
||||||
|
:autosize="{ minRows: 4, maxRows: 6 }"
|
||||||
|
:placeholder="
|
||||||
|
store.kelingUseImageMode
|
||||||
|
? '描述视频画面细节'
|
||||||
|
: '请在此输入视频提示词,您也可以点击下面的提示词助手生成视频提示词'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 提示词生成按钮 -->
|
||||||
|
<div class="param-line pt">
|
||||||
|
<el-button
|
||||||
|
class="generate-btn"
|
||||||
|
@click="store.generatePrompt"
|
||||||
|
:loading="store.isGenerating"
|
||||||
|
size="small"
|
||||||
|
color="#5865f2"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-chuangzuo"></i>
|
||||||
|
生成专业视频提示词
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 排除内容 -->
|
||||||
|
<div class="param-line pt">
|
||||||
|
<span>不希望出现的内容:(可选)</span>
|
||||||
|
<el-tooltip content="不想出现在图片上的元素(例如:树,建筑)" placement="right">
|
||||||
|
<el-icon><InfoFilled /></el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="param-line pt">
|
||||||
|
<el-input
|
||||||
|
v-model="store.kelingParams.negative_prompt"
|
||||||
|
type="textarea"
|
||||||
|
:autosize="{ minRows: 4, maxRows: 6 }"
|
||||||
|
placeholder="请在此输入你不希望出现在视频上的内容"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 算力显示 -->
|
||||||
|
<el-row class="text-info">
|
||||||
|
<el-text type="primary"
|
||||||
|
>当前可用算力:<el-text type="warning">{{ store.availablePower }}</el-text></el-text
|
||||||
|
>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 生成按钮 -->
|
||||||
|
<div class="submit-btn">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:dark="false"
|
||||||
|
@click="store.createKelingVideo"
|
||||||
|
round
|
||||||
|
:loading="store.generating"
|
||||||
|
>
|
||||||
|
立即生成 ({{ store.kelingPowerCost }}<i class="iconfont icon-vip2"></i>)
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧任务列表 -->
|
||||||
|
<div
|
||||||
|
class="main-content"
|
||||||
|
v-loading="store.loading"
|
||||||
|
element-loading-background="rgba(100,100,100,0.3)"
|
||||||
|
>
|
||||||
|
<div class="works-header">
|
||||||
|
<h2 class="h-title text-2xl">你的作品</h2>
|
||||||
|
<div class="filter-buttons">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button
|
||||||
|
:type="store.taskFilter === 'all' ? 'primary' : 'default'"
|
||||||
|
@click="store.switchTaskFilter('all')"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
:type="store.taskFilter === 'luma' ? 'primary' : 'default'"
|
||||||
|
@click="store.switchTaskFilter('luma')"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Luma
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
:type="store.taskFilter === 'keling' ? 'primary' : 'default'"
|
||||||
|
@click="store.switchTaskFilter('keling')"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
可灵
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="video-list">
|
||||||
|
<div class="list-box" v-if="!store.noData">
|
||||||
|
<div v-for="item in store.currentList" :key="item.id">
|
||||||
|
<div class="item">
|
||||||
|
<div class="left">
|
||||||
|
<div class="container">
|
||||||
|
<div v-if="item.progress === 100">
|
||||||
|
<video
|
||||||
|
class="video"
|
||||||
|
:src="store.replaceImg(item.video_url)"
|
||||||
|
preload="auto"
|
||||||
|
loop="loop"
|
||||||
|
muted="muted"
|
||||||
|
>
|
||||||
|
您的浏览器不支持视频播放
|
||||||
|
</video>
|
||||||
|
<button
|
||||||
|
class="play flex justify-center items-center"
|
||||||
|
@click="store.playVideo(item)"
|
||||||
|
>
|
||||||
|
<img src="/images/play.svg" alt="" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<el-image
|
||||||
|
:src="item.cover_url"
|
||||||
|
class="border rounded-lg"
|
||||||
|
fit="cover"
|
||||||
|
v-else-if="item.progress === 101"
|
||||||
|
/>
|
||||||
|
<generating message="正在生成视频" v-else />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<div class="pb-2" v-if="item.raw_data">
|
||||||
|
<el-tag class="mr-1">{{
|
||||||
|
item.raw_data.task_type || store.activeVideoType
|
||||||
|
}}</el-tag>
|
||||||
|
<el-tag class="mr-1" v-if="item.raw_data.model">{{ item.raw_data.model }}</el-tag>
|
||||||
|
<el-tag class="mr-1" v-if="item.raw_data.duration"
|
||||||
|
>{{ item.raw_data.duration }}秒</el-tag
|
||||||
|
>
|
||||||
|
<el-tag class="mr-1" v-if="item.raw_data.mode">{{ item.raw_data.mode }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="failed" v-if="item.progress === 101">
|
||||||
|
任务执行失败:{{ item.err_msg }},任务提示词:{{ item.prompt }}
|
||||||
|
</div>
|
||||||
|
<div class="prompt" v-else>
|
||||||
|
{{ store.substr(item.prompt, 1000) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right" v-if="item.progress === 100">
|
||||||
|
<div class="tools">
|
||||||
|
<el-tooltip content="复制提示词" placement="top">
|
||||||
|
<button class="btn btn-icon copy-prompt" :data-clipboard-text="item.prompt">
|
||||||
|
<i class="iconfont icon-copy"></i>
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="下载视频" placement="top">
|
||||||
|
<button
|
||||||
|
class="btn btn-icon"
|
||||||
|
@click="store.downloadVideo(item)"
|
||||||
|
:disabled="item.downloading"
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-download" v-if="!item.downloading"></i>
|
||||||
|
<el-image src="/images/loading.gif" class="downloading" fit="cover" v-else />
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="删除" placement="top">
|
||||||
|
<button class="btn btn-icon" @click="store.removeJob(item)">
|
||||||
|
<i class="iconfont icon-remove"></i>
|
||||||
|
</button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right-error" v-else>
|
||||||
|
<el-button type="danger" @click="store.removeJob(item)" circle>
|
||||||
|
<i class="iconfont icon-remove"></i>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-empty
|
||||||
|
:image-size="100"
|
||||||
|
:image="store.nodata"
|
||||||
|
description="没有任何作品,赶紧去创作吧!"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-if="store.total > store.pageSize"
|
||||||
|
background
|
||||||
|
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||||
|
layout="total,prev, pager, next"
|
||||||
|
:hide-on-single-page="true"
|
||||||
|
:current-page="store.page"
|
||||||
|
:page-size="store.pageSize"
|
||||||
|
@current-change="store.fetchData"
|
||||||
|
:total="store.total"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 视频预览对话框 -->
|
||||||
|
<black-dialog
|
||||||
|
:show="store.showDialog"
|
||||||
|
title="预览视频"
|
||||||
|
hide-footer
|
||||||
|
@cancal="store.showDialog = false"
|
||||||
|
@update:show="store.showDialog = $event"
|
||||||
|
width="auto"
|
||||||
|
>
|
||||||
|
<video
|
||||||
|
style="max-width: 90vw; max-height: 90vh"
|
||||||
|
:src="store.currentVideoUrl"
|
||||||
|
preload="auto"
|
||||||
|
:autoplay="true"
|
||||||
|
loop="loop"
|
||||||
|
muted="muted"
|
||||||
|
v-show="store.showDialog"
|
||||||
|
>
|
||||||
|
您的浏览器不支持视频播放
|
||||||
|
</video>
|
||||||
|
</black-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import BlackDialog from '@/components/ui/BlackDialog.vue'
|
||||||
|
import Generating from '@/components/ui/Generating.vue'
|
||||||
|
import { useVideoStore } from '@/store/video'
|
||||||
|
import { CircleCloseFilled, InfoFilled, Plus } from '@element-plus/icons-vue'
|
||||||
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
const store = useVideoStore()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
store.init()
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
store.cleanup()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
@import "../assets/css/video.styl"
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user