refactor: use waterflow component in mj, sd and dall image drawing page

This commit is contained in:
RockYang 2024-05-13 19:04:00 +08:00
parent cc80b87fee
commit 3e3d3e162e
19 changed files with 1118 additions and 759 deletions

View File

@ -8,6 +8,8 @@
* Bug修复修复管理后台角色删除失败的Bug * Bug修复修复管理后台角色删除失败的Bug
* Bug修复兼容最新版秋叶SD懒人包的 SD API新增 scheduler 参数 * Bug修复兼容最新版秋叶SD懒人包的 SD API新增 scheduler 参数
* 功能优化:支持在管理后台配置 AI 绘图相关配置,包括 SD, MJ-PLUS, MJ-PROXY * 功能优化:支持在管理后台配置 AI 绘图相关配置,包括 SD, MJ-PLUS, MJ-PROXY
* Bug修复修复注册用户提示注册人数达到上限的 Bug
* 功能优化将MJ,SD,Dall绘画页面的任务列表全改成瀑布流组件
## v4.0.5 ## v4.0.5

View File

@ -8,6 +8,8 @@ package dalle
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import ( import (
"errors"
"fmt"
"geekai/core/types" "geekai/core/types"
logger2 "geekai/logger" logger2 "geekai/logger"
"geekai/service" "geekai/service"
@ -16,8 +18,6 @@ import (
"geekai/store" "geekai/store"
"geekai/store/model" "geekai/store/model"
"geekai/utils" "geekai/utils"
"errors"
"fmt"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"time" "time"
@ -261,7 +261,7 @@ func (s *Service) downloadImage(jobId uint, userId int, orgURL string) (string,
if res.Error != nil { if res.Error != nil {
return "", err return "", err
} }
s.notifyQueue.RPush(sd.NotifyMessage{UserId: userId, JobId: int(jobId), Message: sd.Failed}) s.notifyQueue.RPush(sd.NotifyMessage{UserId: userId, JobId: int(jobId), Message: sd.Finished})
return imgURL, nil return imgURL, nil
} }
@ -294,7 +294,7 @@ func (s *Service) CheckTaskStatus() {
Balance: user.Power + job.Power, Balance: user.Power + job.Power,
Mark: types.PowerAdd, Mark: types.PowerAdd,
Model: "dall-e-3", Model: "dall-e-3",
Remark: fmt.Sprintf("任务失败退回算力。任务ID%d", job.Id), Remark: fmt.Sprintf("任务失败退回算力。任务ID%d", job.Id),
CreatedAt: time.Now(), CreatedAt: time.Now(),
}) })
} }

View File

@ -31,7 +31,7 @@ type ServicePool struct {
db *gorm.DB db *gorm.DB
uploaderManager *oss.UploaderManager uploaderManager *oss.UploaderManager
Clients *types.LMap[uint, *types.WsClient] // UserId => Client Clients *types.LMap[uint, *types.WsClient] // UserId => Client
licenseService *service.LicenseService licenseService *service.LicenseService
} }
var logger = logger2.GetLogger() var logger = logger2.GetLogger()
@ -56,6 +56,7 @@ func (p *ServicePool) InitServices(plusConfigs []types.MjPlusConfig, proxyConfig
for _, s := range p.services { for _, s := range p.services {
s.Stop() s.Stop()
} }
p.services = make([]*Service, 0)
for k, config := range plusConfigs { for k, config := range plusConfigs {
if config.Enabled == false { if config.Enabled == false {

View File

@ -28,7 +28,7 @@ type Service struct {
taskQueue *store.RedisQueue taskQueue *store.RedisQueue
notifyQueue *store.RedisQueue notifyQueue *store.RedisQueue
db *gorm.DB db *gorm.DB
running bool running bool
} }
func NewService(name string, taskQueue *store.RedisQueue, notifyQueue *store.RedisQueue, db *gorm.DB, cli Client) *Service { func NewService(name string, taskQueue *store.RedisQueue, notifyQueue *store.RedisQueue, db *gorm.DB, cli Client) *Service {
@ -38,7 +38,7 @@ func NewService(name string, taskQueue *store.RedisQueue, notifyQueue *store.Red
taskQueue: taskQueue, taskQueue: taskQueue,
notifyQueue: notifyQueue, notifyQueue: notifyQueue,
Client: cli, Client: cli,
running: true, running: true,
} }
} }
@ -129,7 +129,6 @@ func (s *Service) Run() {
func (s *Service) Stop() { func (s *Service) Stop() {
s.running = false s.running = false
s.Client = nil
} }
type CBReq struct { type CBReq struct {

View File

@ -25,8 +25,8 @@ type ServicePool struct {
notifyQueue *store.RedisQueue notifyQueue *store.RedisQueue
db *gorm.DB db *gorm.DB
Clients *types.LMap[uint, *types.WsClient] // UserId => Client Clients *types.LMap[uint, *types.WsClient] // UserId => Client
uploader *oss.UploaderManager uploader *oss.UploaderManager
levelDB *store.LevelDB levelDB *store.LevelDB
} }
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, levelDB *store.LevelDB) *ServicePool { func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, levelDB *store.LevelDB) *ServicePool {
@ -40,8 +40,8 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
services: services, services: services,
db: db, db: db,
Clients: types.NewLMap[uint, *types.WsClient](), Clients: types.NewLMap[uint, *types.WsClient](),
uploader: manager, uploader: manager,
levelDB: levelDB, levelDB: levelDB,
} }
} }
@ -50,6 +50,7 @@ func (p *ServicePool) InitServices(configs []types.StableDiffusionConfig) {
for _, s := range p.services { for _, s := range p.services {
s.Stop() s.Stop()
} }
p.services = make([]*Service, 0)
for k, config := range configs { for k, config := range configs {
if config.Enabled == false { if config.Enabled == false {

View File

@ -60,7 +60,7 @@ func (s *Service) Run() {
logger.Errorf("taking task with error: %v", err) logger.Errorf("taking task with error: %v", err)
continue continue
} }
logger.Infof("%s handle a new Stable-Diffusion task: %+v", s.name, task)
// translate prompt // translate prompt
if utils.HasChinese(task.Params.Prompt) { if utils.HasChinese(task.Params.Prompt) {
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.RewritePromptTemplate, task.Params.Prompt)) content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.RewritePromptTemplate, task.Params.Prompt))

View File

@ -33,7 +33,7 @@
"qs": "^6.11.1", "qs": "^6.11.1",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"three": "^0.128.0", "three": "^0.128.0",
"v3-waterfall": "^1.2.1", "v3-waterfall": "^1.3.3",
"vant": "^4.5.0", "vant": "^4.5.0",
"vue": "^3.2.13", "vue": "^3.2.13",
"vue-router": "^4.0.15" "vue-router": "^4.0.15"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -81,7 +81,281 @@
} }
} }
@import "task-list.styl" .task-list-box {
width 100%
padding 10px
color #ffffff
overflow-x hidden
.task-list-inner {
.el-tabs {
--el-tabs-header-height: 55px;
}
.el-tabs__item {
color: #fff;
font-size: 18px;
}
.title-tabs .el-tabs__item.is-active {
color: #47FFF1;
font-size: 18px;
}
.title-tabs .el-tabs__active-bar {
background-color: #47FFF1;
}
.el-textarea {
--el-input-focus-border-color: #47FFF1;
}
.el-textarea__inner {
background: transparent;
color: #fff;
}
.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 #ffffff
}
//
.img-inline {
display flex
.img-uploader {
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width 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
}
}
//
.job-list-box {
@import "running-job-list.styl"
.finish-job-list {
#waterfall {
display flex
justify-content center
padding-top 20px
flex-flow column
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover {
.remove {
display block
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
}
.el-image {
width 100%
height 100%
overflow visible
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
min-height 200px
color #ffffff
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
img {
height 310px
}
.image-slot {
height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
}
.no-more-data {
text-align center
padding 30px
}
}
} }
} }

View File

@ -233,7 +233,280 @@
} }
} }
@import "task-list.styl" .task-list-box {
width 100%
padding 10px
color #ffffff
overflow-x hidden
.task-list-inner {
.el-tabs {
--el-tabs-header-height: 55px;
}
.el-tabs__item {
color: #fff;
font-size: 18px;
}
.title-tabs .el-tabs__item.is-active {
color: #47FFF1;
font-size: 18px;
}
.title-tabs .el-tabs__active-bar {
background-color: #47FFF1;
}
.el-textarea {
--el-input-focus-border-color: #47FFF1;
}
.el-textarea__inner {
background: transparent;
color: #fff;
}
.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 #ffffff
}
//
.img-inline {
display flex
.img-uploader {
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width 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
}
}
.job-list-box {
//
@import "running-job-list.styl"
.finish-job-list {
#waterfall {
display flex
justify-content center
padding-top 20px
flex-flow column
.waterfall-item {
overflow visible
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
border-radius 6px
//position relative
.el-image {
overflow auto
}
.opt {
padding-top 5px
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover {
.remove {
display block
}
}
}
}
}
}
.el-image {
width 100%
height 100%
overflow visible
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
min-height 200px
color #ffffff
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
img {
//height 310px
}
.image-slot {
min-height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
}
.no-more-data {
text-align center
padding 30px
}
}
} }
} }

View File

@ -81,7 +81,281 @@
} }
} }
@import "task-list.styl" .task-list-box {
width 100%
padding 10px
color #ffffff
overflow-x hidden
.task-list-inner {
.el-tabs {
--el-tabs-header-height: 55px;
}
.el-tabs__item {
color: #fff;
font-size: 18px;
}
.title-tabs .el-tabs__item.is-active {
color: #47FFF1;
font-size: 18px;
}
.title-tabs .el-tabs__active-bar {
background-color: #47FFF1;
}
.el-textarea {
--el-input-focus-border-color: #47FFF1;
}
.el-textarea__inner {
background: transparent;
color: #fff;
}
.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 #ffffff
}
//
.img-inline {
display flex
.img-uploader {
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width 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
}
}
//
.job-list-box {
@import "running-job-list.styl"
.finish-job-list {
#waterfall {
display flex
justify-content center
padding-top 20px
flex-flow column
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover {
.remove {
display block
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
}
.el-image {
width 100%
height 100%
overflow visible
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
min-height 200px
color #ffffff
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
img {
height 310px
}
.image-slot {
height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
}
.no-more-data {
text-align center
padding 30px
}
}
} }
@import "sd-task-dialog.styl" @import "sd-task-dialog.styl"

View File

@ -46,6 +46,10 @@
overflow-y auto overflow-y auto
overflow-x hidden overflow-x hidden
.waterfall-over-message {
display none
}
.list-item { .list-item {
.image { .image {

View File

@ -0,0 +1,39 @@
.running-job-list {
.running-job-box {
width 100%
display flex
flex-flow row
}
.job-item {
margin-right 10px
width 200px
height 200px
overflow hidden
padding 2px
background-color #555555
.job-item-inner {
position relative
height 100%
overflow hidden
.progress {
position absolute
width 100%
height 100%
top 0
left 0
display flex
justify-content center
align-items center
span {
font-size 20px
color #ffffff
}
}
}
}
}

View File

@ -1,215 +0,0 @@
.task-list-box {
width: 100%;
padding: 10px;
color: #fff;
overflow-x: hidden;
}
.task-list-box .task-list-inner .el-tabs {
--el-tabs-header-height: 55px;
}
.task-list-box .task-list-inner .el-tabs__item {
color: #fff;
font-size: 18px;
}
.task-list-box .task-list-inner .title-tabs .el-tabs__item.is-active {
color: #47fff1;
font-size: 18px;
}
.task-list-box .task-list-inner .title-tabs .el-tabs__active-bar {
background-color: #47fff1;
}
.task-list-box .task-list-inner .el-textarea {
--el-input-focus-border-color: #47fff1;
}
.task-list-box .task-list-inner .el-textarea__inner {
background: transparent;
color: #fff;
}
.task-list-box .task-list-inner .el-input__wrapper {
background: transparent;
padding: 5px;
}
.task-list-box .task-list-inner .text {
margin-bottom: 10px;
color: #6b778c;
font-size: 15px;
}
.task-list-box .task-list-inner .param-line.pt {
padding-top: 5px;
padding-bottom: 5px;
}
.task-list-box .task-list-inner .form-item-inner {
display: flex;
align-items: center;
}
.task-list-box .task-list-inner .form-item-inner .el-icon {
margin-left: 10px;
}
.task-list-box .task-list-inner .el-form-item__label {
color: #fff;
}
.task-list-box .task-list-inner .img-inline {
display: flex;
}
.task-list-box .task-list-inner .img-inline .img-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width: 120px;
transition: var(--el-transition-duration-fast);
margin-bottom: 20px;
}
.task-list-box .task-list-inner .img-inline .img-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.task-list-box .task-list-inner .img-inline .img-uploader .el-upload .el-icon.uploader-icon {
font-size: 28px;
color: #8c939d;
width: 100%;
height: 120px;
text-align: center;
}
.task-list-box .task-list-inner .img-inline .img-list-box {
display: flex;
}
.task-list-box .task-list-inner .img-inline .img-list-box .img-item {
width: 120px;
position: relative;
margin-right: 10px;
}
.task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-image {
width: 120px;
height: 120px;
border-radius: 5px;
}
.task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-button {
position: absolute;
right: 5px;
top: 5px;
width: 20px;
height: 20px;
}
.task-list-box .task-list-inner .submit-btn {
display: flex;
margin: 20px 0;
}
.task-list-box .task-list-inner .submit-btn .el-button {
width: 200px;
}
.task-list-box .task-list-inner .submit-btn .text-info {
width: 100%;
display: flex;
justify-content: right;
align-items: center;
}
.task-list-box .task-list-inner .job-list-box .running-job-list .job-item {
width: 100%;
padding: 2px;
background-color: #555;
}
.task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner {
position: relative;
height: 100%;
overflow: hidden;
}
.task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
}
.task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress span {
font-size: 20px;
color: #fff;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item {
width: 100%;
height: 100%;
border: 1px solid #666;
padding: 6px;
overflow: hidden;
border-radius: 6px;
transition: all 0.3s ease; /* 添加过渡效果 */
position: relative;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line {
margin: 6px 0;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul {
display: flex;
flex-flow: row;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li {
margin-right: 6px;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a {
padding: 3px 0;
width: 40px;
text-align: center;
border-radius: 5px;
display: block;
cursor: pointer;
background-color: #4e5058;
color: #fff;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
background-color: #6d6f78;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
font-size: 20px;
cursor: pointer;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .remove {
display: none;
position: absolute;
right: 10px;
top: 10px;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item:hover .remove {
display: block;
}
.task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover {
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
transform: translateY(-10px); /* 向上移动10像素 */
}
.task-list-box .task-list-inner .job-list-box .el-image {
width: 100%;
height: 100%;
overflow: visible;
}
.task-list-box .task-list-inner .job-list-box .el-image img {
height: 240px;
}
.task-list-box .task-list-inner .job-list-box .el-image .el-image-viewer__wrapper img {
width: auto;
height: auto;
}
.task-list-box .task-list-inner .job-list-box .el-image .image-slot {
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
min-height: 200px;
color: #fff;
height: 240px;
}
.task-list-box .task-list-inner .job-list-box .el-image .image-slot .iconfont {
font-size: 50px;
margin-bottom: 10px;
}
.task-list-box .task-list-inner .job-list-box .el-image.upscale {
max-height: 310px;
}
.task-list-box .task-list-inner .job-list-box .el-image.upscale img {
height: 310px;
}
.task-list-box .task-list-inner .job-list-box .el-image.upscale .el-image-viewer__wrapper img {
width: auto;
height: auto;
}

View File

@ -1,302 +0,0 @@
.task-list-box {
width 100%
padding 10px
color #ffffff
overflow-x hidden
.task-list-inner {
.el-tabs {
--el-tabs-header-height: 55px;
}
.el-tabs__item {
color: #fff;
font-size: 18px;
}
.title-tabs .el-tabs__item.is-active {
color: #47FFF1;
font-size: 18px;
}
.title-tabs .el-tabs__active-bar {
background-color: #47FFF1;
}
.el-textarea {
--el-input-focus-border-color: #47FFF1;
}
.el-textarea__inner {
background: transparent;
color: #fff;
}
.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 #ffffff
}
//
.img-inline {
display flex
.img-uploader {
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
width 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
}
}
//
.job-list-box {
.running-job-list {
.job-item {
//border: 1px solid #454545;
width: 100%;
padding 2px
background-color #555555
.job-item-inner {
position relative
height 100%
overflow hidden
.progress {
position absolute
width 100%
height 100%
top 0
left 0
display flex
justify-content center
align-items center
span {
font-size 20px
color #ffffff
}
}
}
}
}
.finish-job-list {
.job-item {
width 100%
height 100%
border 1px solid #666666
padding 6px
overflow hidden
border-radius 6px
transition: all 0.3s ease; /* */
position relative
.opt {
.opt-line {
margin 6px 0
ul {
display flex
flex-flow row
li {
margin-right 6px
a {
padding 3px 0
width 40px
text-align center
border-radius 5px
display block
cursor pointer
background-color #4E5058
color #ffffff
&:hover {
background-color #6D6F78
}
}
}
.show-prompt {
font-size 20px
cursor pointer
}
}
}
}
.remove {
display none
position absolute
right 10px
top 10px
}
&:hover {
.remove {
display block
}
}
}
.animate {
&:hover {
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* */
transform: translateY(-10px); /* 10 */
}
}
}
.el-image {
width 100%
height 100%
overflow visible
img {
height 240px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
.image-slot {
display flex
flex-flow column
justify-content center
align-items center
min-height 200px
color #ffffff
height 240px
.iconfont {
font-size 50px
margin-bottom 10px
}
}
}
.el-image.upscale {
img {
height 310px
}
.image-slot {
height 310px
}
.el-image-viewer__wrapper {
img {
width auto
height auto
}
}
}
}
}
.no-more-data {
text-align center
padding 20px
}
}

View File

@ -82,39 +82,70 @@
</el-button> </el-button>
</div> </div>
</div> </div>
<div class="task-list-box" @scrollend="handleScrollEnd"> <div class="task-list-box">
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }"> <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
<div class="job-list-box"> <div class="job-list-box">
<h2>任务列表</h2> <h2>任务列表</h2>
<div class="running-job-list"> <div class="running-job-list">
<ItemList :items="runningJobs" v-if="runningJobs.length > 0" :width="240"> <div class="running-job-box" v-if="runningJobs.length > 0">
<template #default> <div class="job-item" v-for="item in runningJobs">
<div class="job-item"> <div v-if="item.progress > 0" class="job-item-inner">
<el-image fit="cover"> <el-image :src="item['img_url']" fit="cover" loading="lazy">
<template #placeholder>
<div class="image-slot">
正在加载图片
</div>
</template>
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
<i class="iconfont icon-quick-start"></i> <el-icon>
<span>任务正在排队中</span> <Picture/>
</el-icon>
</div> </div>
</template> </template>
</el-image> </el-image>
<div class="progress">
<el-progress type="circle" :percentage="item.progress" :width="100"
color="#47fff1"/>
</div>
</div> </div>
</template> <el-image fit="cover" v-else>
</ItemList> <template #error>
<div class="image-slot">
<i class="iconfont icon-quick-start"></i>
<span>任务正在排队中</span>
</div>
</template>
</el-image>
</div>
</div>
<el-empty :image-size="100" v-else/> <el-empty :image-size="100" v-else/>
</div> </div>
<h2>创作记录</h2> <h2>创作记录</h2>
<div class="finish-job-list"> <div class="finish-job-list">
<div v-if="finishedJobs.length > 0"> <div v-if="finishedJobs.length > 0">
<ItemList :items="finishedJobs" :width="240" :gap="16"> <v3-waterfall
<template #default="scope"> id="waterfall"
:list="finishedJobs"
srcKey="img_thumb"
:gap="20"
:bottomGap="-10"
:colWidth="colWidth"
:distanceToScroll="100"
:isLoading="loading"
:isOver="isOver"
@scrollReachBottom="fetchFinishJobs()">
<template #default="slotProp">
<div class="job-item"> <div class="job-item">
<el-image v-if="scope.item['img_url']" <el-image
:src="scope.item['img_url']+'?imageView2/1/w/240/h/240/q/75'" v-if="slotProp.item.img_url !== ''"
fit="cover" @click="previewImg(slotProp.item)"
:preview-src-list="[scope.item['img_url']]" :src="slotProp.item['img_thumb']"
loading="lazy"> fit="cover"
loading="lazy">
<template #placeholder> <template #placeholder>
<div class="image-slot"> <div class="image-slot">
正在加载图片 正在加载图片
@ -130,18 +161,12 @@
</template> </template>
</el-image> </el-image>
<el-image v-else <el-image v-else>
:src="scope.item['org_url']"
fit="cover"
:preview-src-list="[scope.item['org_url']]"
loading="lazy">
<template #placeholder>
<div class="image-slot">
正在加载图片
</div>
</template>
<template #error> <template #error>
<div class="image-slot">
<i class="iconfont icon-loading"></i>
<span>正在下载图片</span>
</div>
<div class="image-slot"> <div class="image-slot">
<el-icon> <el-icon>
<Picture/> <Picture/>
@ -152,35 +177,39 @@
<div class="remove"> <div class="remove">
<el-tooltip content="删除" placement="top" effect="light"> <el-tooltip content="删除" placement="top" effect="light">
<el-button type="danger" :icon="Delete" @click="removeImage($event,scope.item)" circle/> <el-button type="danger" :icon="Delete" @click="removeImage($event,slotProp.item)" circle/>
</el-tooltip> </el-tooltip>
<el-tooltip content="分享" placement="top" effect="light" v-if="scope.item.publish"> <el-tooltip content="分享" placement="top" effect="light" v-if="slotProp.item.publish">
<el-button type="warning" <el-button type="warning"
@click="publishImage($event,scope.item, false)" @click="publishImage($event,slotProp.item, false)"
circle> circle>
<i class="iconfont icon-cancel-share"></i> <i class="iconfont icon-cancel-share"></i>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="取消分享" placement="top" effect="light" v-else> <el-tooltip content="取消分享" placement="top" effect="light" v-else>
<el-button type="success" @click="publishImage($event,scope.item, true)" circle> <el-button type="success" @click="publishImage($event,slotProp.item, true)" circle>
<i class="iconfont icon-share-bold"></i> <i class="iconfont icon-share-bold"></i>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="复制提示词" placement="top" effect="light"> <el-tooltip content="复制提示词" placement="top" effect="light">
<el-button type="info" circle class="copy-prompt" :data-clipboard-text="scope.item.prompt"> <el-button type="info" circle class="copy-prompt"
:data-clipboard-text="slotProp.item.prompt">
<i class="iconfont icon-file"></i> <i class="iconfont icon-file"></i>
</el-button> </el-button>
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
</template> </template>
</ItemList>
<div class="no-more-data" v-if="isOver"> <template #footer>
<span>没有更多数据了</span> <div class="no-more-data">
<i class="iconfont icon-face"></i> <span>没有更多数据了</span>
</div> <i class="iconfont icon-face"></i>
</div>
</template>
</v3-waterfall>
</div> </div>
<el-empty :image-size="100" v-else/> <el-empty :image-size="100" v-else/>
</div> <!-- end finish job list--> </div> <!-- end finish job list-->
@ -193,15 +222,15 @@
</div> </div>
<login-dialog :show="showLoginDialog" @hide="showLoginDialog = false" @success="initData"/> <login-dialog :show="showLoginDialog" @hide="showLoginDialog = false" @success="initData"/>
<el-image-viewer @close="() => { previewURL = '' }" v-if="previewURL !== ''" :url-list="[previewURL]"/>
</div> </div>
</template> </template>
<script setup> <script setup>
import {onMounted, onUnmounted, ref} from "vue" import {onMounted, onUnmounted, ref} from "vue"
import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue"; import {Delete, InfoFilled} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage, ElMessageBox, ElNotification} from "element-plus"; import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
import ItemList from "@/components/ItemList.vue";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import {checkSession} from "@/action/session"; import {checkSession} from "@/action/session";
import LoginDialog from "@/components/LoginDialog.vue"; import LoginDialog from "@/components/LoginDialog.vue";
@ -210,6 +239,10 @@ const listBoxHeight = ref(window.innerHeight - 40)
const paramBoxHeight = ref(window.innerHeight - 150) const paramBoxHeight = ref(window.innerHeight - 150)
const showLoginDialog = ref(false) const showLoginDialog = ref(false)
const isLogin = ref(false) const isLogin = ref(false)
const loading = ref(true)
const colWidth = ref(240)
const isOver = ref(false)
const previewURL = ref("")
window.onresize = () => { window.onresize = () => {
listBoxHeight.value = window.innerHeight - 40 listBoxHeight.value = window.innerHeight - 40
@ -268,21 +301,15 @@ const initData = () => {
power.value = user['power'] power.value = user['power']
userId.value = user.id userId.value = user.id
isLogin.value = true isLogin.value = true
page.value = 0
fetchRunningJobs() fetchRunningJobs()
fetchFinishJobs(1) fetchFinishJobs()
connect() connect()
}).catch(() => { }).catch(() => {
}); });
} }
const handleScrollEnd = () => {
if (isOver.value === true) {
return
}
page.value += 1
fetchFinishJobs(page.value)
}
const socket = ref(null) const socket = ref(null)
const heartbeatHandle = ref(null) const heartbeatHandle = ref(null)
const connect = () => { const connect = () => {
@ -323,7 +350,7 @@ const connect = () => {
reader.onload = () => { reader.onload = () => {
const message = String(reader.result) const message = String(reader.result)
if (message === "FINISH") { if (message === "FINISH") {
page.value = 1 page.value = 0
fetchFinishJobs(page.value) fetchFinishJobs(page.value)
isOver.value = false isOver.value = false
} }
@ -368,21 +395,30 @@ const fetchRunningJobs = () => {
const page = ref(1) const page = ref(1)
const pageSize = ref(15) const pageSize = ref(15)
const isOver = ref(false)
// //
const fetchFinishJobs = (page) => { const fetchFinishJobs = () => {
if (!isLogin.value) { if (!isLogin.value) {
return return
} }
httpGet(`/api/dall/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => {
loading.value = true
page.value = page.value + 1
httpGet(`/api/dall/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
if (res.data.length < pageSize.value) { if (res.data.length < pageSize.value) {
isOver.value = true isOver.value = true
} }
if (page === 1) { const imageList = res.data
finishedJobs.value = res.data for (let i = 0; i < imageList.length; i++) {
} else { imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
finishedJobs.value = finishedJobs.value.concat(res.data)
} }
if (page.value === 1) {
finishedJobs.value = imageList
} else {
finishedJobs.value = finishedJobs.value.concat(imageList)
}
loading.value = false
}).catch(e => { }).catch(e => {
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)
}) })
@ -430,6 +466,10 @@ const removeImage = (event, item) => {
}) })
} }
const previewImg = (item) => {
previewURL.value = item.img_url
}
// //
const publishImage = (event, item, action) => { const publishImage = (event, item, action) => {
event.stopPropagation() event.stopPropagation()

View File

@ -451,70 +451,78 @@
<div class="job-list-box"> <div class="job-list-box">
<h2>任务列表</h2> <h2>任务列表</h2>
<div class="running-job-list"> <div class="running-job-list">
<ItemList :items="runningJobs" v-if="runningJobs.length > 0"> <div class="running-job-box" v-if="runningJobs.length > 0">
<template #default="scope"> <div class="job-item" v-for="item in runningJobs">
<div class="job-item"> <div v-if="item.progress > 0" class="job-item-inner">
<div v-if="scope.item.progress > 0" class="job-item-inner"> <el-image :src="item['img_url']" fit="cover" loading="lazy">
<el-image :src="scope.item['img_url']" :zoom-rate="1.2" <template #placeholder>
:preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="0" <div class="image-slot">
loading="lazy"> 正在加载图片
<template #placeholder> </div>
<div class="image-slot"> </template>
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot">
<el-icon>
<Picture/>
</el-icon>
</div>
</template>
</el-image>
<div class="progress">
<el-progress type="circle" :percentage="scope.item.progress" :width="100"
color="#47fff1"/>
</div>
</div>
<el-image fit="cover" v-else>
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
<i class="iconfont icon-quick-start"></i> <el-icon>
<span>任务正在排队中</span> <Picture/>
</el-icon>
</div> </div>
</template> </template>
</el-image> </el-image>
<div class="progress">
<el-progress type="circle" :percentage="item.progress" :width="100"
color="#47fff1"/>
</div>
</div> </div>
</template> <el-image fit="cover" v-else>
</ItemList> <template #error>
<div class="image-slot">
<i class="iconfont icon-quick-start"></i>
<span>任务正在排队中</span>
</div>
</template>
</el-image>
</div>
</div>
<el-empty :image-size="100" v-else/> <el-empty :image-size="100" v-else/>
</div> </div>
<h2>创作记录</h2> <h2>创作记录</h2>
<div class="finish-job-list"> <div class="finish-job-list">
<div v-if="finishedJobs.length > 0"> <div v-if="finishedJobs.length > 0">
<ItemList :items="finishedJobs" :width="240" :gap="16"> <v3-waterfall
<template #default="scope"> id="waterfall"
:list="finishedJobs"
srcKey="thumb_url"
:gap="20"
:bottomGap="-8"
:colWidth="colWidth"
:distanceToScroll="100"
:isLoading="loading"
:isOver="isOver"
@scrollReachBottom="fetchFinishJobs()">
<template #default="slotProp">
<div class="job-item"> <div class="job-item">
<el-image <el-image
:src="scope.item['thumb_url']" v-if="slotProp.item.img_url !== ''"
:class="scope.item['can_opt'] ? '' : 'upscale'" :zoom-rate="1.2" :src="slotProp.item['thumb_url']"
:preview-src-list="[scope.item['img_url']]" fit="cover" :initial-index="scope.index" :class="slotProp.item['can_opt'] ? '' : 'upscale'" @click="previewImg(slotProp.item)"
loading="lazy" v-if="scope.item.progress > 0"> fit="cover" :initial-index="slotProp.index"
loading="lazy">
<template #placeholder> <template #placeholder>
<div class="image-slot"> <div class="image-slot">
正在加载图片 正在加载图片
</div> </div>
</template> </template>
</el-image>
<el-image v-else>
<template #error> <template #error>
<div class="image-slot" v-if="scope.item['img_url'] === ''"> <div class="image-slot">
<i class="iconfont icon-loading"></i> <i class="iconfont icon-loading"></i>
<span>正在下载图片</span> <span>正在下载图片</span>
</div> </div>
<div class="image-slot" v-else> <div class="image-slot">
<el-icon> <el-icon>
<Picture/> <Picture/>
</el-icon> </el-icon>
@ -522,13 +530,13 @@
</template> </template>
</el-image> </el-image>
<div class="opt" v-if="scope.item['can_opt']"> <div class="opt" v-if="slotProp.item['can_opt']">
<div class="opt-line"> <div class="opt-line">
<ul> <ul>
<li><a @click="upscale(1, scope.item)">U1</a></li> <li><a @click="upscale(1, slotProp.item)">U1</a></li>
<li><a @click="upscale(2, scope.item)">U2</a></li> <li><a @click="upscale(2, slotProp.item)">U2</a></li>
<li><a @click="upscale(3, scope.item)">U3</a></li> <li><a @click="upscale(3, slotProp.item)">U3</a></li>
<li><a @click="upscale(4, scope.item)">U4</a></li> <li><a @click="upscale(4, slotProp.item)">U4</a></li>
<li class="show-prompt"> <li class="show-prompt">
<el-popover placement="left" title="提示词" :width="240" trigger="hover"> <el-popover placement="left" title="提示词" :width="240" trigger="hover">
@ -540,9 +548,9 @@
<template #default> <template #default>
<div class="mj-list-item-prompt"> <div class="mj-list-item-prompt">
<span>{{ scope.item.prompt }}</span> <span>{{ slotProp.item.prompt }}</span>
<el-icon class="copy-prompt-mj" <el-icon class="copy-prompt-mj"
:data-clipboard-text="scope.item.prompt"> :data-clipboard-text="slotProp.item.prompt">
<DocumentCopy/> <DocumentCopy/>
</el-icon> </el-icon>
</div> </div>
@ -554,40 +562,45 @@
<div class="opt-line"> <div class="opt-line">
<ul> <ul>
<li><a @click="variation(1, scope.item)">V1</a></li> <li><a @click="variation(1, slotProp.item)">V1</a></li>
<li><a @click="variation(2, scope.item)">V2</a></li> <li><a @click="variation(2, slotProp.item)">V2</a></li>
<li><a @click="variation(3, scope.item)">V3</a></li> <li><a @click="variation(3, slotProp.item)">V3</a></li>
<li><a @click="variation(4, scope.item)">V4</a></li> <li><a @click="variation(4, slotProp.item)">V4</a></li>
</ul> </ul>
</div> </div>
</div> </div>
<div class="remove"> <div class="remove">
<el-button type="danger" :icon="Delete" @click="removeImage(scope.item)" circle/> <el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
<el-button type="warning" v-if="scope.item.publish" @click="publishImage(scope.item, false)" <el-button type="warning" v-if="slotProp.item.publish"
@click="publishImage(slotProp.item, false)"
circle> circle>
<i class="iconfont icon-cancel-share"></i> <i class="iconfont icon-cancel-share"></i>
</el-button> </el-button>
<el-button type="success" v-else @click="publishImage(scope.item, true)" circle> <el-button type="success" v-else @click="publishImage(slotProp.item, true)" circle>
<i class="iconfont icon-share-bold"></i> <i class="iconfont icon-share-bold"></i>
</el-button> </el-button>
</div> </div>
</div> </div>
</template> </template>
</ItemList>
<div class="no-more-data" v-if="isOver"> <template #footer>
<span>没有更多数据了</span> <div class="no-more-data">
<i class="iconfont icon-face"></i> <span>没有更多数据了</span>
</div> <i class="iconfont icon-face"></i>
</div>
</template>
</v3-waterfall>
</div> </div>
<el-empty :image-size="100" v-else/> <el-empty :image-size="100" v-else/>
</div> <!-- end finish job list--> </div> <!-- end finish job list-->
</div> </div>
</div> </div>
</div><!-- end task list box --> </div><!-- end task list box -->
</div> </div>
<el-image-viewer @close="() => { previewURL = '' }" v-if="previewURL !== ''" :url-list="[previewURL]"/>
<login-dialog :show="showLoginDialog" @hide="showLoginDialog = false" @success="initData"/> <login-dialog :show="showLoginDialog" @hide="showLoginDialog = false" @success="initData"/>
</div> </div>
</template> </template>
@ -598,7 +611,6 @@ import {ChromeFilled, Delete, DocumentCopy, InfoFilled, Picture, Plus, UploadFil
import Compressor from "compressorjs"; import Compressor from "compressorjs";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage, ElMessageBox, ElNotification} from "element-plus"; import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
import ItemList from "@/components/ItemList.vue";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import {checkSession} from "@/action/session"; import {checkSession} from "@/action/session";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
@ -609,6 +621,9 @@ import LoginDialog from "@/components/LoginDialog.vue";
const listBoxHeight = ref(window.innerHeight - 40) const listBoxHeight = ref(window.innerHeight - 40)
const paramBoxHeight = ref(window.innerHeight - 150) const paramBoxHeight = ref(window.innerHeight - 150)
const showLoginDialog = ref(false) const showLoginDialog = ref(false)
const loading = ref(true)
const colWidth = ref(240)
const previewURL = ref("")
window.onresize = () => { window.onresize = () => {
listBoxHeight.value = window.innerHeight - 40 listBoxHeight.value = window.innerHeight - 40
@ -730,7 +745,7 @@ const connect = () => {
reader.onload = () => { reader.onload = () => {
const message = String(reader.result) const message = String(reader.result)
if (message === "FINISH") { if (message === "FINISH") {
page.value = 1 page.value = 0
fetchFinishJobs(page.value) fetchFinishJobs(page.value)
isOver.value = false isOver.value = false
} }
@ -773,9 +788,9 @@ const initData = () => {
power.value = user['power'] power.value = user['power']
userId.value = user.id userId.value = user.id
isLogin.value = true isLogin.value = true
page.value = 0
fetchRunningJobs() fetchRunningJobs()
fetchFinishJobs(1) fetchFinishJobs()
connect() connect()
}).catch(() => { }).catch(() => {
@ -824,35 +839,28 @@ const fetchRunningJobs = () => {
}) })
} }
const page = ref(0)
const handleScrollEnd = () => {
if (isOver.value === true) {
return
}
page.value += 1
fetchFinishJobs(page.value)
};
const page = ref(1)
const pageSize = ref(15) const pageSize = ref(15)
const isOver = ref(false) const isOver = ref(false)
const fetchFinishJobs = (page) => { const fetchFinishJobs = () => {
if (!isLogin.value) { if (!isLogin.value || isOver.value) {
return return
} }
loading.value = true
page.value = page.value + 1
// //
httpGet(`/api/mj/jobs?status=1&page=${page}&page_size=${pageSize.value}`).then(res => { httpGet(`/api/mj/jobs?status=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
const jobs = res.data const jobs = res.data
for (let i = 0; i < jobs.length; i++) { for (let i = 0; i < jobs.length; i++) {
if (jobs[i]['use_proxy']) { if (jobs[i]['img_url'] !== "") {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?x-oss-process=image/quality,q_60&format=webp'
} else {
if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') { if (jobs[i].type === 'upscale' || jobs[i].type === 'swapFace') {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75' jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/600/q/75'
} else { } else {
jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75' jobs[i]['thumb_url'] = jobs[i]['img_url'] + '?imageView2/1/w/480/h/480/q/75'
} }
} else {
jobs[i]['thumb_url'] = '/images/img-placeholder.jpg'
} }
if (jobs[i].type === 'image' || jobs[i].type === 'variation') { if (jobs[i].type === 'image' || jobs[i].type === 'variation') {
@ -862,11 +870,12 @@ const fetchFinishJobs = (page) => {
if (jobs.length < pageSize.value) { if (jobs.length < pageSize.value) {
isOver.value = true isOver.value = true
} }
if (page === 1) { if (page.value === 1) {
finishedJobs.value = jobs finishedJobs.value = jobs
} else { } else {
finishedJobs.value = finishedJobs.value.concat(jobs) finishedJobs.value = finishedJobs.value.concat(jobs)
} }
loading.value = false
}).catch(e => { }).catch(e => {
ElMessage.error("获取任务失败:" + e.message) ElMessage.error("获取任务失败:" + e.message)
}) })
@ -1005,6 +1014,11 @@ const publishImage = (item, action) => {
}) })
} }
const previewImg = (item) => {
previewURL.value = item.img_url
}
// //
const tabChange = (tab) => { const tabChange = (tab) => {
if (tab === "txt2img" || tab === "img2img" || tab === "cref") { if (tab === "txt2img" || tab === "img2img" || tab === "cref") {

View File

@ -297,43 +297,40 @@
<div class="job-list-box"> <div class="job-list-box">
<h2>任务列表</h2> <h2>任务列表</h2>
<div class="running-job-list"> <div class="running-job-list">
<ItemList :items="runningJobs" v-if="runningJobs.length > 0" :width="240"> <div class="running-job-box" v-if="runningJobs.length > 0">
<template #default="scope"> <div class="job-item" v-for="item in runningJobs">
<div class="job-item"> <div v-if="item.progress > 0" class="job-item-inner">
<div v-if="scope.item.progress > 0" class="job-item-inner"> <el-image :src="item['img_url']" fit="cover" loading="lazy">
<el-image :src="scope.item['img_url']" <template #placeholder>
fit="cover" <div class="image-slot">
loading="lazy"> 正在加载图片
<template #placeholder> </div>
<div class="image-slot"> </template>
正在加载图片
</div>
</template>
<template #error>
<div class="image-slot">
<el-icon v-if="scope.item['img_url'] !== ''">
<Picture/>
</el-icon>
</div>
</template>
</el-image>
<div class="progress">
<el-progress type="circle" :percentage="scope.item.progress" :width="100" color="#47fff1"/>
</div>
</div>
<el-image fit="cover" v-else>
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
<i class="iconfont icon-quick-start"></i> <el-icon>
<span>任务正在排队中</span> <Picture/>
</el-icon>
</div> </div>
</template> </template>
</el-image> </el-image>
<div class="progress">
<el-progress type="circle" :percentage="item.progress" :width="100"
color="#47fff1"/>
</div>
</div> </div>
</template> <el-image fit="cover" v-else>
</ItemList> <template #error>
<div class="image-slot">
<i class="iconfont icon-quick-start"></i>
<span>任务正在排队中</span>
</div>
</template>
</el-image>
</div>
</div>
<el-empty :image-size="100" v-else/> <el-empty :image-size="100" v-else/>
</div> </div>
<h2>创作记录</h2> <h2>创作记录</h2>
@ -344,7 +341,7 @@
:list="finishedJobs" :list="finishedJobs"
srcKey="img_thumb" srcKey="img_thumb"
:gap="20" :gap="20"
:bottomGap="10" :bottomGap="-10"
:colWidth="colWidth" :colWidth="colWidth"
:distanceToScroll="100" :distanceToScroll="100"
:isLoading="loading" :isLoading="loading"
@ -369,49 +366,15 @@
</div> </div>
</div> </div>
</template> </template>
<template #footer>
<div class="no-more-data">
<span>没有更多数据了</span>
<i class="iconfont icon-face"></i>
</div>
</template>
</v3-waterfall> </v3-waterfall>
<!-- <ItemList :items="finishedJobs" :width="240" :gap="16">-->
<!-- <template #default="scope">-->
<!-- <div class="job-item animate" @click="showTask(scope.item)">-->
<!-- <el-image-->
<!-- :src="scope.item['img_url']+'?imageView2/1/w/240/h/240/q/75'"-->
<!-- fit="cover"-->
<!-- loading="lazy">-->
<!-- <template #placeholder>-->
<!-- <div class="image-slot">-->
<!-- 正在加载图片-->
<!-- </div>-->
<!-- </template>-->
<!-- <template #error>-->
<!-- <div class="image-slot">-->
<!-- <el-icon>-->
<!-- <Picture/>-->
<!-- </el-icon>-->
<!-- </div>-->
<!-- </template>-->
<!-- </el-image>-->
<!-- <div class="remove">-->
<!-- <el-button type="danger" :icon="Delete" @click="removeImage($event,scope.item)" circle/>-->
<!-- <el-button type="warning" v-if="scope.item.publish"-->
<!-- @click="publishImage($event,scope.item, false)"-->
<!-- circle>-->
<!-- <i class="iconfont icon-cancel-share"></i>-->
<!-- </el-button>-->
<!-- <el-button type="success" v-else @click="publishImage($event,scope.item, true)" circle>-->
<!-- <i class="iconfont icon-share-bold"></i>-->
<!-- </el-button>-->
<!-- </div>-->
<!-- </div>-->
<!-- </template>-->
<!-- </ItemList>-->
<!-- <div class="no-more-data" v-if="isOver">-->
<!-- <span>没有更多数据了</span>-->
<!-- <i class="iconfont icon-face"></i>-->
<!-- </div>-->
</div> </div>
<el-empty :image-size="100" v-else/> <el-empty :image-size="100" v-else/>
</div> <!-- end finish job list--> </div> <!-- end finish job list-->
@ -541,10 +504,9 @@
<script setup> <script setup>
import {onMounted, onUnmounted, ref} from "vue" import {onMounted, onUnmounted, ref} from "vue"
import {DocumentCopy, InfoFilled, Orange, Picture} from "@element-plus/icons-vue"; import {Delete, DocumentCopy, InfoFilled, Orange, Picture} from "@element-plus/icons-vue";
import {httpGet, httpPost} from "@/utils/http"; import {httpGet, httpPost} from "@/utils/http";
import {ElMessage, ElMessageBox, ElNotification} from "element-plus"; import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
import ItemList from "@/components/ItemList.vue";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import {checkSession} from "@/action/session"; import {checkSession} from "@/action/session";
import {useRouter} from "vue-router"; import {useRouter} from "vue-router";
@ -560,16 +522,8 @@ const showLoginDialog = ref(false)
const isLogin = ref(false) const isLogin = ref(false)
const loading = ref(true) const loading = ref(true)
const colWidth = ref(240) const colWidth = ref(240)
//
const calcColWidth = () => {
const listBoxWidth = window.innerWidth - 400
const rows = Math.floor(listBoxWidth / colWidth.value)
colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows)
}
calcColWidth()
window.onresize = () => { window.onresize = () => {
calcColWidth()
listBoxHeight.value = window.innerHeight - 40 listBoxHeight.value = window.innerHeight - 40
paramBoxHeight.value = window.innerHeight - 150 paramBoxHeight.value = window.innerHeight - 150
} }
@ -645,7 +599,7 @@ const connect = () => {
reader.onload = () => { reader.onload = () => {
const message = String(reader.result) const message = String(reader.result)
if (message === "FINISH") { if (message === "FINISH") {
page.value = 1 page.value = 0
fetchFinishJobs() fetchFinishJobs()
isOver.value = false isOver.value = false
} }
@ -695,6 +649,7 @@ const initData = () => {
power.value = user['power'] power.value = user['power']
userId.value = user.id userId.value = user.id
isLogin.value = true isLogin.value = true
page.value = 0
fetchRunningJobs() fetchRunningJobs()
fetchFinishJobs() fetchFinishJobs()
connect() connect()
@ -735,7 +690,7 @@ const pageSize = ref(20)
const isOver = ref(false) const isOver = ref(false)
// //
const fetchFinishJobs = () => { const fetchFinishJobs = () => {
if (!isLogin.value) { if (!isLogin.value || isOver.value === true) {
return return
} }
loading.value = true loading.value = true
@ -749,7 +704,7 @@ const fetchFinishJobs = () => {
for (let i = 0; i < imageList.length; i++) { for (let i = 0; i < imageList.length; i++) {
imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75" imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
} }
if (page === 1) { if (page.value === 1) {
finishedJobs.value = imageList finishedJobs.value = imageList
} else { } else {
finishedJobs.value = finishedJobs.value.concat(imageList) finishedJobs.value = finishedJobs.value.concat(imageList)

View File

@ -21,7 +21,7 @@
:colWidth="colWidth" :colWidth="colWidth"
:distanceToScroll="100" :distanceToScroll="100"
:isLoading="loading" :isLoading="loading"
:isOver="false" :isOver="isOver"
@scrollReachBottom="getNext"> @scrollReachBottom="getNext">
<template #default="slotProp"> <template #default="slotProp">
<div class="list-item"> <div class="list-item">
@ -81,7 +81,7 @@
:colWidth="colWidth" :colWidth="colWidth"
:distanceToScroll="100" :distanceToScroll="100"
:isLoading="loading" :isLoading="loading"
:isOver="false" :isOver="isOver"
@scrollReachBottom="getNext"> @scrollReachBottom="getNext">
<template #default="slotProp"> <template #default="slotProp">
<div class="list-item"> <div class="list-item">
@ -132,7 +132,7 @@
:colWidth="colWidth" :colWidth="colWidth"
:distanceToScroll="100" :distanceToScroll="100"
:isLoading="loading" :isLoading="loading"
:isOver="false" :isOver="isOver"
@scrollReachBottom="getNext"> @scrollReachBottom="getNext">
<template #default="slotProp"> <template #default="slotProp">
<div class="list-item"> <div class="list-item">