style:样式切换
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 802 B |
Before Width: | Height: | Size: 939 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.6 KiB |
@ -51,6 +51,6 @@
|
||||
color: #999;
|
||||
}
|
||||
.page-apps .inner .list-box .app-item:hover {
|
||||
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
|
||||
box-shadow: 0 0 10px var(--shadow-color); /* 添加阴影效果 */
|
||||
transform: translateY(-10px); /* 向上移动10像素 */
|
||||
}
|
||||
|
@ -1,30 +1,38 @@
|
||||
.page-apps {
|
||||
background-color: #282c34;
|
||||
// background-color: #282c34;
|
||||
height 100%
|
||||
|
||||
.apps-type-nav{
|
||||
height 43px
|
||||
height 50px
|
||||
padding 8px 0;
|
||||
margin-bottom 3px
|
||||
margin 10px auto
|
||||
}
|
||||
|
||||
.scrollbar-type-nav{
|
||||
display flex
|
||||
align-items center
|
||||
height 43px
|
||||
padding 0 5px
|
||||
|
||||
padding 2px
|
||||
background-color #f4f1f7
|
||||
width fit-content
|
||||
border 1px solid rgba(79,89,102,.078)
|
||||
border-radius: 20px
|
||||
margin: 0 auto
|
||||
// background: var(--chat-bg);
|
||||
// width 100%
|
||||
li{
|
||||
flex-shrink 0
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
margin 0 10px
|
||||
margin 5px 8px
|
||||
height 26px
|
||||
border-radius 4px
|
||||
border 1px solid rgb(80,80,80)
|
||||
// border 1px solid rgb(80,80,80)
|
||||
padding 2px 12px
|
||||
background rgba(60,60,60 0.9)
|
||||
color #fff
|
||||
// background rgba(60,60,60 0.9)
|
||||
color var(--theme-text-tertiary)
|
||||
font-weight: bold
|
||||
font-size 14px
|
||||
cursor pointer
|
||||
|
||||
@ -34,9 +42,12 @@
|
||||
overflow hidden
|
||||
margin-right 5px
|
||||
border-radius 50%
|
||||
|
||||
}
|
||||
&.active{
|
||||
background #21aa93;
|
||||
background #fff;
|
||||
color: var(--el-color-primary);
|
||||
border-radius 20px
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,16 +64,24 @@
|
||||
.item {
|
||||
display flex
|
||||
flex-flow row
|
||||
border 1px solid rgb(80,80,80)
|
||||
// border 1px solid rgb(80,80,80)
|
||||
padding 10px
|
||||
background rgba(60,60,60 0.5)
|
||||
background: var(--chat-bg);
|
||||
border-radius 8px
|
||||
|
||||
.image {
|
||||
width 80px
|
||||
height 80px
|
||||
min-width 80px
|
||||
border-radius 5px
|
||||
border-radius 50%
|
||||
overflow hidden
|
||||
object-fit: contain
|
||||
display: flex
|
||||
align-items center
|
||||
justify-content center
|
||||
flex-shrink 0
|
||||
border: 2px solid #f5f7fd
|
||||
background: #fff
|
||||
}
|
||||
|
||||
.inner {
|
||||
@ -75,7 +94,7 @@
|
||||
text-align left
|
||||
|
||||
.info-title {
|
||||
color var(--el-text-color)
|
||||
color: var(--text-theme-color)
|
||||
font-size 1.25rem
|
||||
line-height 1.75rem
|
||||
letter-spacing: .025em;
|
||||
@ -96,7 +115,8 @@
|
||||
word-break: break-all;
|
||||
height 34px
|
||||
font-size: .875rem;
|
||||
color #999999
|
||||
color var(--el-text-color-primary)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
$sideBgColor = #252526;
|
||||
$borderColor = #4676d0;
|
||||
|
||||
#app {
|
||||
|
||||
height: 100%;
|
||||
@ -33,7 +32,7 @@ $borderColor = #4676d0;
|
||||
// .search-input {
|
||||
// --el-input-bg-color: #363535
|
||||
// --el-input-border-color: #464545
|
||||
// --el-input-focus-border-color: #47fff1
|
||||
// --el-input-focus-border-color:#b0a0f8
|
||||
// --el-input-hover-border-color: #2DA39A
|
||||
// box-shadow: none
|
||||
// }
|
||||
|
@ -2,6 +2,7 @@
|
||||
--sm-txt:rgba(163, 174, 208, 1);
|
||||
--text-secondary: #8a939d;
|
||||
--el-color-primary: rgb(107, 80, 225);
|
||||
--theme-textcolor-normal:#b0a0f8;
|
||||
--el-border-radius-base: 8px;
|
||||
--el-color-primary-light-5:rgb(107, 85, 255);
|
||||
--el-color-primary-light-3:rgb(78, 51, 254);
|
||||
@ -19,9 +20,18 @@
|
||||
--theme-border-primary: rgba(86, 86, 95, .322); //主题边框颜色
|
||||
--theme-border-hover: rgb(107, 85, 255);//主题边框hover颜色
|
||||
--text--hover:rgba(215, 211, 240, 0.581) //主题色hover色系
|
||||
--el-input-focus-border-color: #b0a0f8;
|
||||
--little-btn-bg:#e9d3f6;
|
||||
--gray-btn-bg:#ededf591;
|
||||
--a-link-color: #3561ff
|
||||
--shadow-color:rgba(223,71,255,0.6)
|
||||
--sm-btn-bg:#6052ed;
|
||||
--theme-text-tertiary: #595959;
|
||||
--theme-btn-fill-tertiary: #f0ebff;
|
||||
--theme-text-btn-tertiary: #6841ea;
|
||||
|
||||
|
||||
|
||||
// #e7e7e8
|
||||
}
|
||||
.el-dialog{
|
||||
--el-border-radius-base: 20px;
|
||||
@ -90,13 +100,13 @@
|
||||
/* 滚动条的轨道背景颜色 */
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1; /* 滚动条轨道颜色 */
|
||||
background: #f1f1f1 !important; /* 滚动条轨道颜色 */
|
||||
border-radius: 6px; /* 可选:轨道圆角 */
|
||||
}
|
||||
|
||||
/* 滚动条滑块的颜色 */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888; /* 滑块颜色 */
|
||||
background: #888 !important; /* 滑块颜色 */
|
||||
border-radius: 6px; /* 可选:滑块圆角 */
|
||||
}
|
||||
|
||||
@ -116,3 +126,12 @@
|
||||
font-size: 16px
|
||||
}
|
||||
}
|
||||
.sm-btn-theme{
|
||||
background-color: var(--theme-btn-fill-tertiary) !important;
|
||||
color: var(--theme-text-btn-tertiary) !important;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-tag, .el-tag.el-tag--primary{
|
||||
--el-tag-bg-color:#f0ebff
|
||||
}
|
@ -1,13 +1,18 @@
|
||||
.custom-scroll ::-webkit-scrollbar {
|
||||
width: 8px; /* 滚动条宽度 */
|
||||
display:none;
|
||||
}
|
||||
.custom-scroll ::-webkit-scrollbar-track {
|
||||
background-color: #282c34;
|
||||
display:none;
|
||||
}
|
||||
.custom-scroll ::-webkit-scrollbar-thumb {
|
||||
background-color: #444;
|
||||
border-radius: 8px;
|
||||
display:none;
|
||||
}
|
||||
.custom-scroll ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #666;
|
||||
display:none;
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,9 @@
|
||||
.tab-box{
|
||||
align-items: center
|
||||
background-color: var(--card-bg)
|
||||
height: 100%
|
||||
// height: 100%
|
||||
// position: fixed;
|
||||
height: 100vh;
|
||||
.title{
|
||||
font-size: 28px
|
||||
font-weight: 700
|
||||
@ -173,6 +175,13 @@
|
||||
background: var(--theme-bg-all);
|
||||
// background-image: linear-gradient(180deg, rgba(247, 232, 255, .54), rgba(191, 223, 255, .35));
|
||||
width: 100%;
|
||||
.loginMask{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999;
|
||||
}
|
||||
.topheader{
|
||||
display: flex;
|
||||
position: fixed;
|
||||
|
@ -1,25 +1,25 @@
|
||||
.page-dall {
|
||||
background-color: #282c34;
|
||||
// background-color: #282c34;
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
|
||||
.sd-box {
|
||||
margin 10px
|
||||
background-color #262626
|
||||
border 1px solid #454545
|
||||
// background-color #262626
|
||||
// border 1px solid #454545
|
||||
min-width 300px
|
||||
max-width 300px
|
||||
padding 10px
|
||||
border-radius 10px
|
||||
color #ffffff;
|
||||
color var(--text-theme-color);
|
||||
font-size 14px
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
font-size 20px
|
||||
text-align center
|
||||
color #47fff1
|
||||
color#b0a0f8
|
||||
}
|
||||
|
||||
// 隐藏滚动条
|
||||
@ -66,25 +66,24 @@
|
||||
text-align center
|
||||
|
||||
.el-button {
|
||||
width 100%
|
||||
width 200px
|
||||
|
||||
|
||||
span {
|
||||
color #2D3A4B
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form {
|
||||
.el-form-item__label {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
}
|
||||
}
|
||||
|
||||
.task-list-box {
|
||||
background: var(--chat-bg);
|
||||
width 100%
|
||||
padding 10px
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
overflow-x hidden
|
||||
|
||||
.task-list-inner {
|
||||
@ -93,26 +92,26 @@
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
color: #fff;
|
||||
color: var(--text-theme-color);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.title-tabs .el-tabs__item.is-active {
|
||||
color: #47FFF1;
|
||||
color:#b0a0f8;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.title-tabs .el-tabs__active-bar {
|
||||
background-color: #47FFF1;
|
||||
background-color:#b0a0f8;
|
||||
}
|
||||
|
||||
.el-textarea {
|
||||
--el-input-focus-border-color: #47FFF1;
|
||||
// --el-input-focus-border-color:#b0a0f8;
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
color: var(--text-theme-color);
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
@ -141,7 +140,7 @@
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
}
|
||||
|
||||
// 图片上传样式
|
||||
|
@ -365,7 +365,7 @@
|
||||
display: block;
|
||||
}
|
||||
.page-mj .inner .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); /* 添加阴影效果 */
|
||||
box-shadow: 0 0 10px rgba(223,71,255,0.6); /* 添加阴影效果 */
|
||||
transform: translateY(-10px); /* 向上移动10像素 */
|
||||
}
|
||||
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image {
|
||||
|
@ -1,26 +1,30 @@
|
||||
.page-mj {
|
||||
background-color: #282c34;
|
||||
// background-color: #282c34;
|
||||
height 100%
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
// height: 100%
|
||||
|
||||
.mj-box {
|
||||
margin 10px
|
||||
background-color #262626
|
||||
border 1px solid #454545
|
||||
// background-color #262626
|
||||
// border 1px solid #454545
|
||||
// height: calc(100vh - 50px)
|
||||
// overflow: scroll
|
||||
min-width 300px
|
||||
max-width 300px
|
||||
padding 10px
|
||||
border-radius 10px
|
||||
color #ffffff;
|
||||
color var(--text-theme-color);
|
||||
font-size 14px
|
||||
overflow auto
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
font-size 20px
|
||||
text-align center
|
||||
color #47fff1
|
||||
color var( --theme-textcolor-normal)
|
||||
}
|
||||
|
||||
// 隐藏滚动条
|
||||
@ -44,16 +48,20 @@
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
background-color #383838
|
||||
border-radius 5px
|
||||
// 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 #383838
|
||||
border 1px solid var(--chat-bg)
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color #585858
|
||||
border 1px solid var(--theme-border-hover)
|
||||
|
||||
}
|
||||
|
||||
.icon {
|
||||
@ -70,28 +78,30 @@
|
||||
|
||||
|
||||
.grid-content.active {
|
||||
color #47fff1
|
||||
background-color #585858
|
||||
border 1px solid #47fff1
|
||||
// color #47fff1
|
||||
// background-color #585858
|
||||
border 1px solid var(--theme-border-hover)
|
||||
}
|
||||
|
||||
.model {
|
||||
background-color #383838
|
||||
border 1px solid #454545
|
||||
border-radius 5px
|
||||
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 {
|
||||
background-color #585858
|
||||
border 1px solid var(--theme-border-hover)
|
||||
|
||||
}
|
||||
|
||||
.el-image {
|
||||
height 30px
|
||||
height 40px
|
||||
width 100%
|
||||
}
|
||||
|
||||
@ -103,9 +113,10 @@
|
||||
}
|
||||
|
||||
.model.active {
|
||||
color #47fff1
|
||||
background-color #585858
|
||||
border 1px solid #47fff1
|
||||
// color #47fff1
|
||||
// background-color #585858
|
||||
border 1px solid var(--theme-border-hover)
|
||||
|
||||
}
|
||||
|
||||
.form-item-inner {
|
||||
@ -113,16 +124,16 @@
|
||||
align-items: center
|
||||
|
||||
.el-select {
|
||||
--el-select-input-focus-border-color: #47FFF1;
|
||||
--el-input-focus-border-color: #47FFF1;
|
||||
--el-select-input-focus-border-color: var(--el-color-primary)
|
||||
--el-input-focus-border-color: var(--el-color-primary)
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background: #383838;
|
||||
background: var(--chat-bg)
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
color: #fff
|
||||
color: var(--text-theme-color)
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
@ -167,7 +178,7 @@
|
||||
|
||||
.el-form {
|
||||
.el-form-item__label {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
}
|
||||
|
||||
.el-input, .el-slider {
|
||||
@ -182,9 +193,10 @@
|
||||
}
|
||||
|
||||
.task-list-box {
|
||||
background: var(--chat-bg);
|
||||
width 100%
|
||||
padding 0 10px 10px 10px
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
overflow-x hidden
|
||||
|
||||
.task-list-inner {
|
||||
@ -193,26 +205,26 @@
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
color: #fff;
|
||||
color: var(--text-theme-color);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.title-tabs .el-tabs__item.is-active {
|
||||
color: #47FFF1;
|
||||
color: var( --theme-textcolor-normal);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.title-tabs .el-tabs__active-bar {
|
||||
background-color: #47FFF1;
|
||||
background-color: var( --theme-textcolor-normal);
|
||||
}
|
||||
|
||||
.el-textarea {
|
||||
--el-input-focus-border-color: #47FFF1;
|
||||
--el-input-focus-border-color: var(--el-color-primary)
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
color: var(--text-theme-color);
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
@ -241,7 +253,7 @@
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
}
|
||||
|
||||
// 图片上传样式
|
||||
@ -367,7 +379,7 @@
|
||||
display block
|
||||
cursor pointer
|
||||
background-color #4E5058
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
|
||||
&:hover {
|
||||
background-color #6D6F78
|
||||
@ -422,7 +434,7 @@
|
||||
justify-content center
|
||||
align-items center
|
||||
min-height 220px
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
overflow hidden
|
||||
|
||||
.err-msg-container {
|
||||
|
@ -250,7 +250,7 @@
|
||||
display: block;
|
||||
}
|
||||
.page-sd .inner .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); /* 添加阴影效果 */
|
||||
box-shadow: 0 0 10px rgba(223,71,255,0.6); /* 添加阴影效果 */
|
||||
transform: translateY(-10px); /* 向上移动10像素 */
|
||||
}
|
||||
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image {
|
||||
|
@ -1,18 +1,18 @@
|
||||
.page-sd {
|
||||
background-color: #282c34;
|
||||
// background-color: #282c34;
|
||||
|
||||
.inner {
|
||||
display: flex;
|
||||
|
||||
.sd-box {
|
||||
margin 10px
|
||||
background-color #262626
|
||||
border 1px solid #454545
|
||||
// background-color #262626
|
||||
// border 1px solid #454545
|
||||
min-width 300px
|
||||
max-width 300px
|
||||
padding 10px 10px 20px 10px
|
||||
border-radius 10px
|
||||
color #ffffff;
|
||||
color var(--text-theme-color);
|
||||
font-size 14px
|
||||
overflow auto
|
||||
|
||||
@ -20,7 +20,8 @@
|
||||
font-weight: bold;
|
||||
font-size 20px
|
||||
text-align center
|
||||
color #47fff1
|
||||
color var( --theme-textcolor-normal)
|
||||
|
||||
}
|
||||
|
||||
// 隐藏滚动条
|
||||
@ -67,25 +68,24 @@
|
||||
text-align center
|
||||
|
||||
.el-button {
|
||||
width 100%
|
||||
width 200px
|
||||
|
||||
|
||||
span {
|
||||
color #2D3A4B
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-form {
|
||||
.el-form-item__label {
|
||||
color #ffffff
|
||||
color: var(--text-theme-color)
|
||||
}
|
||||
}
|
||||
|
||||
.task-list-box {
|
||||
background: var(--chat-bg);
|
||||
width 100%
|
||||
padding 0 10px 10px 10px
|
||||
color #ffffff
|
||||
color: var(--text-theme-color)
|
||||
overflow-x hidden
|
||||
|
||||
.task-list-inner {
|
||||
@ -108,7 +108,7 @@
|
||||
}
|
||||
|
||||
.el-textarea {
|
||||
--el-input-focus-border-color: #47FFF1;
|
||||
// --el-input-focus-border-color: #47FFF1;
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
@ -142,7 +142,7 @@
|
||||
}
|
||||
|
||||
.el-form-item__label {
|
||||
color #ffffff
|
||||
color: var(--text-theme-color)
|
||||
}
|
||||
|
||||
// 图片上传样式
|
||||
|
@ -9,11 +9,11 @@
|
||||
|
||||
.page-images-wall {
|
||||
display: flex;
|
||||
background-color: #282c34;
|
||||
// background-color: #282c34;
|
||||
|
||||
.inner {
|
||||
width 100%
|
||||
color #ffffff
|
||||
color var(--text-theme-color);
|
||||
overflow hidden
|
||||
|
||||
.header {
|
||||
@ -33,7 +33,7 @@
|
||||
font-size 16px
|
||||
|
||||
.el-radio {
|
||||
color #ffffff
|
||||
color var(--text-theme-color);
|
||||
}
|
||||
|
||||
}
|
||||
@ -68,7 +68,7 @@
|
||||
width 100%
|
||||
bottom 0
|
||||
left 0
|
||||
color #ffffff
|
||||
color var(--text-theme-color);
|
||||
padding 8px 10px
|
||||
line-height 1.2
|
||||
border-top-right-radius 10px
|
||||
@ -77,11 +77,15 @@
|
||||
span {
|
||||
word-break break-all
|
||||
}
|
||||
.iconfont{
|
||||
|
||||
|
||||
}
|
||||
.el-icon, .iconfont {
|
||||
top 2px
|
||||
cursor pointer
|
||||
border 1px solid #ffffff
|
||||
color: #fff !important;
|
||||
border 1px solid #fff !important;
|
||||
border-radius 5px
|
||||
padding 2px
|
||||
font-size 16px;
|
||||
|
@ -1,12 +1,14 @@
|
||||
.page-luma {
|
||||
display flex
|
||||
height 100%
|
||||
background-color #0E0808
|
||||
// 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));
|
||||
// background: linear-gradient(180deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3));
|
||||
|
||||
|
||||
.prompt-box {
|
||||
@ -50,7 +52,7 @@
|
||||
.btn-swap {
|
||||
margin-right 10px
|
||||
.icon-exchange{
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
cursor pointer
|
||||
}
|
||||
}
|
||||
@ -60,18 +62,20 @@
|
||||
.prompt-container {
|
||||
width: 100%;
|
||||
.input-container {
|
||||
background: linear-gradient(90deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3));
|
||||
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;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
margin-bottom: 16px;
|
||||
// box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.prompt-input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: white;
|
||||
color var(--text-theme-color);
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
@ -82,16 +86,14 @@
|
||||
overflow-wrap: break-word;
|
||||
|
||||
scrollbar-width: none; /* 隐藏滚动条 */
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.upload-icon, .send-icon {
|
||||
color #e1e1e1
|
||||
color var( --el-color-primary)
|
||||
.iconfont {
|
||||
font-size 20px
|
||||
cursor pointer
|
||||
@ -104,7 +106,7 @@
|
||||
.params {
|
||||
display flex
|
||||
justify-content right
|
||||
color #e1e1e1
|
||||
color var(--text-theme-color);
|
||||
font-size 14px
|
||||
padding 10px 30px
|
||||
|
||||
@ -129,9 +131,9 @@
|
||||
padding 0 40px
|
||||
|
||||
.h-title {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
width 100%
|
||||
font-size 36px
|
||||
// font-size 36px
|
||||
text-align left
|
||||
}
|
||||
|
||||
@ -147,9 +149,7 @@
|
||||
cursor pointer
|
||||
margin-bottom 10px
|
||||
|
||||
&:hover {
|
||||
background-color #2A2525
|
||||
}
|
||||
|
||||
|
||||
.left {
|
||||
.container {
|
||||
@ -189,7 +189,8 @@
|
||||
border-radius 5px
|
||||
background rgba(100, 100, 100, 0.3)
|
||||
cursor pointer
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
|
||||
opacity 0
|
||||
transform: translate(-50%, 0px);
|
||||
transition opacity 0.3s ease 0s
|
||||
@ -222,7 +223,8 @@
|
||||
text-overflow ellipsis
|
||||
}
|
||||
.prompt {
|
||||
color rgb(250 247 245)
|
||||
color var( --el-text-color-primary)
|
||||
cursor: text
|
||||
}
|
||||
.failed {
|
||||
color #E4696B
|
||||
@ -248,7 +250,7 @@
|
||||
|
||||
.text {
|
||||
margin-right 10px
|
||||
color #e1e1e1
|
||||
color #000
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,8 +261,9 @@
|
||||
color #726E6C
|
||||
|
||||
&:hover {
|
||||
background #5f5958
|
||||
color #e1e1e1
|
||||
// background #5f5958
|
||||
// color #e1e1e1
|
||||
color:var(--el-color-primary)
|
||||
}
|
||||
|
||||
.downloading {
|
||||
@ -317,7 +320,7 @@
|
||||
// border-radius 20px
|
||||
// padding 3px 15px
|
||||
// cursor pointer
|
||||
// color #ffffff
|
||||
// color var(--text-theme-color)
|
||||
// font-size 14px
|
||||
//
|
||||
// .iconfont {
|
||||
@ -344,14 +347,14 @@
|
||||
|
||||
.btn {
|
||||
margin-right 10px
|
||||
background-color #363030
|
||||
border none
|
||||
border-radius 5px
|
||||
padding 5px 10px
|
||||
cursor pointer
|
||||
color #000
|
||||
|
||||
&:hover {
|
||||
background-color #5F5958
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
.page-mark-map {
|
||||
background-color: #282c34;
|
||||
// background-color: #282c34;
|
||||
height 100%
|
||||
|
||||
.inner {
|
||||
@ -7,20 +7,20 @@
|
||||
|
||||
.mark-map-box {
|
||||
margin 10px
|
||||
background-color #262626
|
||||
border 1px solid #454545
|
||||
// background-color #262626
|
||||
// border 1px solid #454545
|
||||
min-width 300px
|
||||
max-width 300px
|
||||
padding 10px
|
||||
border-radius 10px
|
||||
color #ffffff;
|
||||
color var(--text-theme-color);
|
||||
font-size 14px
|
||||
|
||||
h2 {
|
||||
font-weight: bold;
|
||||
font-size 20px
|
||||
text-align center
|
||||
color #47fff1
|
||||
color var( --theme-textcolor-normal)
|
||||
}
|
||||
|
||||
// 隐藏滚动条
|
||||
@ -43,7 +43,7 @@
|
||||
width 100%
|
||||
|
||||
span {
|
||||
color #2D3A4B
|
||||
color #fff
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,13 +61,15 @@
|
||||
|
||||
.el-form {
|
||||
.el-form-item__label {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.chat-box {
|
||||
width 100%
|
||||
background: var(--chat-bg);
|
||||
|
||||
|
||||
.top-bar {
|
||||
display flex
|
||||
@ -77,13 +79,13 @@
|
||||
}
|
||||
|
||||
.markdown {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
display flex
|
||||
justify-content center
|
||||
align-items center
|
||||
|
||||
h1 {
|
||||
color: #47fff1;
|
||||
color: var( --theme-textcolor-normal);
|
||||
}
|
||||
|
||||
h2 {
|
||||
@ -116,7 +118,7 @@
|
||||
|
||||
.markmap {
|
||||
width 100%
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
font-size 12px
|
||||
|
||||
.markmap-foreign {
|
||||
@ -139,7 +141,7 @@
|
||||
|
||||
.mm-toolbar-item {
|
||||
cursor pointer
|
||||
color var(--el-color-white)
|
||||
color var( --el-text-color-primary)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,11 @@
|
||||
list-style: normal;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
font-weight: 600;
|
||||
|
||||
color :var(--a-link-color);
|
||||
text-decoration: underline;
|
||||
|
||||
padding: 0 2px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1,
|
||||
|
@ -141,7 +141,7 @@
|
||||
margin-right: 5px;
|
||||
}
|
||||
.member .inner .product-box .list-box .product-item:hover {
|
||||
box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
|
||||
box-shadow: 0 0 10px var(--shadow-color); /* 添加阴影效果 */
|
||||
transform: translateY(-10px); /* 向上移动10像素 */
|
||||
}
|
||||
.member .inner .product-box .headline {
|
||||
|
@ -1,18 +1,18 @@
|
||||
.member {
|
||||
background-color: #282c34;
|
||||
// background-color: #282c34;
|
||||
height 100%
|
||||
|
||||
.title {
|
||||
text-align center
|
||||
background-color #25272d
|
||||
font-size 24px
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
padding 10px
|
||||
border-bottom 1px solid #3c3c3c
|
||||
}
|
||||
|
||||
.inner {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
padding 15px 0 15px 15px;
|
||||
overflow-x hidden
|
||||
overflow-y visible
|
||||
@ -22,13 +22,13 @@
|
||||
.user-profile {
|
||||
padding 10px 20px 20px 20px
|
||||
width 300px
|
||||
background-color #393F4A
|
||||
color #ffffff
|
||||
background-color var(--chat-bg)
|
||||
color var(--text-theme-color)
|
||||
border-radius 10px
|
||||
//height 100vh
|
||||
|
||||
.el-form-item__label {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
justify-content start
|
||||
}
|
||||
|
||||
@ -58,7 +58,8 @@
|
||||
|
||||
.list-box {
|
||||
.product-item {
|
||||
border 1px solid #666666
|
||||
// border 1px solid #666666
|
||||
background-color var(--chat-bg)
|
||||
border-radius 6px
|
||||
overflow hidden
|
||||
cursor pointer
|
||||
@ -87,7 +88,7 @@
|
||||
text-align center
|
||||
font-size 16px
|
||||
font-weight bold
|
||||
color #47fff1
|
||||
color var( --el-color-primary)
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,14 +139,14 @@
|
||||
padding 0
|
||||
|
||||
.icon-alipay,.icon-wechat-pay {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
}
|
||||
.icon-qq {
|
||||
color #15A6E8
|
||||
font-size 24px
|
||||
}
|
||||
.icon-jd-pay {
|
||||
color #ffffff
|
||||
color var(--text-theme-color)
|
||||
font-size 24px
|
||||
}
|
||||
.icon-douyin {
|
||||
@ -161,7 +162,7 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
|
||||
// box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
|
||||
transform: translateY(-10px); /* 向上移动10像素 */
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
height 200px
|
||||
overflow hidden
|
||||
padding 2px
|
||||
background-color #555555
|
||||
background-color var( --gray-btn-bg)
|
||||
|
||||
.job-item-inner {
|
||||
position relative
|
||||
|
@ -1,7 +1,7 @@
|
||||
.page-suno {
|
||||
display flex
|
||||
height 100%
|
||||
background-color #0E0808
|
||||
// background-color #0E0808
|
||||
overflow auto
|
||||
|
||||
.left-bar {
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
.params {
|
||||
padding 20px 0
|
||||
color rgb(250 247 245)
|
||||
color: var(--text-theme-color);
|
||||
position relative
|
||||
|
||||
.pure-music {
|
||||
@ -78,6 +78,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.song {
|
||||
display flex
|
||||
padding 10px
|
||||
@ -137,6 +138,8 @@
|
||||
bottom 10px
|
||||
font-size 12px
|
||||
padding 2px 5px
|
||||
background-color var(--sm-btn-bg)
|
||||
color: #fff
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,12 +157,16 @@
|
||||
.tag {
|
||||
margin-right 10px
|
||||
word-break keep-all
|
||||
background-color #312C2C
|
||||
color #e1e1e1
|
||||
border-radius 5px
|
||||
background: var(--card-bg);
|
||||
color:var(--theme-text-color-primary);
|
||||
opacity 0.7
|
||||
border-radius 8px
|
||||
padding 3px 6px
|
||||
cursor pointer
|
||||
font-size 13px
|
||||
&:hover{
|
||||
color:var( --el-color-primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -169,6 +176,8 @@
|
||||
width 100%
|
||||
color rgb(250 247 245)
|
||||
overflow auto
|
||||
background: var(--chat-bg)
|
||||
|
||||
|
||||
.list-box {
|
||||
padding 0 0 0 20px
|
||||
@ -180,7 +189,7 @@
|
||||
margin-bottom 10px
|
||||
|
||||
&:hover {
|
||||
background-color #2A2525
|
||||
background: rgba(188,149,236,0.08)
|
||||
}
|
||||
|
||||
.left {
|
||||
@ -247,18 +256,18 @@
|
||||
font-weight 700
|
||||
|
||||
a {
|
||||
color rgb(250 247 245)
|
||||
color vae( --a-link-color)
|
||||
&:hover {
|
||||
text-decoration underline
|
||||
}
|
||||
}
|
||||
|
||||
.model {
|
||||
color #E2E8F0
|
||||
background-color #1C1616
|
||||
border 1px solid #8f8f8f
|
||||
color #8f8f8f
|
||||
// background-color #1C1616
|
||||
// border 1px solid #8f8f8f
|
||||
font-weight normal
|
||||
font-size 14px
|
||||
font-size 12px
|
||||
padding 1px 3px
|
||||
border-radius 5px
|
||||
margin-left 10px
|
||||
@ -271,7 +280,7 @@
|
||||
|
||||
.tags {
|
||||
font-size 14px
|
||||
color #d1d1d1
|
||||
color var(--el-text-color-primary)
|
||||
padding 3px 0
|
||||
}
|
||||
}
|
||||
@ -303,8 +312,9 @@
|
||||
color #726E6C
|
||||
|
||||
&:hover {
|
||||
background #5f5958
|
||||
color #e1e1e1
|
||||
// background #5f5958
|
||||
// color #e1e1e1
|
||||
color:var(--el-color-primary)
|
||||
}
|
||||
|
||||
.downloading {
|
||||
@ -372,14 +382,25 @@
|
||||
|
||||
.btn {
|
||||
margin-right 10px
|
||||
background-color #363030
|
||||
color: #000
|
||||
border none
|
||||
border-radius 5px
|
||||
padding 5px 10px
|
||||
cursor pointer
|
||||
|
||||
|
||||
&:hover {
|
||||
background-color #5F5958
|
||||
opacity :0.8
|
||||
}
|
||||
}
|
||||
}
|
||||
.submit-btn {
|
||||
display flex
|
||||
align-items: center
|
||||
margin: 20px 0
|
||||
justify-content: center;
|
||||
.el-button {
|
||||
width 200px
|
||||
}
|
||||
|
||||
}
|
@ -20,7 +20,9 @@
|
||||
}
|
||||
--btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
|
||||
--border-active:rgba(255, 255, 255, 0.1);
|
||||
--card-bg: rgba(17, 28, 68, 1);
|
||||
// --card-bg: rgba(17, 28, 68, 1);
|
||||
--card-bg: #1f243f;
|
||||
--card-bg-table: rgba(17, 28, 68, 1);
|
||||
--theme-bg:rgb(13, 20, 53);
|
||||
--theme-bg-all:rgb(13, 20, 53);
|
||||
--sign-bg: rgba(27, 37, 75, 1);
|
||||
@ -28,7 +30,7 @@
|
||||
--text-color-primary: #d1c7ff;
|
||||
--el-text-color-regular: rgba(163, 174, 208, 1)
|
||||
--el-border-color:rgb(79, 80, 85)
|
||||
--el-text-color-primary: #fff;
|
||||
--el-text-color-primary: #fff;//黑白切换
|
||||
--el-bg-color-overlay: rgba(17, 28, 68, 1);
|
||||
--el-border-color-light: rgba(255, 255, 255, 0.2);
|
||||
--line-box:rgba(255, 255, 255, 0.1);
|
||||
@ -39,6 +41,7 @@
|
||||
--el-color-primary-light-9:rgba(86, 86, 95, .2);
|
||||
--chat-wel-bg:#2d2f388a;
|
||||
--theme-text-color-secondary: #a3aed0;
|
||||
// --el-pagination-button-bg-color: rgba(86,86,95,0.2);
|
||||
|
||||
//layout
|
||||
.more-menus li.moreTitle,
|
||||
@ -52,9 +55,14 @@
|
||||
.more-menus span.title{
|
||||
color:#000;
|
||||
}
|
||||
.menu-list .menu-list-item.active .el-icon{
|
||||
background: #0080006e;
|
||||
}
|
||||
|
||||
--theme-text-color-primary: #fff;
|
||||
--theme-text-primary: #f3f3f3;
|
||||
--chat-content-bg:rgba(86, 86, 95, .2);
|
||||
--chat-content-bg-list:rgba(86, 86, 95, .2);
|
||||
|
||||
// --theme-text-tertiary: #e1e1e1;
|
||||
}
|
@ -45,7 +45,7 @@
|
||||
--chat-list-bg: #0302020a;
|
||||
--chat-content-bg-list:#fff;
|
||||
--chat-wel-bg:rgba(247, 247, 248, 1);
|
||||
|
||||
--el-pagination-button-bg-color: rgba(86,86,95,0.2);
|
||||
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@
|
||||
|
||||
.animate {
|
||||
&:hover {
|
||||
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
|
||||
box-shadow: 0 0 10px var(--shadow-color); /* 添加阴影效果 */
|
||||
transform: translateY(-10px); /* 向上移动10像素 */
|
||||
}
|
||||
}
|
||||
|
BIN
web/src/assets/img/no-data.png
Normal file
After Width: | Height: | Size: 39 KiB |
@ -256,7 +256,7 @@ const reGenerate = (prompt) => {
|
||||
|
||||
code {
|
||||
color:var(--theme-text-color-primary);
|
||||
background-color #e7e7e8
|
||||
background-color var(--el-color-primary-light-3)
|
||||
padding 0 3px;
|
||||
border-radius 5px;
|
||||
}
|
||||
@ -348,7 +348,7 @@ const reGenerate = (prompt) => {
|
||||
padding 10px 10px 10px 0;
|
||||
|
||||
.bar-item {
|
||||
background-color #e7e7e8;
|
||||
background-color var( --little-btn-bg);
|
||||
color #888
|
||||
padding 3px 5px;
|
||||
margin-right 10px;
|
||||
@ -433,7 +433,7 @@ const reGenerate = (prompt) => {
|
||||
|
||||
code {
|
||||
color:var(--theme-text-color-primary);
|
||||
background-color #e7e7e8
|
||||
background-color var( --little-btn-bg)
|
||||
padding 0 3px;
|
||||
border-radius 5px;
|
||||
}
|
||||
@ -539,7 +539,7 @@ const reGenerate = (prompt) => {
|
||||
}
|
||||
|
||||
.bar-item.bg {
|
||||
background-color #e7e7e8
|
||||
background-color var( --gray-btn-bg)
|
||||
cursor pointer
|
||||
}
|
||||
|
||||
|
@ -1,76 +1,90 @@
|
||||
<template>
|
||||
<div class="invite-list" v-loading="loading">
|
||||
<el-row v-if="items.length > 0">
|
||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
||||
style="--el-table-border-color:#373C47;
|
||||
--el-table-tr-bg-color:#2D323B;
|
||||
--el-table-row-hover-bg-color:#373C47;
|
||||
--el-table-header-bg-color:#474E5C;
|
||||
--el-table-text-color:#d1d1d1">
|
||||
<el-table-column prop="username" label="用户"/>
|
||||
<el-table-column prop="invite_code" label="邀请码"/>
|
||||
<el-table-column prop="remark" label="邀请奖励"/>
|
||||
<el-table
|
||||
:data="items"
|
||||
:row-key="(row) => row.id"
|
||||
table-layout="auto"
|
||||
border
|
||||
style="
|
||||
--el-table-border-color: #373c47;
|
||||
--el-table-tr-bg-color: #2d323b;
|
||||
--el-table-row-hover-bg-color: #373c47;
|
||||
--el-table-header-bg-color: #474e5c;
|
||||
--el-table-text-color: #d1d1d1;
|
||||
"
|
||||
>
|
||||
<el-table-column prop="username" label="用户" />
|
||||
<el-table-column prop="invite_code" label="邀请码" />
|
||||
<el-table-column prop="remark" label="邀请奖励" />
|
||||
|
||||
<el-table-column label="注册时间">
|
||||
<template #default="scope">
|
||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
||||
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
<el-empty :image-size="100" :image="nodata" description="暂无数据" v-else />
|
||||
<div class="pagination">
|
||||
<el-pagination v-if="total > 0" background
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
background
|
||||
layout="total,prev, pager, next"
|
||||
:hide-on-single-page="true"
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||
@current-change="fetchData()"
|
||||
:total="total"/>
|
||||
|
||||
:total="total"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
import { onMounted, ref } from "vue";
|
||||
import { httpGet } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { dateFormat } from "@/utils/libs";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
const items = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const loading = ref(true)
|
||||
const items = ref([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const loading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
const clipboard = new Clipboard('.copy-order-no');
|
||||
clipboard.on('success', () => {
|
||||
fetchData();
|
||||
const clipboard = new Clipboard(".copy-order-no");
|
||||
clipboard.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
clipboard.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
});
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
httpGet('/api/invite/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
||||
httpGet("/api/invite/list", { page: page.value, page_size: pageSize.value })
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items
|
||||
total.value = res.data.total
|
||||
page.value = res.data.page
|
||||
pageSize.value = res.data.page_size
|
||||
items.value = res.data.items;
|
||||
total.value = res.data.total;
|
||||
page.value = res.data.page;
|
||||
pageSize.value = res.data.page_size;
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-container class="realtime-conversation" :style="{height: height}">
|
||||
<el-container class="realtime-conversation" :style="{ height: height }">
|
||||
<!-- connection animation -->
|
||||
<el-container class="connection-container" v-if="!isConnected">
|
||||
<div class="phone-container">
|
||||
@ -36,14 +36,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="call-controls">
|
||||
<el-tooltip content="长按发送语音" placement="top" effect="light">
|
||||
<el-tooltip content="长按发送语音" placement="top">
|
||||
<ripple-button>
|
||||
<button class="call-button answer" @mousedown="startRecording" @mouseup="stopRecording">
|
||||
<button
|
||||
class="call-button answer"
|
||||
@mousedown="startRecording"
|
||||
@mouseup="stopRecording"
|
||||
>
|
||||
<i class="iconfont icon-mic-bold"></i>
|
||||
</button>
|
||||
</ripple-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="结束通话" placement="top" effect="light">
|
||||
<el-tooltip content="结束通话" placement="top">
|
||||
<button class="call-button hangup" @click="hangUp">
|
||||
<i class="iconfont icon-hung-up"></i>
|
||||
</button>
|
||||
@ -51,32 +55,31 @@
|
||||
</div>
|
||||
</div>
|
||||
</el-container>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import RippleButton from "@/components/ui/RippleButton.vue";
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { RealtimeClient } from '@openai/realtime-api-beta';
|
||||
import { WavRecorder, WavStreamPlayer } from '@/lib/wavtools/index.js';
|
||||
import { instructions } from '@/utils/conversation_config.js';
|
||||
import { WavRenderer } from '@/utils/wav_renderer';
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import {getUserToken} from "@/store/session";
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { RealtimeClient } from "@openai/realtime-api-beta";
|
||||
import { WavRecorder, WavStreamPlayer } from "@/lib/wavtools/index.js";
|
||||
import { instructions } from "@/utils/conversation_config.js";
|
||||
import { WavRenderer } from "@/utils/wav_renderer";
|
||||
import { showMessageError } from "@/utils/dialog";
|
||||
import { getUserToken } from "@/store/session";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars,no-undef
|
||||
const props = defineProps({
|
||||
height: {
|
||||
type: String,
|
||||
default: '100vh'
|
||||
default: "100vh"
|
||||
}
|
||||
})
|
||||
});
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['close']);
|
||||
const emits = defineEmits(["close"]);
|
||||
|
||||
/********************** connection animation code *************************/
|
||||
const fullText = "正在接通中...";
|
||||
const connectingText = ref("")
|
||||
const connectingText = ref("");
|
||||
let index = 0;
|
||||
const typeText = () => {
|
||||
if (index < fullText.length) {
|
||||
@ -85,12 +88,12 @@ const typeText = () => {
|
||||
setTimeout(typeText, 200); // 每300毫秒显示一个字
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
connectingText.value = '';
|
||||
connectingText.value = "";
|
||||
index = 0;
|
||||
typeText();
|
||||
}, 1000); // 等待1秒后重新开始
|
||||
}
|
||||
}
|
||||
};
|
||||
/*************************** end of code ****************************************/
|
||||
|
||||
/********************** conversation process code ***************************/
|
||||
@ -102,31 +105,29 @@ const animateVoice = () => {
|
||||
rightVoiceActive.value = Math.random() > 0.5;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const wavRecorder = ref(new WavRecorder({ sampleRate: 24000 }));
|
||||
const wavStreamPlayer = ref(new WavStreamPlayer({ sampleRate: 24000 }));
|
||||
let host = process.env.VUE_APP_WS_HOST
|
||||
if (host === '') {
|
||||
if (location.protocol === 'https:') {
|
||||
host = 'wss://' + location.host;
|
||||
let host = process.env.VUE_APP_WS_HOST;
|
||||
if (host === "") {
|
||||
if (location.protocol === "https:") {
|
||||
host = "wss://" + location.host;
|
||||
} else {
|
||||
host = 'ws://' + location.host;
|
||||
host = "ws://" + location.host;
|
||||
}
|
||||
}
|
||||
const client = ref(
|
||||
new RealtimeClient({
|
||||
url: `${host}/api/realtime`,
|
||||
apiKey: getUserToken(),
|
||||
dangerouslyAllowAPIKeyInBrowser: true,
|
||||
dangerouslyAllowAPIKeyInBrowser: true
|
||||
})
|
||||
);
|
||||
// // Set up client instructions and transcription
|
||||
client.value.updateSession({
|
||||
instructions: instructions,
|
||||
turn_detection: null,
|
||||
input_audio_transcription: { model: 'whisper-1' },
|
||||
voice: 'alloy',
|
||||
input_audio_transcription: { model: "whisper-1" },
|
||||
voice: "alloy"
|
||||
});
|
||||
|
||||
// set voice wave canvas
|
||||
@ -137,50 +138,52 @@ const isRecording = ref(false);
|
||||
const backgroundAudio = ref(null);
|
||||
const hangUpAudio = ref(null);
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
const connect = async () => {
|
||||
if (isConnected.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
// 播放背景音乐
|
||||
if (backgroundAudio.value) {
|
||||
backgroundAudio.value.play().catch(error => {
|
||||
console.error('播放失败,可能是浏览器的自动播放策略导致的:', error);
|
||||
backgroundAudio.value.play().catch((error) => {
|
||||
console.error("播放失败,可能是浏览器的自动播放策略导致的:", error);
|
||||
});
|
||||
}
|
||||
// 模拟拨号延时
|
||||
await sleep(3000)
|
||||
await sleep(3000);
|
||||
try {
|
||||
await client.value.connect();
|
||||
await wavRecorder.value.begin();
|
||||
await wavStreamPlayer.value.connect();
|
||||
console.log("对话连接成功!")
|
||||
console.log("对话连接成功!");
|
||||
if (!client.value.isConnected()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
isConnected.value = true;
|
||||
backgroundAudio.value?.pause()
|
||||
backgroundAudio.value.currentTime = 0
|
||||
backgroundAudio.value?.pause();
|
||||
backgroundAudio.value.currentTime = 0;
|
||||
client.value.sendUserMessageContent([
|
||||
{
|
||||
type: 'input_text',
|
||||
text: '你好,我是极客学长!',
|
||||
},
|
||||
type: "input_text",
|
||||
text: "你好,我是极客学长!"
|
||||
}
|
||||
]);
|
||||
if (client.value.getTurnDetectionType() === 'server_vad') {
|
||||
await wavRecorder.value.record((data) => client.value.appendInputAudio(data.mono));
|
||||
if (client.value.getTurnDetectionType() === "server_vad") {
|
||||
await wavRecorder.value.record((data) =>
|
||||
client.value.appendInputAudio(data.mono)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
// 开始语音输入
|
||||
const startRecording = async () => {
|
||||
if (isRecording.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
isRecording.value = true;
|
||||
@ -190,9 +193,11 @@ const startRecording = async () => {
|
||||
const { trackId, offset } = trackSampleOffset;
|
||||
client.value.cancelResponse(trackId, offset);
|
||||
}
|
||||
await wavRecorder.value.record((data) => client.value.appendInputAudio(data.mono));
|
||||
await wavRecorder.value.record((data) =>
|
||||
client.value.appendInputAudio(data.mono)
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
@ -203,7 +208,7 @@ const stopRecording = async () => {
|
||||
await wavRecorder.value.pause();
|
||||
client.value.createResponse();
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
@ -232,13 +237,13 @@ const initialize = async () => {
|
||||
canvas.width = canvas.offsetWidth;
|
||||
canvas.height = canvas.offsetHeight;
|
||||
}
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const result = wavRecorder.value.recording
|
||||
? wavRecorder.value.getFrequencies('voice')
|
||||
? wavRecorder.value.getFrequencies("voice")
|
||||
: { values: new Float32Array([0]) };
|
||||
WavRenderer.drawBars(canvas, ctx, result.values, '#0099ff', 10, 0, 8);
|
||||
WavRenderer.drawBars(canvas, ctx, result.values, "#0099ff", 10, 0, 8);
|
||||
}
|
||||
}
|
||||
if (serverCanvasRef.value) {
|
||||
@ -247,13 +252,13 @@ const initialize = async () => {
|
||||
canvas.width = canvas.offsetWidth;
|
||||
canvas.height = canvas.offsetHeight;
|
||||
}
|
||||
const ctx = canvas.getContext('2d');
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
const result = wavStreamPlayer.value.analyser
|
||||
? wavStreamPlayer.value.getFrequencies('voice')
|
||||
? wavStreamPlayer.value.getFrequencies("voice")
|
||||
: { values: new Float32Array([0]) };
|
||||
WavRenderer.drawBars(canvas, ctx, result.values, '#009900', 10, 0, 8);
|
||||
WavRenderer.drawBars(canvas, ctx, result.values, "#009900", 10, 0, 8);
|
||||
}
|
||||
}
|
||||
requestAnimationFrame(render);
|
||||
@ -261,17 +266,17 @@ const initialize = async () => {
|
||||
};
|
||||
render();
|
||||
|
||||
client.value.on('error', (event) => {
|
||||
showMessageError(event.error)
|
||||
client.value.on("error", (event) => {
|
||||
showMessageError(event.error);
|
||||
});
|
||||
|
||||
client.value.on('realtime.event', (re) => {
|
||||
if (re.event.type === 'error') {
|
||||
showMessageError(re.event.error)
|
||||
client.value.on("realtime.event", (re) => {
|
||||
if (re.event.type === "error") {
|
||||
showMessageError(re.event.error);
|
||||
}
|
||||
});
|
||||
|
||||
client.value.on('conversation.interrupted', async () => {
|
||||
client.value.on("conversation.interrupted", async () => {
|
||||
const trackSampleOffset = await wavStreamPlayer.value.interrupt();
|
||||
if (trackSampleOffset?.trackId) {
|
||||
const { trackId, offset } = trackSampleOffset;
|
||||
@ -279,21 +284,20 @@ const initialize = async () => {
|
||||
}
|
||||
});
|
||||
|
||||
client.value.on('conversation.updated', async ({ item, delta }) => {
|
||||
client.value.on("conversation.updated", async ({ item, delta }) => {
|
||||
// console.log('item updated', item, delta)
|
||||
if (delta?.audio) {
|
||||
wavStreamPlayer.value.add16BitPCM(delta.audio, item.id);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const voiceInterval = ref(null);
|
||||
onMounted(() => {
|
||||
initialize()
|
||||
initialize();
|
||||
// 启动聊天进行中的动画
|
||||
voiceInterval.value = setInterval(animateVoice, 200);
|
||||
typeText()
|
||||
typeText();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -304,32 +308,31 @@ onUnmounted(() => {
|
||||
// 挂断通话
|
||||
const hangUp = async () => {
|
||||
try {
|
||||
isConnected.value = false
|
||||
isConnected.value = false;
|
||||
// 停止播放拨号音乐
|
||||
if (backgroundAudio.value?.currentTime) {
|
||||
backgroundAudio.value?.pause()
|
||||
backgroundAudio.value.currentTime = 0
|
||||
backgroundAudio.value?.pause();
|
||||
backgroundAudio.value.currentTime = 0;
|
||||
}
|
||||
// 断开客户端的连接
|
||||
client.value.reset()
|
||||
client.value.reset();
|
||||
// 中断语音输入和输出服务
|
||||
await wavRecorder.value.end()
|
||||
await wavStreamPlayer.value.interrupt()
|
||||
await wavRecorder.value.end();
|
||||
await wavStreamPlayer.value.interrupt();
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
} finally {
|
||||
// 播放挂断音乐
|
||||
hangUpAudio.value?.play()
|
||||
emits('close')
|
||||
hangUpAudio.value?.play();
|
||||
emits("close");
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
defineExpose({ connect,hangUp });
|
||||
defineExpose({ connect, hangUp });
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
|
||||
@import "@/assets/css/realtime.styl"
|
||||
|
||||
</style>
|
@ -3,12 +3,14 @@
|
||||
<div class="running-job-box" v-if="list.length > 0">
|
||||
<div class="job-item" v-for="item in list" :key="item.id">
|
||||
<div v-if="item.progress > 0" class="job-item-inner">
|
||||
|
||||
<div class="progress" v-if="item.progress > 0">
|
||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
||||
color="#47fff1"/>
|
||||
<el-progress
|
||||
type="circle"
|
||||
:percentage="item.progress"
|
||||
:width="100"
|
||||
color="#47fff1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<el-image fit="cover" v-else>
|
||||
<template #error>
|
||||
@ -20,19 +22,20 @@
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
<el-empty :image-size="100" v-else :image="nodata" description="暂无任务" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default:[],
|
||||
default: []
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
|
@ -1,95 +1,111 @@
|
||||
<template>
|
||||
<div class="user-bill" v-loading="loading" element-loading-background="rgba(255,255,255,.3)">
|
||||
<div
|
||||
class="user-bill"
|
||||
v-loading="loading"
|
||||
element-loading-background="rgba(255,255,255,.3)"
|
||||
>
|
||||
<el-row v-if="items.length > 0">
|
||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
||||
style="--el-table-border-color:#373C47;
|
||||
--el-table-tr-bg-color:#2D323B;
|
||||
--el-table-row-hover-bg-color:#373C47;
|
||||
--el-table-header-bg-color:#474E5C;
|
||||
--el-table-text-color:#d1d1d1">
|
||||
<el-table
|
||||
:data="items"
|
||||
:row-key="(row) => row.id"
|
||||
table-layout="auto"
|
||||
border
|
||||
>
|
||||
<el-table-column prop="order_no" label="订单号">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.order_no }}</span>
|
||||
<el-icon class="copy-order-no" :data-clipboard-text="scope.row.order_no">
|
||||
<DocumentCopy/>
|
||||
<el-icon
|
||||
class="copy-order-no"
|
||||
:data-clipboard-text="scope.row.order_no"
|
||||
>
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="subject" label="产品名称"/>
|
||||
<el-table-column prop="amount" label="订单金额"/>
|
||||
<el-table-column prop="subject" label="产品名称" />
|
||||
<el-table-column prop="amount" label="订单金额" />
|
||||
<el-table-column label="订单算力">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.remark?.power }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="pay_method" label="支付渠道"/>
|
||||
<el-table-column prop="pay_name" label="支付名称"/>
|
||||
<el-table-column prop="pay_method" label="支付渠道" />
|
||||
<el-table-column prop="pay_name" label="支付名称" />
|
||||
<el-table-column label="支付时间">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row['pay_time']">{{ dateFormat(scope.row['pay_time']) }}</span>
|
||||
<span v-if="scope.row['pay_time']">{{
|
||||
dateFormat(scope.row["pay_time"])
|
||||
}}</span>
|
||||
<el-tag v-else>未支付</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
<el-empty :image-size="100" v-else />
|
||||
<div class="pagination">
|
||||
<el-pagination v-if="total > 0" background
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
background
|
||||
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"/>
|
||||
|
||||
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||
:total="total"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {DocumentCopy} from "@element-plus/icons-vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { httpGet } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { dateFormat } from "@/utils/libs";
|
||||
import { DocumentCopy } from "@element-plus/icons-vue";
|
||||
import Clipboard from "clipboard";
|
||||
|
||||
const items = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(12)
|
||||
const loading = ref(true)
|
||||
const items = ref([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(12);
|
||||
const loading = ref(true);
|
||||
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
const clipboard = new Clipboard('.copy-order-no');
|
||||
clipboard.on('success', () => {
|
||||
fetchData();
|
||||
const clipboard = new Clipboard(".copy-order-no");
|
||||
clipboard.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
clipboard.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
});
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
httpGet('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
|
||||
httpGet("/api/order/list", { page: page.value, page_size: pageSize.value })
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items
|
||||
total.value = res.data.total
|
||||
page.value = res.data.page
|
||||
pageSize.value = res.data.page_size
|
||||
items.value = res.data.items;
|
||||
total.value = res.data.total;
|
||||
page.value = res.data.page;
|
||||
pageSize.value = res.data.page_size;
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="stylus">
|
||||
.user-bill {
|
||||
background-color: var(--chat-bg);
|
||||
|
||||
.pagination {
|
||||
margin: 20px 0 0 0;
|
||||
display: flex;
|
||||
|
@ -9,71 +9,86 @@
|
||||
:http-request="afterRead"
|
||||
accept=".png,.jpg,.jpeg,.bmp"
|
||||
>
|
||||
<el-avatar v-if="user.avatar" :src="user.avatar" shape="circle" :size="100"/>
|
||||
<el-avatar
|
||||
v-if="user.avatar"
|
||||
:src="user.avatar"
|
||||
shape="circle"
|
||||
:size="100"
|
||||
/>
|
||||
<el-icon v-else class="avatar-uploader-icon">
|
||||
<Plus/>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</el-row>
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="user['nickname']"/>
|
||||
<el-input v-model="user['nickname']" />
|
||||
</el-form-item>
|
||||
<el-form-item label="账号">
|
||||
<div class="flex">
|
||||
<span>{{ user.username }}</span>
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="您已经是 VIP 会员"
|
||||
placement="right"
|
||||
>
|
||||
<span class="vip-icon"><el-image v-if="user.vip" :src="vipImg" style="height: 25px;margin-left: 10px"/></span>
|
||||
<span class="vip-icon"
|
||||
><el-image
|
||||
v-if="user.vip"
|
||||
:src="vipImg"
|
||||
style="height: 25px; margin-left: 10px"
|
||||
/></span>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="剩余算力">
|
||||
<el-tag>{{ user['power'] }}</el-tag>
|
||||
<el-text type="warning">{{ user["power"] }}</el-text>
|
||||
</el-form-item>
|
||||
<el-form-item label="会员到期时间" v-if="user['expired_time'] > 0">
|
||||
<el-tag type="danger">{{ dateFormat(user['expired_time']) }}</el-tag>
|
||||
<el-tag type="danger">{{ dateFormat(user["expired_time"]) }}</el-tag>
|
||||
</el-form-item>
|
||||
|
||||
<el-row class="opt-line">
|
||||
<el-button color="#47fff1" :dark="false" @click="save">保存</el-button>
|
||||
<el-button :dark="false" type="primary" @click="save">保存</el-button>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {Plus} from "@element-plus/icons-vue";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import Compressor from "compressorjs";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {checkSession} from "@/store/cache";
|
||||
import { dateFormat } from "@/utils/libs";
|
||||
import { checkSession } from "@/store/cache";
|
||||
|
||||
const user = ref({
|
||||
vip: false,
|
||||
username: '演示数据',
|
||||
nickname: '演示数据',
|
||||
avatar: '/images/vip.png',
|
||||
mobile: '演示数据',
|
||||
power: 99999,
|
||||
})
|
||||
const vipImg = ref("/images/vip.png")
|
||||
username: "演示数据",
|
||||
nickname: "演示数据",
|
||||
avatar: "/images/menu/member.png",
|
||||
mobile: "演示数据",
|
||||
power: 99999
|
||||
});
|
||||
const vipImg = ref("/images/menu/member.png");
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(() => {
|
||||
checkSession()
|
||||
.then(() => {
|
||||
// 获取最新用户信息
|
||||
httpGet('/api/user/profile').then(res => {
|
||||
user.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取用户信息失败:" + e.message)
|
||||
});
|
||||
}).catch(e => {
|
||||
console.log(e)
|
||||
httpGet("/api/user/profile")
|
||||
.then((res) => {
|
||||
user.value = res.data;
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取用户信息失败:" + e.message);
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
});
|
||||
});
|
||||
|
||||
const afterRead = (file) => {
|
||||
// 压缩图片并上传
|
||||
@ -81,28 +96,32 @@ const afterRead = (file) => {
|
||||
quality: 0.6,
|
||||
success(result) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', result, result.name);
|
||||
formData.append("file", result, result.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => {
|
||||
user.value.avatar = res.data.url
|
||||
ElMessage.success({message: "上传成功", duration: 500})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('图片上传失败:' + e.message)
|
||||
httpPost("/api/upload", formData)
|
||||
.then((res) => {
|
||||
user.value.avatar = res.data.url;
|
||||
ElMessage.success({ message: "上传成功", duration: 500 });
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("图片上传失败:" + e.message);
|
||||
});
|
||||
},
|
||||
error(err) {
|
||||
console.log(err.message);
|
||||
},
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
httpPost('/api/user/profile/update', user.value).then(() => {
|
||||
ElMessage.success({message: '更新成功', duration: 500})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('更新失败:' + e.message)
|
||||
httpPost("/api/user/profile/update", user.value)
|
||||
.then(() => {
|
||||
ElMessage.success({ message: "更新成功", duration: 500 });
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("更新失败:" + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="welcome">
|
||||
<div class="container">
|
||||
<h1 class="title">{{ title }}-{{ version }}</h1>
|
||||
<h2 class="title">{{ title }}-{{ version }}</h2>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
@ -128,10 +128,11 @@ const send = (text) => {
|
||||
width 100%
|
||||
|
||||
.title {
|
||||
font-size: 2.25rem
|
||||
// font-size: 2.25rem
|
||||
line-height: 2.5rem
|
||||
font-weight 600
|
||||
margin-bottom: 4rem
|
||||
color var( --theme-textcolor-normal)
|
||||
}
|
||||
|
||||
.grid-content {
|
||||
|
@ -1,57 +1,58 @@
|
||||
<template>
|
||||
<div class="black-input-wrapper">
|
||||
<el-input v-model="model" :type="type" :rows="rows"
|
||||
<el-input
|
||||
v-model="model"
|
||||
:type="type"
|
||||
:rows="rows"
|
||||
@input="onInput"
|
||||
style="--el-input-bg-color:#252020;
|
||||
--el-input-border-color:#414141;
|
||||
--el-input-focus-border-color:#414141;
|
||||
--el-text-color-regular: #f1f1f1;
|
||||
--el-input-border-radius: 10px;
|
||||
--el-border-color-hover:#616161"
|
||||
resize="none"
|
||||
:placeholder="placeholder" :maxlength="maxlength"/>
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxlength"
|
||||
/>
|
||||
<div class="word-stat" v-if="rows > 1">
|
||||
<span>{{value.length}}</span>/<span>{{maxlength}}</span>
|
||||
<span>{{ value.length }}</span
|
||||
>/<span>{{ maxlength }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {ref, watch} from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
value : {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: ""
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: ""
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'input',
|
||||
default: "input"
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 5,
|
||||
default: 5
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: 1024
|
||||
}
|
||||
});
|
||||
watch(() => props.value, (newValue) => {
|
||||
model.value = newValue
|
||||
})
|
||||
const model = ref(props.value)
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
model.value = newValue;
|
||||
}
|
||||
);
|
||||
const model = ref(props.value);
|
||||
// eslint-disable-next-line no-undef
|
||||
const emits = defineEmits(['update:value']);
|
||||
const emits = defineEmits(["update:value"]);
|
||||
const onInput = (value) => {
|
||||
emits('update:value',value)
|
||||
}
|
||||
emits("update:value", value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
@ -1,33 +1,32 @@
|
||||
<template>
|
||||
|
||||
<el-select v-model="model" :placeholder="placeholder"
|
||||
:value="value" @change="$emit('update:value', $event)"
|
||||
style="--el-fill-color-blank:#252020;
|
||||
--el-text-color-regular: #a1a1a1;
|
||||
--el-select-disabled-color:#0E0808;
|
||||
--el-color-primary-light-9:#0E0808;
|
||||
--el-border-radius-base:20px;
|
||||
--el-border-color:#0E0808;">
|
||||
<el-option v-for="item in options"
|
||||
<el-select
|
||||
v-model="model"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@change="$emit('update:value', $event)"
|
||||
style="--el-border-radius-base: 20px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value">
|
||||
:value="item.value"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BlackSelect',
|
||||
name: "BlackSelect",
|
||||
props: {
|
||||
value : {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
default: ""
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择',
|
||||
default: "请选择"
|
||||
},
|
||||
options: {
|
||||
type: Array,
|
||||
@ -37,7 +36,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
model: this.value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,24 +1,26 @@
|
||||
<template>
|
||||
|
||||
<el-switch v-model="model" :size="size"
|
||||
<el-switch
|
||||
v-model="model"
|
||||
:size="size"
|
||||
@change="$emit('update:value', $event)"
|
||||
style="--el-switch-on-color:#555555;--el-color-white:#0E0808"/>
|
||||
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {ref, watch} from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
const props = defineProps({
|
||||
value : Boolean,
|
||||
value: Boolean,
|
||||
size: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
default: "default"
|
||||
}
|
||||
});
|
||||
const model = ref(props.value)
|
||||
const model = ref(props.value);
|
||||
|
||||
watch(() => props.value, (newValue) => {
|
||||
model.value = newValue
|
||||
})
|
||||
watch(
|
||||
() => props.value,
|
||||
(newValue) => {
|
||||
model.value = newValue;
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
@ -1,26 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<div class="page-apps custom-scroll">
|
||||
<div class="apps-type-nav">
|
||||
<el-scrollbar>
|
||||
<ul class="scrollbar-type-nav">
|
||||
<li :class="{active: typeId === ''}" @click="getAppList('')">全部分类</li>
|
||||
<li v-for="item in appTypes" :key="item.id" :class="{active: typeId === item.id}" @click="getAppList(item.id)">
|
||||
<li :class="{ active: typeId === '' }" @click="getAppList('')">
|
||||
全部分类
|
||||
</li>
|
||||
<li
|
||||
v-for="item in appTypes"
|
||||
:key="item.id"
|
||||
:class="{ active: typeId === item.id }"
|
||||
@click="getAppList(item.id)"
|
||||
>
|
||||
<div class="image" v-if="item.icon">
|
||||
<el-image :src="item.icon" fit="cover"/>
|
||||
<el-image :src="item.icon" fit="cover" />
|
||||
</div>
|
||||
{{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<div class="app-list-container" :style="{height: listBoxHeight + 'px'}">
|
||||
<div class="app-list-container" :style="{ height: listBoxHeight + 'px' }">
|
||||
<ItemList :items="list" v-if="list.length > 0" :gap="15" :width="300">
|
||||
<template #default="scope">
|
||||
<div class="item">
|
||||
<div class="image">
|
||||
<el-image :src="scope.item.icon" fit="cover"/>
|
||||
<el-image :src="scope.item.icon" fit="cover" />
|
||||
</div>
|
||||
|
||||
<div class="inner">
|
||||
@ -29,17 +35,34 @@
|
||||
<div class="info-text">{{ scope.item.hello_msg }}</div>
|
||||
</div>
|
||||
<div class="btn">
|
||||
<el-button size="small" color="#21aa93" @click="useRole(scope.item)">使用</el-button>
|
||||
<el-tooltip effect="light" content="从工作区移除" placement="top" v-if="hasRole(scope.item.key)">
|
||||
<el-button size="small" type="danger" @click="updateRole(scope.item,'remove')">移除</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
class="sm-btn-theme"
|
||||
@click="useRole(scope.item)"
|
||||
>使用</el-button
|
||||
>
|
||||
<el-tooltip
|
||||
content="从工作区移除"
|
||||
placement="top"
|
||||
v-if="hasRole(scope.item.key)"
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="updateRole(scope.item, 'remove')"
|
||||
>移除</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
<el-tooltip effect="light" content="添加到工作区" placement="top" v-else>
|
||||
<el-button size="small" style="--el-color-primary:#009999" @click="updateRole(scope.item, 'add')">添加</el-button>
|
||||
<el-tooltip content="添加到工作区" placement="top" v-else>
|
||||
<el-button
|
||||
size="small"
|
||||
style="--el-color-primary: #009999"
|
||||
@click="updateRole(scope.item, 'add')"
|
||||
>添加</el-button
|
||||
>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- <div class="app-item">-->
|
||||
<!-- <el-image :src="scope.item.icon" fit="cover"/>-->
|
||||
@ -73,95 +96,108 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import {ElMessage} from "element-plus";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {checkSession} from "@/store/cache";
|
||||
import {arrayContains, removeArrayItem, substr} from "@/utils/libs";
|
||||
import {useRouter} from "vue-router";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { checkSession } from "@/store/cache";
|
||||
import { arrayContains, removeArrayItem, substr } from "@/utils/libs";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useSharedStore } from "@/store/sharedata";
|
||||
import ItemList from "@/components/ItemList.vue";
|
||||
|
||||
const listBoxHeight = window.innerHeight - 133
|
||||
const listBoxHeight = window.innerHeight - 133;
|
||||
|
||||
const typeId = ref('')
|
||||
const appTypes = ref([])
|
||||
const list = ref([])
|
||||
const roles = ref([])
|
||||
const typeId = ref("");
|
||||
const appTypes = ref([]);
|
||||
const list = ref([]);
|
||||
const roles = ref([]);
|
||||
const store = useSharedStore();
|
||||
|
||||
onMounted(() => {
|
||||
getAppType()
|
||||
getAppList()
|
||||
getRoles()
|
||||
})
|
||||
getAppType();
|
||||
getAppList();
|
||||
getRoles();
|
||||
});
|
||||
|
||||
const getRoles = () => {
|
||||
checkSession().then(user => {
|
||||
roles.value = user.chat_roles
|
||||
}).catch(e => {
|
||||
console.log(e.message)
|
||||
checkSession()
|
||||
.then((user) => {
|
||||
roles.value = user.chat_roles;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
console.log(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const getAppType = () => {
|
||||
httpGet("/api/app/type/list").then((res) => {
|
||||
appTypes.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取分类失败:" + e.message)
|
||||
httpGet("/api/app/type/list")
|
||||
.then((res) => {
|
||||
appTypes.value = res.data;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取分类失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const getAppList = (tid = '') => {
|
||||
const getAppList = (tid = "") => {
|
||||
typeId.value = tid;
|
||||
httpGet("/api/app/list", { tid }).then((res) => {
|
||||
const items = res.data
|
||||
httpGet("/api/app/list", { tid })
|
||||
.then((res) => {
|
||||
const items = res.data;
|
||||
// 处理 hello message
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].intro = substr(items[i].hello_msg, 80)
|
||||
items[i].intro = substr(items[i].hello_msg, 80);
|
||||
}
|
||||
list.value = items
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取应用失败:" + e.message)
|
||||
list.value = items;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取应用失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const updateRole = (row, opt) => {
|
||||
checkSession().then(() => {
|
||||
const title = ref("")
|
||||
checkSession()
|
||||
.then(() => {
|
||||
const title = ref("");
|
||||
if (opt === "add") {
|
||||
title.value = "添加应用"
|
||||
const exists = arrayContains(roles.value, row.key)
|
||||
title.value = "添加应用";
|
||||
const exists = arrayContains(roles.value, row.key);
|
||||
if (exists) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
roles.value.push(row.key)
|
||||
roles.value.push(row.key);
|
||||
} else {
|
||||
title.value = "移除应用"
|
||||
const exists = arrayContains(roles.value, row.key)
|
||||
title.value = "移除应用";
|
||||
const exists = arrayContains(roles.value, row.key);
|
||||
if (!exists) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
roles.value = removeArrayItem(roles.value, row.key)
|
||||
roles.value = removeArrayItem(roles.value, row.key);
|
||||
}
|
||||
httpPost("/api/app/update", {keys: roles.value}).then(() => {
|
||||
ElMessage.success({message: title.value + "成功!", duration: 1000})
|
||||
}).catch(e => {
|
||||
ElMessage.error(title.value + "失败:" + e.message)
|
||||
httpPost("/api/app/update", { keys: roles.value })
|
||||
.then(() => {
|
||||
ElMessage.success({
|
||||
message: title.value + "成功!",
|
||||
duration: 1000
|
||||
});
|
||||
})
|
||||
}).catch(() => {
|
||||
store.setShowLoginDialog(true)
|
||||
.catch((e) => {
|
||||
ElMessage.error(title.value + "失败:" + e.message);
|
||||
});
|
||||
})
|
||||
}
|
||||
.catch(() => {
|
||||
store.setShowLoginDialog(true);
|
||||
});
|
||||
};
|
||||
|
||||
const hasRole = (roleKey) => {
|
||||
return arrayContains(roles.value, roleKey, (v1, v2) => v1 === v2)
|
||||
}
|
||||
return arrayContains(roles.value, roleKey, (v1, v2) => v1 === v2);
|
||||
};
|
||||
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const useRole = (role) => {
|
||||
router.push(`/chat?role_id=${role.id}`)
|
||||
}
|
||||
router.push(`/chat?role_id=${role.id}`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
@ -880,6 +880,7 @@ const autofillPrompt = (text) => {
|
||||
// 发送消息
|
||||
const sendMessage = function () {
|
||||
if (!isLogin.value) {
|
||||
console.log("未登录");
|
||||
store.setShowLoginDialog(true);
|
||||
return;
|
||||
}
|
||||
|
@ -11,8 +11,13 @@
|
||||
<el-form-item label="图片质量">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-select v-model="params.quality" style="width:176px">
|
||||
<el-option v-for="v in qualities" :label="v.name" :value="v.value" :key="v.value"/>
|
||||
<el-select v-model="params.quality" style="width: 176px">
|
||||
<el-option
|
||||
v-for="v in qualities"
|
||||
:label="v.name"
|
||||
:value="v.value"
|
||||
:key="v.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
@ -23,8 +28,13 @@
|
||||
<el-form-item label="图片尺寸">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-select v-model="params.size" style="width:176px">
|
||||
<el-option v-for="v in sizes" :label="v" :value="v" :key="v"/>
|
||||
<el-select v-model="params.size" style="width: 176px">
|
||||
<el-option
|
||||
v-for="v in sizes"
|
||||
:label="v"
|
||||
:value="v"
|
||||
:key="v"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
@ -35,17 +45,21 @@
|
||||
<el-form-item label="图片样式">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-select v-model="params.style" style="width:176px">
|
||||
<el-option v-for="v in styles" :label="v.name" :value="v.value" :key="v.value"/>
|
||||
<el-select v-model="params.style" style="width: 176px">
|
||||
<el-option
|
||||
v-for="v in styles"
|
||||
:label="v.name"
|
||||
:value="v.value"
|
||||
:key="v.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="生动使模型倾向于生成超真实和戏剧性的图像"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -61,42 +75,58 @@
|
||||
ref="promptRef"
|
||||
placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
|
||||
v-loading="isGenerating"
|
||||
style="--el-mask-color:rgba(100, 100, 100, 0.8)"
|
||||
style="--el-mask-color: rgba(100, 100, 100, 0.8)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-row class="text-info">
|
||||
<el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2" :disabled="isGenerating">
|
||||
<i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
|
||||
<el-button
|
||||
class="generate-btn"
|
||||
size="small"
|
||||
@click="generatePrompt"
|
||||
color="#5865f2"
|
||||
:disabled="isGenerating"
|
||||
>
|
||||
<i
|
||||
class="iconfont icon-chuangzuo"
|
||||
style="margin-right: 5px"
|
||||
></i>
|
||||
<span>生成专业绘画指令</span>
|
||||
</el-button>
|
||||
</el-row>
|
||||
|
||||
<div class="text-info">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-tag>每次绘图消耗{{ dallPower }}算力</el-tag>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-tag type="success">当前可用{{ power }}算力</el-tag>
|
||||
</el-col>
|
||||
<el-text type="primary"
|
||||
>每次绘图消耗
|
||||
<el-text type="warning"
|
||||
>{{ dallPower }}算力;</el-text
|
||||
></el-text
|
||||
>
|
||||
|
||||
<el-text type="primary"
|
||||
>当前可用
|
||||
<el-text type="warning"> {{ power }}算力</el-text>
|
||||
</el-text>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="submit-btn">
|
||||
<el-button color="#47fff1" :dark="false" round @click="generate">
|
||||
<el-button type="primary" :dark="false" round @click="generate">
|
||||
立即生成
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<h2>任务列表</h2>
|
||||
<task-list :list="runningJobs" />
|
||||
|
||||
<template v-if="finishedJobs.length > 0">
|
||||
<h2>创作记录</h2>
|
||||
<div class="finish-job-list">
|
||||
<div v-if="finishedJobs.length > 0">
|
||||
@ -110,7 +140,8 @@
|
||||
:distanceToScroll="100"
|
||||
:isLoading="loading"
|
||||
:isOver="isOver"
|
||||
@scrollReachBottom="fetchFinishJobs()">
|
||||
@scrollReachBottom="fetchFinishJobs()"
|
||||
>
|
||||
<template #default="slotProp">
|
||||
<div class="job-item">
|
||||
<el-image
|
||||
@ -118,17 +149,16 @@
|
||||
@click="previewImg(slotProp.item)"
|
||||
:src="slotProp.item['img_thumb']"
|
||||
fit="cover"
|
||||
loading="lazy">
|
||||
loading="lazy"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
<div class="image-slot">正在加载图片</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
<Picture />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
@ -140,12 +170,22 @@
|
||||
<div class="err-msg-container">
|
||||
<div class="title">任务失败</div>
|
||||
<div class="opt">
|
||||
<el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
|
||||
<el-popover
|
||||
title="错误详情"
|
||||
trigger="click"
|
||||
:width="250"
|
||||
:content="slotProp.item['err_msg']"
|
||||
placement="top"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button type="info">详情</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-button type="danger" @click="removeImage(slotProp.item)">删除</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
@click="removeImage(slotProp.item)"
|
||||
>删除</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -162,25 +202,44 @@
|
||||
</el-image>
|
||||
|
||||
<div class="remove">
|
||||
<el-tooltip content="删除" placement="top" effect="light">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
@click="removeImage(slotProp.item)"
|
||||
circle
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="取消分享" placement="top" effect="light" v-if="slotProp.item.publish">
|
||||
<el-button type="warning"
|
||||
<el-tooltip
|
||||
content="取消分享"
|
||||
placement="top"
|
||||
v-if="slotProp.item.publish"
|
||||
>
|
||||
<el-button
|
||||
type="warning"
|
||||
@click="publishImage(slotProp.item, false)"
|
||||
circle>
|
||||
circle
|
||||
>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="分享" placement="top" effect="light" v-else>
|
||||
<el-button type="success" @click="publishImage(slotProp.item, true)" circle>
|
||||
<el-tooltip content="分享" placement="top" v-else>
|
||||
<el-button
|
||||
type="success"
|
||||
@click="publishImage(slotProp.item, true)"
|
||||
circle
|
||||
>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip content="复制提示词" placement="top" effect="light">
|
||||
<el-button type="info" circle class="copy-prompt"
|
||||
:data-clipboard-text="slotProp.item.prompt">
|
||||
<el-tooltip content="复制提示词" placement="top">
|
||||
<el-button
|
||||
type="info"
|
||||
circle
|
||||
class="copy-prompt"
|
||||
:data-clipboard-text="slotProp.item.prompt"
|
||||
>
|
||||
<i class="iconfont icon-file"></i>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
@ -195,246 +254,276 @@
|
||||
</div>
|
||||
</template>
|
||||
</v3-waterfall>
|
||||
</div>
|
||||
<el-empty
|
||||
:image-size="100"
|
||||
:image="nodata"
|
||||
description="暂无记录"
|
||||
v-else
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
</div> <!-- end finish job list-->
|
||||
<!-- end finish job list-->
|
||||
</div>
|
||||
</div>
|
||||
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
||||
</div><!-- end task list box -->
|
||||
<back-top :right="30" :bottom="30" />
|
||||
</div>
|
||||
<!-- end task list box -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<el-image-viewer @close="() => { previewURL = '' }" v-if="previewURL !== ''" :url-list="[previewURL]"/>
|
||||
<el-image-viewer
|
||||
@close="
|
||||
() => {
|
||||
previewURL = '';
|
||||
}
|
||||
"
|
||||
v-if="previewURL !== ''"
|
||||
:url-list="[previewURL]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
|
||||
import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||
import { Delete, InfoFilled, Picture } from "@element-plus/icons-vue";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import Clipboard from "clipboard";
|
||||
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import { checkSession, getClientId, getSystemInfo } from "@/store/cache";
|
||||
import { useSharedStore } from "@/store/sharedata";
|
||||
import TaskList from "@/components/TaskList.vue";
|
||||
import BackTop from "@/components/BackTop.vue";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import { showMessageError } from "@/utils/dialog";
|
||||
|
||||
const listBoxHeight = ref(0)
|
||||
const listBoxHeight = ref(0);
|
||||
// const paramBoxHeight = ref(0)
|
||||
const isLogin = ref(false)
|
||||
const loading = ref(true)
|
||||
const colWidth = ref(220)
|
||||
const isOver = ref(false)
|
||||
const previewURL = ref("")
|
||||
const isLogin = ref(false);
|
||||
const loading = ref(true);
|
||||
const colWidth = ref(220);
|
||||
const isOver = ref(false);
|
||||
const previewURL = ref("");
|
||||
const store = useSharedStore();
|
||||
|
||||
const resizeElement = function () {
|
||||
listBoxHeight.value = window.innerHeight - 90
|
||||
listBoxHeight.value = window.innerHeight - 90;
|
||||
// paramBoxHeight.value = window.innerHeight - 110
|
||||
};
|
||||
resizeElement()
|
||||
resizeElement();
|
||||
window.onresize = () => {
|
||||
resizeElement()
|
||||
}
|
||||
resizeElement();
|
||||
};
|
||||
const qualities = [
|
||||
{name: "标准", value: "standard"},
|
||||
{name: "高清", value: "hd"},
|
||||
]
|
||||
const sizes = ["1024x1024", "1792x1024", "1024x1792"]
|
||||
{ name: "标准", value: "standard" },
|
||||
{ name: "高清", value: "hd" }
|
||||
];
|
||||
const sizes = ["1024x1024", "1792x1024", "1024x1792"];
|
||||
const styles = [
|
||||
{name: "生动", value: "vivid"},
|
||||
{name: "自然", value: "natural"}
|
||||
]
|
||||
{ name: "生动", value: "vivid" },
|
||||
{ name: "自然", value: "natural" }
|
||||
];
|
||||
const params = ref({
|
||||
client_id: getClientId(),
|
||||
quality: "standard",
|
||||
size: "1024x1024",
|
||||
style: "vivid",
|
||||
prompt: ""
|
||||
})
|
||||
});
|
||||
|
||||
const finishedJobs = ref([])
|
||||
const runningJobs = ref([])
|
||||
const power = ref(0)
|
||||
const dallPower = ref(0) // 画一张 SD 图片消耗算力
|
||||
const clipboard = ref(null)
|
||||
const userId = ref(0)
|
||||
const finishedJobs = ref([]);
|
||||
const runningJobs = ref([]);
|
||||
const power = ref(0);
|
||||
const dallPower = ref(0); // 画一张 SD 图片消耗算力
|
||||
const clipboard = ref(null);
|
||||
const userId = ref(0);
|
||||
onMounted(() => {
|
||||
initData()
|
||||
clipboard.value = new Clipboard('.copy-prompt');
|
||||
clipboard.value.on('success', () => {
|
||||
initData();
|
||||
clipboard.value = new Clipboard(".copy-prompt");
|
||||
clipboard.value.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.value.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
clipboard.value.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
|
||||
getSystemInfo().then(res => {
|
||||
dallPower.value = res.data["dall_power"]
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
getSystemInfo()
|
||||
.then((res) => {
|
||||
dallPower.value = res.data["dall_power"];
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message);
|
||||
});
|
||||
|
||||
store.addMessageHandler("dall",(data) => {
|
||||
store.addMessageHandler("dall", (data) => {
|
||||
// 丢弃无关消息
|
||||
if (data.channel !== "dall" || data.clientId !== getClientId()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.body === "FINISH" || data.body === "FAIL") {
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
fetchFinishJobs()
|
||||
page.value = 0;
|
||||
isOver.value = false;
|
||||
fetchFinishJobs();
|
||||
}
|
||||
nextTick(() => fetchRunningJobs())
|
||||
})
|
||||
})
|
||||
nextTick(() => fetchRunningJobs());
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
store.removeMessageHandler("dall")
|
||||
})
|
||||
clipboard.value.destroy();
|
||||
store.removeMessageHandler("dall");
|
||||
});
|
||||
|
||||
const initData = () => {
|
||||
checkSession().then(user => {
|
||||
power.value = user['power']
|
||||
userId.value = user.id
|
||||
isLogin.value = true
|
||||
checkSession()
|
||||
.then((user) => {
|
||||
power.value = user["power"];
|
||||
userId.value = user.id;
|
||||
isLogin.value = true;
|
||||
|
||||
page.value = 0
|
||||
fetchRunningJobs()
|
||||
fetchFinishJobs()
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
page.value = 0;
|
||||
fetchRunningJobs();
|
||||
fetchFinishJobs();
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const fetchRunningJobs = () => {
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/dall/jobs?finish=false`).then(res => {
|
||||
runningJobs.value = res.data.items
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取任务失败:" + e.message)
|
||||
httpGet(`/api/dall/jobs?finish=false`)
|
||||
.then((res) => {
|
||||
runningJobs.value = res.data.items;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取任务失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(15)
|
||||
const page = ref(1);
|
||||
const pageSize = ref(15);
|
||||
// 获取已完成的任务
|
||||
const fetchFinishJobs = () => {
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
page.value = page.value + 1
|
||||
loading.value = true;
|
||||
page.value = page.value + 1;
|
||||
|
||||
httpGet(`/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(
|
||||
`/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.data.items.length < pageSize.value) {
|
||||
isOver.value = true
|
||||
isOver.value = true;
|
||||
}
|
||||
const imageList = res.data.items
|
||||
const imageList = res.data.items;
|
||||
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.value === 1) {
|
||||
finishedJobs.value = imageList
|
||||
finishedJobs.value = imageList;
|
||||
} else {
|
||||
finishedJobs.value = finishedJobs.value.concat(imageList)
|
||||
finishedJobs.value = finishedJobs.value.concat(imageList);
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取任务失败:" + e.message)
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取任务失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
// 创建绘图任务
|
||||
const promptRef = ref(null)
|
||||
const promptRef = ref(null);
|
||||
const generate = () => {
|
||||
if (params.value.prompt === '') {
|
||||
promptRef.value.focus()
|
||||
return ElMessage.error("请输入绘画提示词!")
|
||||
if (params.value.prompt === "") {
|
||||
promptRef.value.focus();
|
||||
return ElMessage.error("请输入绘画提示词!");
|
||||
}
|
||||
|
||||
if (!isLogin.value) {
|
||||
store.setShowLoginDialog(true)
|
||||
return
|
||||
store.setShowLoginDialog(true);
|
||||
return;
|
||||
}
|
||||
httpPost("/api/dall/image", params.value).then(() => {
|
||||
ElMessage.success("任务执行成功!")
|
||||
power.value -= dallPower.value
|
||||
fetchRunningJobs()
|
||||
}).catch(e => {
|
||||
ElMessage.error("任务执行失败:" + e.message)
|
||||
httpPost("/api/dall/image", params.value)
|
||||
.then(() => {
|
||||
ElMessage.success("任务执行成功!");
|
||||
power.value -= dallPower.value;
|
||||
fetchRunningJobs();
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("任务执行失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const removeImage = (item) => {
|
||||
ElMessageBox.confirm(
|
||||
'此操作将会删除任务和图片,继续操作码?',
|
||||
'删除提示',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
httpGet("/api/dall/remove", {id: item.id}).then(() => {
|
||||
ElMessage.success("任务删除成功")
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
fetchFinishJobs()
|
||||
}).catch(e => {
|
||||
ElMessage.error("任务删除失败:" + e.message)
|
||||
ElMessageBox.confirm("此操作将会删除任务和图片,继续操作码?", "删除提示", {
|
||||
confirmButtonText: "确认",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
}).catch(() => {
|
||||
.then(() => {
|
||||
httpGet("/api/dall/remove", { id: item.id })
|
||||
.then(() => {
|
||||
ElMessage.success("任务删除成功");
|
||||
page.value = 0;
|
||||
isOver.value = false;
|
||||
fetchFinishJobs();
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("任务删除失败:" + e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const previewImg = (item) => {
|
||||
previewURL.value = item.img_url
|
||||
}
|
||||
previewURL.value = item.img_url;
|
||||
};
|
||||
|
||||
// 发布图片到作品墙
|
||||
const publishImage = (item, action) => {
|
||||
let text = "图片发布"
|
||||
let text = "图片发布";
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
text = "取消发布";
|
||||
}
|
||||
httpGet("/api/dall/publish", {id: item.id, action: action}).then(() => {
|
||||
ElMessage.success(text + "成功")
|
||||
item.publish = action
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error(text + "失败:" + e.message)
|
||||
httpGet("/api/dall/publish", { id: item.id, action: action })
|
||||
.then(() => {
|
||||
ElMessage.success(text + "成功");
|
||||
item.publish = action;
|
||||
page.value = 0;
|
||||
isOver.value = false;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error(text + "失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const isGenerating = ref(false)
|
||||
const isGenerating = ref(false);
|
||||
const generatePrompt = () => {
|
||||
if (params.value.prompt === "") {
|
||||
return showMessageError("请输入原始提示词")
|
||||
return showMessageError("请输入原始提示词");
|
||||
}
|
||||
isGenerating.value = true
|
||||
httpPost("/api/prompt/image", {prompt: params.value.prompt}).then(res => {
|
||||
params.value.prompt = res.data
|
||||
isGenerating.value = false
|
||||
}).catch(e => {
|
||||
showMessageError("生成提示词失败:"+e.message)
|
||||
isGenerating.value = false
|
||||
isGenerating.value = true;
|
||||
httpPost("/api/prompt/image", { prompt: params.value.prompt })
|
||||
.then((res) => {
|
||||
params.value.prompt = res.data;
|
||||
isGenerating.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
.catch((e) => {
|
||||
showMessageError("生成提示词失败:" + e.message);
|
||||
isGenerating.value = false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
@ -180,7 +180,14 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- :style="{ 'padding-left': isCollapse ? '65px' : '170px' }" -->
|
||||
<div class="right-main">
|
||||
<div
|
||||
v-if="loginUser.id === undefined || !loginUser.id"
|
||||
class="loginMask"
|
||||
:style="{ left: isCollapse ? '65px' : '170px' }"
|
||||
@click="showNoticeLogin = true"
|
||||
></div>
|
||||
<div class="topheader" v-if="loginUser.id === undefined || !loginUser.id">
|
||||
<el-button
|
||||
@click="router.push('/login')"
|
||||
@ -189,6 +196,7 @@
|
||||
>登录</el-button
|
||||
>
|
||||
</div>
|
||||
<!-- <div class="content custom-scroll"> -->
|
||||
<div class="content custom-scroll">
|
||||
<router-view :key="routerViewKey" v-slot="{ Component }">
|
||||
<transition name="move" mode="out-in">
|
||||
@ -196,6 +204,7 @@
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
<config-dialog
|
||||
v-if="loginUser.id"
|
||||
@ -203,12 +212,21 @@
|
||||
@hide="showConfigDialog = false"
|
||||
/>
|
||||
</div>
|
||||
<el-dialog v-model="showNoticeLogin">
|
||||
<el-result icon="warning" title="未登录" sub-title="登录后解锁功能">
|
||||
<template #extra>
|
||||
<el-button type="primary" @click="router.push('/login')"
|
||||
>登录</el-button
|
||||
>
|
||||
</template>
|
||||
</el-result>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { CirclePlus, Setting } from "@element-plus/icons-vue";
|
||||
import ThemeChange from "@/components/ThemeChange.vue";
|
||||
import { avatarImg } from "@/assets/img/avatar.jpg";
|
||||
import avatarImg from "@/assets/img/avatar.jpg";
|
||||
import { useRouter } from "vue-router";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { httpGet } from "@/utils/http";
|
||||
@ -226,10 +244,37 @@ const router = useRouter();
|
||||
const logo = ref("");
|
||||
const mainNavs = ref([]);
|
||||
const moreNavs = ref([]);
|
||||
const curPath = ref(router.currentRoute.value.path);
|
||||
// const curPath = ref(router.currentRoute.value.path);
|
||||
const curPath = ref();
|
||||
|
||||
const title = ref("");
|
||||
const showNoticeLogin = ref(false);
|
||||
// const mainWinHeight = window.innerHeight - 50;
|
||||
|
||||
/**
|
||||
* 从路径名中提取第一个路径段
|
||||
* @param pathname - URL 的路径名部分,例如 '/chat/12345'
|
||||
* @returns 第一个路径段(不含斜杠),例如 'chat',如果不存在则返回 null
|
||||
*/
|
||||
const extractFirstSegment = (pathname) => {
|
||||
const segments = pathname.split("/").filter((segment) => segment.length > 0);
|
||||
return segments.length > 0 ? segments[0] : null;
|
||||
};
|
||||
const getFirstPathSegment = (url) => {
|
||||
try {
|
||||
// 尝试使用 URL 构造函数解析完整的 URL
|
||||
const parsedUrl = new URL(url);
|
||||
return extractFirstSegment(parsedUrl.pathname);
|
||||
} catch (error) {
|
||||
// 如果解析失败,假设是相对路径,使用当前窗口的位置作为基准
|
||||
if (typeof window !== "undefined") {
|
||||
const parsedUrl = new URL(url, window.location.origin);
|
||||
return extractFirstSegment(parsedUrl.pathname);
|
||||
}
|
||||
// 如果无法解析,返回 null
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const loginUser = ref({});
|
||||
const mainWinHeight = loginUser.value.id
|
||||
? window.innerHeight
|
||||
@ -252,10 +297,10 @@ watch(
|
||||
);
|
||||
|
||||
// 监听路由变化
|
||||
router.beforeEach((to, from, next) => {
|
||||
curPath.value = to.path;
|
||||
next();
|
||||
});
|
||||
// router.beforeEach((to, from, next) => {
|
||||
// curPath.value = to.path;
|
||||
// next();
|
||||
// });
|
||||
|
||||
if (curPath.value === "/external") {
|
||||
curPath.value = router.currentRoute.value.query.url;
|
||||
@ -302,7 +347,7 @@ onMounted(() => {
|
||||
license.value = { de_copy: false };
|
||||
showMessageError("获取 License 配置:" + e.message);
|
||||
});
|
||||
|
||||
curPath.value = "/" + getFirstPathSegment(window.location.href);
|
||||
init();
|
||||
});
|
||||
|
||||
|
@ -11,17 +11,21 @@
|
||||
<el-form-item label="采样方法">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-select v-model="params.sampler" style="width:176px">
|
||||
<el-option v-for="item in samplers" :label="item" :value="item" :key="item"/>
|
||||
<el-select v-model="params.sampler" style="width: 176px">
|
||||
<el-option
|
||||
v-for="item in samplers"
|
||||
:label="item"
|
||||
:value="item"
|
||||
:key="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="出图效果比较好的一般是 Euler 和 DPM 系列算法"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -33,17 +37,24 @@
|
||||
<el-form-item label="采样调度">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-select v-model="params.scheduler" style="width:176px">
|
||||
<el-option v-for="item in schedulers" :label="item" :value="item" :key="item"/>
|
||||
<el-select
|
||||
v-model="params.scheduler"
|
||||
style="width: 176px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in schedulers"
|
||||
:label="item"
|
||||
:value="item"
|
||||
:key="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="推荐自动或者 Karras"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -57,10 +68,16 @@
|
||||
<div class="form-item-inner">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-input v-model.number="params.width" placeholder="图片宽度"/>
|
||||
<el-input
|
||||
v-model.number="params.width"
|
||||
placeholder="图片宽度"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-input v-model.number="params.height" placeholder="图片高度"/>
|
||||
<el-input
|
||||
v-model.number="params.height"
|
||||
placeholder="图片高度"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@ -72,15 +89,14 @@
|
||||
<el-form-item label="迭代步数">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-input v-model.number="params.steps"/>
|
||||
<el-input v-model.number="params.steps" />
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="值越大则代表细节越多,同时也意味着出图速度越慢"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -92,15 +108,14 @@
|
||||
<el-form-item label="引导系数">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-input v-model.number="params.cfg_scale"/>
|
||||
<el-input v-model.number="params.cfg_scale" />
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="提示词引导系数,图像在多大程度上服从提示词<br/> 较低值会产生更有创意的结果"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -112,26 +127,24 @@
|
||||
<el-form-item label="随机因子">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-input v-model.number="params.seed"/>
|
||||
<el-input v-model.number="params.seed" />
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="随机数种子,相同的种子会得到相同的结果<br/> 设置为 -1 则每次随机生成种子"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="使用随机数"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon @click="params.seed = -1" class="info-icon">
|
||||
<Orange/>
|
||||
<Orange />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -143,15 +156,18 @@
|
||||
<el-form-item label="高清修复">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-switch v-model="params.hd_fix" style="--el-switch-on-color: #47fff1;" size="large"/>
|
||||
<el-switch
|
||||
v-model="params.hd_fix"
|
||||
style="--el-switch-on-color: #47fff1"
|
||||
size="large"
|
||||
/>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="先以较小的分辨率生成图像,接着方法图像<br />然后在不更改构图的情况下再修改细节"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon style="margin-left: 10px; top: 12px">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -164,16 +180,22 @@
|
||||
<el-form-item label="重绘幅度">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-slider v-model.number="params.hd_redraw_rate" :max="1" :step="0.1"
|
||||
style="width: 180px;--el-slider-main-bg-color:#47fff1"/>
|
||||
<el-slider
|
||||
v-model.number="params.hd_redraw_rate"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
style="
|
||||
width: 180px;
|
||||
--el-slider-main-bg-color: #47fff1;
|
||||
"
|
||||
/>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="决定算法对图像内容的影响程度<br />较大的值将得到越有创意的图像"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -185,17 +207,24 @@
|
||||
<el-form-item label="放大算法">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-select v-model="params.hd_scale_alg" style="width:176px">
|
||||
<el-option v-for="item in scaleAlg" :label="item" :value="item" :key="item"/>
|
||||
<el-select
|
||||
v-model="params.hd_scale_alg"
|
||||
style="width: 176px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in scaleAlg"
|
||||
:label="item"
|
||||
:value="item"
|
||||
:key="item"
|
||||
/>
|
||||
</el-select>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="高清修复放大算法,主流算法有Latent和ESRGAN_4x"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -207,15 +236,14 @@
|
||||
<el-form-item label="放大倍数">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-input v-model.number="params.hd_scale"/>
|
||||
<el-input v-model.number="params.hd_scale" />
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="随机数种子,相同的种子会得到相同的结果<br/> 设置为 -1 则每次随机生成种子"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -227,15 +255,14 @@
|
||||
<el-form-item label="迭代步数">
|
||||
<template #default>
|
||||
<div class="form-item-inner">
|
||||
<el-input v-model.number="params.hd_steps"/>
|
||||
<el-input v-model.number="params.hd_steps" />
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="重绘迭代步数,如果设置为0,则设置跟原图相同的迭代步数"
|
||||
raw-content
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -252,13 +279,22 @@
|
||||
ref="promptRef"
|
||||
placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
|
||||
v-loading="isGenerating"
|
||||
style="--el-mask-color:rgba(100, 100, 100, 0.8)"
|
||||
style="--el-mask-color: rgba(100, 100, 100, 0.8)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-row class="text-info">
|
||||
<el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2" :disabled="isGenerating">
|
||||
<i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
|
||||
<el-button
|
||||
class="generate-btn"
|
||||
size="small"
|
||||
@click="generatePrompt"
|
||||
color="#5865f2"
|
||||
:disabled="isGenerating"
|
||||
>
|
||||
<i
|
||||
class="iconfont icon-chuangzuo"
|
||||
style="margin-right: 5px"
|
||||
></i>
|
||||
<span>生成专业绘画指令</span>
|
||||
</el-button>
|
||||
</el-row>
|
||||
@ -266,12 +302,11 @@
|
||||
<div class="param-line pt">
|
||||
<span>反向提示词:</span>
|
||||
<el-tooltip
|
||||
effect="light"
|
||||
content="不希望出现的元素,下面给了默认的起手式"
|
||||
placement="right"
|
||||
>
|
||||
<el-icon class="info-icon">
|
||||
<InfoFilled/>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -287,26 +322,38 @@
|
||||
<div class="text-info">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="12">
|
||||
<el-tag>单次绘图消耗{{ sdPower }}算力</el-tag>
|
||||
<el-text type="primary"
|
||||
>单次绘图消耗
|
||||
<el-text type="warning">{{ sdPower }}算力;</el-text>
|
||||
</el-text>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-tag type="success">当前可用{{ power }}算力</el-tag>
|
||||
<el-text type="primary"
|
||||
>当前可用
|
||||
<el-text type="warning">
|
||||
{{ power }}算力</el-text
|
||||
></el-text
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="submit-btn">
|
||||
<el-button color="#47fff1" :dark="false" round @click="generate">立即生成</el-button>
|
||||
<el-button type="primary" :dark="false" round @click="generate"
|
||||
>立即生成</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<h2>任务列表</h2>
|
||||
<task-list :list="runningJobs" />
|
||||
|
||||
<template v-if="finishedJobs.length > 0">
|
||||
<h2>创作记录</h2>
|
||||
<div class="finish-job-list">
|
||||
<div v-if="finishedJobs.length > 0">
|
||||
@ -320,7 +367,8 @@
|
||||
:distanceToScroll="100"
|
||||
:isLoading="loading"
|
||||
:isOver="isOver"
|
||||
@scrollReachBottom="fetchFinishJobs()">
|
||||
@scrollReachBottom="fetchFinishJobs()"
|
||||
>
|
||||
<template #default="slotProp">
|
||||
<div class="job-item animate">
|
||||
<el-image v-if="slotProp.item.progress === 101">
|
||||
@ -329,12 +377,22 @@
|
||||
<div class="err-msg-container">
|
||||
<div class="title">任务失败</div>
|
||||
<div class="opt">
|
||||
<el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
|
||||
<el-popover
|
||||
title="错误详情"
|
||||
trigger="click"
|
||||
:width="250"
|
||||
:content="slotProp.item['err_msg']"
|
||||
placement="top"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button type="info">详情</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-button type="danger" @click="removeImage(slotProp.item)">删除</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
@click="removeImage(slotProp.item)"
|
||||
>删除</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -345,15 +403,29 @@
|
||||
:src="slotProp.item['img_thumb']"
|
||||
@click="showTask(slotProp.item)"
|
||||
fit="cover"
|
||||
loading="lazy"/>
|
||||
loading="lazy"
|
||||
/>
|
||||
<div class="remove">
|
||||
<el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
|
||||
<el-button type="warning" v-if="slotProp.item.publish"
|
||||
<el-button
|
||||
type="danger"
|
||||
:icon="Delete"
|
||||
@click="removeImage(slotProp.item)"
|
||||
circle
|
||||
/>
|
||||
<el-button
|
||||
type="warning"
|
||||
v-if="slotProp.item.publish"
|
||||
@click="publishImage(slotProp.item, false)"
|
||||
circle>
|
||||
circle
|
||||
>
|
||||
<i class="iconfont icon-cancel-share"></i>
|
||||
</el-button>
|
||||
<el-button type="success" v-else @click="publishImage(slotProp.item, true)" circle>
|
||||
<el-button
|
||||
type="success"
|
||||
v-else
|
||||
@click="publishImage(slotProp.item, true)"
|
||||
circle
|
||||
>
|
||||
<i class="iconfont icon-share-bold"></i>
|
||||
</el-button>
|
||||
</div>
|
||||
@ -368,47 +440,63 @@
|
||||
</div>
|
||||
</template>
|
||||
</v3-waterfall>
|
||||
</div>
|
||||
<el-empty
|
||||
:image-size="100"
|
||||
v-else
|
||||
:image="nodata"
|
||||
description="暂无记录"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
</div> <!-- end finish job list-->
|
||||
<!-- end finish job list-->
|
||||
</div>
|
||||
</div>
|
||||
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
||||
</div><!-- end task list box -->
|
||||
<back-top :right="30" :bottom="30" />
|
||||
</div>
|
||||
<!-- end task list box -->
|
||||
</div>
|
||||
|
||||
<!-- 任务详情弹框 -->
|
||||
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
|
||||
<el-dialog
|
||||
v-model="showTaskDialog"
|
||||
title="绘画任务详情"
|
||||
:fullscreen="true"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<div class="img-container" :style="{maxHeight: fullImgHeight+'px'}">
|
||||
<el-image :src="item['img_url']" fit="contain"/>
|
||||
<div
|
||||
class="img-container"
|
||||
:style="{ maxHeight: fullImgHeight + 'px' }"
|
||||
>
|
||||
<el-image :src="item['img_url']" fit="contain" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="task-info">
|
||||
<div class="info-line">
|
||||
<el-divider>
|
||||
正向提示词
|
||||
</el-divider>
|
||||
<el-divider> 正向提示词 </el-divider>
|
||||
<div class="prompt">
|
||||
<span>{{ item.prompt }}</span>
|
||||
<el-icon class="copy-prompt-sd" :data-clipboard-text="item.prompt">
|
||||
<DocumentCopy/>
|
||||
<el-icon
|
||||
class="copy-prompt-sd"
|
||||
:data-clipboard-text="item.prompt"
|
||||
>
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="info-line">
|
||||
<el-divider>
|
||||
反向提示词
|
||||
</el-divider>
|
||||
<el-divider> 反向提示词 </el-divider>
|
||||
<div class="prompt">
|
||||
<span>{{ item.params.neg_prompt }}</span>
|
||||
<el-icon class="copy-prompt-sd" :data-clipboard-text="item.params.neg_prompt">
|
||||
<DocumentCopy/>
|
||||
<el-icon
|
||||
class="copy-prompt-sd"
|
||||
:data-clipboard-text="item.params.neg_prompt"
|
||||
>
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
@ -423,7 +511,9 @@
|
||||
<div class="info-line">
|
||||
<div class="wrapper">
|
||||
<label>图片尺寸:</label>
|
||||
<div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
|
||||
<div class="item-value">
|
||||
{{ item.params.width }} x {{ item.params.height }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -449,13 +539,13 @@
|
||||
</div>
|
||||
|
||||
<div v-if="item.params.hd_fix">
|
||||
<el-divider>
|
||||
高清修复
|
||||
</el-divider>
|
||||
<el-divider> 高清修复 </el-divider>
|
||||
<div class="info-line">
|
||||
<div class="wrapper">
|
||||
<label>重绘幅度:</label>
|
||||
<div class="item-value">{{ item.params.hd_redraw_rate }}</div>
|
||||
<div class="item-value">
|
||||
{{ item.params.hd_redraw_rate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -482,53 +572,68 @@
|
||||
</div>
|
||||
|
||||
<div class="copy-params">
|
||||
<el-button type="primary" round @click="copyParams(item)">画一张同款的</el-button>
|
||||
<el-button type="primary" round @click="copyParams(item)"
|
||||
>画一张同款的</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
|
||||
import {Delete, DocumentCopy, InfoFilled, Orange} from "@element-plus/icons-vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||
import {
|
||||
Delete,
|
||||
DocumentCopy,
|
||||
InfoFilled,
|
||||
Orange
|
||||
} from "@element-plus/icons-vue";
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import Clipboard from "clipboard";
|
||||
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
|
||||
import {useRouter} from "vue-router";
|
||||
import {getSessionId} from "@/store/session";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import { checkSession, getClientId, getSystemInfo } from "@/store/cache";
|
||||
import { useRouter } from "vue-router";
|
||||
import { getSessionId } from "@/store/session";
|
||||
import { useSharedStore } from "@/store/sharedata";
|
||||
import TaskList from "@/components/TaskList.vue";
|
||||
import BackTop from "@/components/BackTop.vue";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import { showMessageError } from "@/utils/dialog";
|
||||
|
||||
const listBoxHeight = ref(0)
|
||||
const listBoxHeight = ref(0);
|
||||
// const paramBoxHeight = ref(0)
|
||||
const fullImgHeight = ref(window.innerHeight - 60)
|
||||
const showTaskDialog = ref(false)
|
||||
const item = ref({})
|
||||
const isLogin = ref(false)
|
||||
const loading = ref(true)
|
||||
const colWidth = ref(220)
|
||||
const fullImgHeight = ref(window.innerHeight - 60);
|
||||
const showTaskDialog = ref(false);
|
||||
const item = ref({});
|
||||
const isLogin = ref(false);
|
||||
const loading = ref(true);
|
||||
const colWidth = ref(220);
|
||||
const store = useSharedStore();
|
||||
|
||||
const resizeElement = function () {
|
||||
listBoxHeight.value = window.innerHeight - 80
|
||||
listBoxHeight.value = window.innerHeight - 80;
|
||||
// paramBoxHeight.value = window.innerHeight - 200
|
||||
};
|
||||
resizeElement()
|
||||
resizeElement();
|
||||
window.onresize = () => {
|
||||
resizeElement()
|
||||
}
|
||||
const samplers = ["Euler a", "DPM++ 2S a", "DPM++ 2M", "DPM++ SDE", "DPM++ 2M SDE", "UniPC", "Restart"]
|
||||
const schedulers = ["Automatic", "Karras", "Exponential", "Uniform"]
|
||||
const scaleAlg = ["Latent", "ESRGAN_4x", "R-ESRGAN 4x+", "SwinIR_4x", "LDSR"]
|
||||
resizeElement();
|
||||
};
|
||||
const samplers = [
|
||||
"Euler a",
|
||||
"DPM++ 2S a",
|
||||
"DPM++ 2M",
|
||||
"DPM++ SDE",
|
||||
"DPM++ 2M SDE",
|
||||
"UniPC",
|
||||
"Restart"
|
||||
];
|
||||
const schedulers = ["Automatic", "Karras", "Exponential", "Uniform"];
|
||||
const scaleAlg = ["Latent", "ESRGAN_4x", "R-ESRGAN 4x+", "SwinIR_4x", "LDSR"];
|
||||
const params = ref({
|
||||
client_id: getClientId(),
|
||||
width: 1024,
|
||||
@ -544,209 +649,221 @@ const params = ref({
|
||||
hd_scale_alg: scaleAlg[0],
|
||||
hd_steps: 0,
|
||||
prompt: "",
|
||||
neg_prompt: "nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet",
|
||||
})
|
||||
neg_prompt:
|
||||
"nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet"
|
||||
});
|
||||
|
||||
const runningJobs = ref([])
|
||||
const finishedJobs = ref([])
|
||||
const router = useRouter()
|
||||
const runningJobs = ref([]);
|
||||
const finishedJobs = ref([]);
|
||||
const router = useRouter();
|
||||
// 检查是否有画同款的参数
|
||||
const _params = router.currentRoute.value.params["copyParams"]
|
||||
const _params = router.currentRoute.value.params["copyParams"];
|
||||
if (_params) {
|
||||
params.value = JSON.parse(_params)
|
||||
params.value = JSON.parse(_params);
|
||||
}
|
||||
const power = ref(0)
|
||||
const sdPower = ref(0) // 画一张 SD 图片消耗算力
|
||||
const power = ref(0);
|
||||
const sdPower = ref(0); // 画一张 SD 图片消耗算力
|
||||
|
||||
const userId = ref(0)
|
||||
const clipboard = ref(null)
|
||||
const userId = ref(0);
|
||||
const clipboard = ref(null);
|
||||
onMounted(() => {
|
||||
initData()
|
||||
clipboard.value = new Clipboard('.copy-prompt-sd');
|
||||
clipboard.value.on('success', () => {
|
||||
initData();
|
||||
clipboard.value = new Clipboard(".copy-prompt-sd");
|
||||
clipboard.value.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.value.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
clipboard.value.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
|
||||
getSystemInfo().then(res => {
|
||||
sdPower.value = res.data.sd_power
|
||||
params.value.neg_prompt = res.data.sd_neg_prompt
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
getSystemInfo()
|
||||
.then((res) => {
|
||||
sdPower.value = res.data.sd_power;
|
||||
params.value.neg_prompt = res.data.sd_neg_prompt;
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message);
|
||||
});
|
||||
|
||||
store.addMessageHandler("sd",(data) => {
|
||||
store.addMessageHandler("sd", (data) => {
|
||||
// 丢弃无关消息
|
||||
if (data.channel !== "sd" || data.clientId !== getClientId()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.body === "FINISH" || data.body === "FAIL") {
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
fetchFinishJobs()
|
||||
page.value = 0;
|
||||
isOver.value = false;
|
||||
fetchFinishJobs();
|
||||
}
|
||||
nextTick(() => fetchRunningJobs())
|
||||
})
|
||||
|
||||
})
|
||||
nextTick(() => fetchRunningJobs());
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
store.removeMessageHandler("sd")
|
||||
})
|
||||
|
||||
clipboard.value.destroy();
|
||||
store.removeMessageHandler("sd");
|
||||
});
|
||||
|
||||
const initData = () => {
|
||||
checkSession().then(user => {
|
||||
power.value = user['power']
|
||||
userId.value = user.id
|
||||
isLogin.value = true
|
||||
page.value = 0
|
||||
fetchRunningJobs()
|
||||
fetchFinishJobs()
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
checkSession()
|
||||
.then((user) => {
|
||||
power.value = user["power"];
|
||||
userId.value = user.id;
|
||||
isLogin.value = true;
|
||||
page.value = 0;
|
||||
fetchRunningJobs();
|
||||
fetchFinishJobs();
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const fetchRunningJobs = () => {
|
||||
if (!isLogin.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取运行中的任务
|
||||
httpGet(`/api/sd/jobs?finish=0`).then(res => {
|
||||
runningJobs.value = res.data.items
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取任务失败:" + e.message)
|
||||
httpGet(`/api/sd/jobs?finish=0`)
|
||||
.then((res) => {
|
||||
runningJobs.value = res.data.items;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取任务失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const page = ref(0)
|
||||
const pageSize = ref(20)
|
||||
const isOver = ref(false)
|
||||
const page = ref(0);
|
||||
const pageSize = ref(20);
|
||||
const isOver = ref(false);
|
||||
// 获取已完成的任务
|
||||
const fetchFinishJobs = () => {
|
||||
if (!isLogin.value || isOver.value === true) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
loading.value = true
|
||||
page.value = page.value + 1
|
||||
loading.value = true;
|
||||
page.value = page.value + 1;
|
||||
|
||||
httpGet(`/api/sd/jobs?finish=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
httpGet(
|
||||
`/api/sd/jobs?finish=1&page=${page.value}&page_size=${pageSize.value}`
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.data.items.length < pageSize.value) {
|
||||
isOver.value = true
|
||||
isOver.value = true;
|
||||
}
|
||||
const imageList = res.data.items
|
||||
const imageList = res.data.items;
|
||||
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.value === 1) {
|
||||
finishedJobs.value = imageList
|
||||
finishedJobs.value = imageList;
|
||||
} else {
|
||||
finishedJobs.value = finishedJobs.value.concat(imageList)
|
||||
finishedJobs.value = finishedJobs.value.concat(imageList);
|
||||
}
|
||||
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取任务失败:" + e.message)
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取任务失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
// 创建绘图任务
|
||||
const promptRef = ref(null)
|
||||
const promptRef = ref(null);
|
||||
const generate = () => {
|
||||
if (params.value.prompt === '') {
|
||||
promptRef.value.focus()
|
||||
return ElMessage.error("请输入绘画提示词!")
|
||||
if (params.value.prompt === "") {
|
||||
promptRef.value.focus();
|
||||
return ElMessage.error("请输入绘画提示词!");
|
||||
}
|
||||
|
||||
if (!isLogin.value) {
|
||||
store.setShowLoginDialog(true)
|
||||
return
|
||||
store.setShowLoginDialog(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!params.value.seed) {
|
||||
params.value.seed = -1
|
||||
params.value.seed = -1;
|
||||
}
|
||||
params.value.session_id = getSessionId()
|
||||
httpPost("/api/sd/image", params.value).then(() => {
|
||||
ElMessage.success("绘画任务推送成功,请耐心等待任务执行...")
|
||||
power.value -= sdPower.value
|
||||
fetchRunningJobs()
|
||||
}).catch(e => {
|
||||
ElMessage.error("任务推送失败:" + e.message)
|
||||
params.value.session_id = getSessionId();
|
||||
httpPost("/api/sd/image", params.value)
|
||||
.then(() => {
|
||||
ElMessage.success("绘画任务推送成功,请耐心等待任务执行...");
|
||||
power.value -= sdPower.value;
|
||||
fetchRunningJobs();
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("任务推送失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const showTask = (row) => {
|
||||
item.value = row
|
||||
showTaskDialog.value = true
|
||||
}
|
||||
item.value = row;
|
||||
showTaskDialog.value = true;
|
||||
};
|
||||
|
||||
const copyParams = (row) => {
|
||||
params.value = row.params
|
||||
showTaskDialog.value = false
|
||||
}
|
||||
params.value = row.params;
|
||||
showTaskDialog.value = false;
|
||||
};
|
||||
|
||||
const removeImage = (item) => {
|
||||
ElMessageBox.confirm(
|
||||
'此操作将会删除任务和图片,继续操作码?',
|
||||
'删除提示',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
).then(() => {
|
||||
httpGet("/api/sd/remove", {id: item.id}).then(() => {
|
||||
ElMessage.success("任务删除成功")
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
fetchFinishJobs()
|
||||
}).catch(e => {
|
||||
ElMessage.error("任务删除失败:" + e.message)
|
||||
ElMessageBox.confirm("此操作将会删除任务和图片,继续操作码?", "删除提示", {
|
||||
confirmButtonText: "确认",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
}).catch(() => {
|
||||
.then(() => {
|
||||
httpGet("/api/sd/remove", { id: item.id })
|
||||
.then(() => {
|
||||
ElMessage.success("任务删除成功");
|
||||
page.value = 0;
|
||||
isOver.value = false;
|
||||
fetchFinishJobs();
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("任务删除失败:" + e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
// 发布图片到作品墙
|
||||
const publishImage = (item, action) => {
|
||||
let text = "图片发布"
|
||||
let text = "图片发布";
|
||||
if (action === false) {
|
||||
text = "取消发布"
|
||||
text = "取消发布";
|
||||
}
|
||||
httpGet("/api/sd/publish", {id: item.id, action: action}).then(() => {
|
||||
ElMessage.success(text + "成功")
|
||||
item.publish = action
|
||||
page.value = 0
|
||||
isOver.value = false
|
||||
item.publish = action
|
||||
}).catch(e => {
|
||||
ElMessage.error(text + "失败:" + e.message)
|
||||
httpGet("/api/sd/publish", { id: item.id, action: action })
|
||||
.then(() => {
|
||||
ElMessage.success(text + "成功");
|
||||
item.publish = action;
|
||||
page.value = 0;
|
||||
isOver.value = false;
|
||||
item.publish = action;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error(text + "失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const isGenerating = ref(false)
|
||||
const isGenerating = ref(false);
|
||||
const generatePrompt = () => {
|
||||
if (params.value.prompt === "") {
|
||||
return showMessageError("请输入原始提示词")
|
||||
return showMessageError("请输入原始提示词");
|
||||
}
|
||||
isGenerating.value = true
|
||||
httpPost("/api/prompt/image", {prompt: params.value.prompt}).then(res => {
|
||||
params.value.prompt = res.data
|
||||
isGenerating.value = false
|
||||
}).catch(e => {
|
||||
showMessageError("生成提示词失败:"+e.message)
|
||||
isGenerating.value = false
|
||||
isGenerating.value = true;
|
||||
httpPost("/api/prompt/image", { prompt: params.value.prompt })
|
||||
.then((res) => {
|
||||
params.value.prompt = res.data;
|
||||
isGenerating.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
.catch((e) => {
|
||||
showMessageError("生成提示词失败:" + e.message);
|
||||
isGenerating.value = false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
@ -11,8 +11,13 @@
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="waterfall" :style="{ height:listBoxHeight + 'px' }" id="waterfall-box">
|
||||
<v3-waterfall v-if="imgType === 'mj'"
|
||||
<div
|
||||
class="waterfall"
|
||||
:style="{ height: listBoxHeight + 'px' }"
|
||||
id="waterfall-box"
|
||||
>
|
||||
<v3-waterfall
|
||||
v-if="imgType === 'mj'"
|
||||
id="waterfall"
|
||||
:list="data['mj']"
|
||||
srcKey="img_thumb"
|
||||
@ -22,26 +27,27 @@
|
||||
:distanceToScroll="100"
|
||||
:isLoading="loading"
|
||||
:isOver="isOver"
|
||||
@scrollReachBottom="getNext">
|
||||
@scrollReachBottom="getNext"
|
||||
>
|
||||
<template #default="slotProp">
|
||||
<div class="list-item">
|
||||
<div class="image">
|
||||
<el-image :src="slotProp.item['img_thumb']"
|
||||
<el-image
|
||||
:src="slotProp.item['img_thumb']"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="[slotProp.item['img_url']]"
|
||||
:preview-teleported="true"
|
||||
:initial-index="10"
|
||||
loading="lazy">
|
||||
loading="lazy"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
<div class="image-slot">正在加载图片</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
<Picture />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
@ -50,29 +56,30 @@
|
||||
<div class="opt">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="复制提示词"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="copy-prompt-wall" :data-clipboard-text="slotProp.item.prompt">
|
||||
<DocumentCopy/>
|
||||
<el-icon
|
||||
class="copy-prompt-wall"
|
||||
:data-clipboard-text="slotProp.item.prompt"
|
||||
>
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="画同款"
|
||||
placement="top"
|
||||
>
|
||||
<i class="iconfont icon-palette-pen" @click="drawSameMj(slotProp.item)"></i>
|
||||
<el-tooltip class="box-item" content="画同款" placement="top">
|
||||
<i
|
||||
class="iconfont icon-palette-pen"
|
||||
@click="drawSameMj(slotProp.item)"
|
||||
></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v3-waterfall>
|
||||
|
||||
<v3-waterfall v-else-if="imgType === 'dall'"
|
||||
<v3-waterfall
|
||||
v-else-if="imgType === 'dall'"
|
||||
id="waterfall"
|
||||
:list="data['dall']"
|
||||
srcKey="img_thumb"
|
||||
@ -82,26 +89,27 @@
|
||||
:distanceToScroll="100"
|
||||
:isLoading="loading"
|
||||
:isOver="isOver"
|
||||
@scrollReachBottom="getNext">
|
||||
@scrollReachBottom="getNext"
|
||||
>
|
||||
<template #default="slotProp">
|
||||
<div class="list-item">
|
||||
<div class="image">
|
||||
<el-image :src="slotProp.item['img_thumb']"
|
||||
<el-image
|
||||
:src="slotProp.item['img_thumb']"
|
||||
:zoom-rate="1.2"
|
||||
:preview-src-list="[slotProp.item['img_url']]"
|
||||
:preview-teleported="true"
|
||||
:initial-index="10"
|
||||
loading="lazy">
|
||||
loading="lazy"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
<div class="image-slot">正在加载图片</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
<Picture />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
@ -110,12 +118,14 @@
|
||||
<div class="opt">
|
||||
<el-tooltip
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="复制提示词"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="copy-prompt-wall" :data-clipboard-text="slotProp.item.prompt">
|
||||
<DocumentCopy/>
|
||||
<el-icon
|
||||
class="copy-prompt-wall"
|
||||
:data-clipboard-text="slotProp.item.prompt"
|
||||
>
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@ -123,7 +133,8 @@
|
||||
</template>
|
||||
</v3-waterfall>
|
||||
|
||||
<v3-waterfall v-else
|
||||
<v3-waterfall
|
||||
v-else
|
||||
id="waterfall"
|
||||
:list="data['sd']"
|
||||
srcKey="img_thumb"
|
||||
@ -133,22 +144,24 @@
|
||||
:distanceToScroll="100"
|
||||
:isLoading="loading"
|
||||
:isOver="isOver"
|
||||
@scrollReachBottom="getNext">
|
||||
@scrollReachBottom="getNext"
|
||||
>
|
||||
<template #default="slotProp">
|
||||
<div class="list-item">
|
||||
<div class="image">
|
||||
<el-image :src="slotProp.item['img_thumb']" loading="lazy"
|
||||
@click="showTask(slotProp.item)">
|
||||
<el-image
|
||||
:src="slotProp.item['img_thumb']"
|
||||
loading="lazy"
|
||||
@click="showTask(slotProp.item)"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
<div class="image-slot">正在加载图片</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
<Picture />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
@ -159,31 +172,36 @@
|
||||
</v3-waterfall>
|
||||
|
||||
<div class="footer" v-if="isOver">
|
||||
<span>没有更多数据了</span>
|
||||
<i class="iconfont icon-face"></i>
|
||||
<el-empty
|
||||
:image-size="100"
|
||||
:image="nodata"
|
||||
description="没有更多数据了"
|
||||
/>
|
||||
<!-- <span>没有更多数据了</span>
|
||||
<i class="iconfont icon-face"></i> -->
|
||||
</div>
|
||||
|
||||
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
||||
|
||||
</div><!-- end of waterfall -->
|
||||
|
||||
<back-top :right="30" :bottom="30" />
|
||||
</div>
|
||||
<!-- end of waterfall -->
|
||||
</div>
|
||||
<!-- 任务详情弹框 -->
|
||||
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="16">
|
||||
<div class="img-container" :style="{maxHeight: fullImgHeight+'px'}">
|
||||
<div
|
||||
class="img-container"
|
||||
:style="{ maxHeight: fullImgHeight + 'px' }"
|
||||
>
|
||||
<el-image :src="item['img_url']" fit="contain">
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
正在加载图片
|
||||
</div>
|
||||
<div class="image-slot">正在加载图片</div>
|
||||
</template>
|
||||
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
<Picture />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
@ -193,26 +211,27 @@
|
||||
<el-col :span="8">
|
||||
<div class="task-info">
|
||||
<div class="info-line">
|
||||
<el-divider>
|
||||
正向提示词
|
||||
</el-divider>
|
||||
<el-divider> 正向提示词 </el-divider>
|
||||
<div class="prompt">
|
||||
<span>{{ item.prompt }}</span>
|
||||
<el-icon class="copy-prompt-wall" :data-clipboard-text="item.prompt">
|
||||
<DocumentCopy/>
|
||||
<el-icon
|
||||
class="copy-prompt-wall"
|
||||
:data-clipboard-text="item.prompt"
|
||||
>
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="info-line">
|
||||
<el-divider>
|
||||
反向提示词
|
||||
</el-divider>
|
||||
<el-divider> 反向提示词 </el-divider>
|
||||
<div class="prompt">
|
||||
<span>{{ item.params.negative_prompt }}</span>
|
||||
<el-icon class="copy-prompt-wall" :data-clipboard-text="item.params.negative_prompt">
|
||||
<DocumentCopy/>
|
||||
<el-icon
|
||||
class="copy-prompt-wall"
|
||||
:data-clipboard-text="item.params.negative_prompt"
|
||||
>
|
||||
<DocumentCopy />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
@ -227,7 +246,9 @@
|
||||
<div class="info-line">
|
||||
<div class="wrapper">
|
||||
<label>图片尺寸:</label>
|
||||
<div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
|
||||
<div class="item-value">
|
||||
{{ item.params.width }} x {{ item.params.height }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -253,9 +274,7 @@
|
||||
</div>
|
||||
|
||||
<div v-if="item.params.hd_fix">
|
||||
<el-divider>
|
||||
高清修复
|
||||
</el-divider>
|
||||
<el-divider> 高清修复 </el-divider>
|
||||
<div class="info-line">
|
||||
<div class="wrapper">
|
||||
<label>重绘幅度:</label>
|
||||
@ -286,146 +305,151 @@
|
||||
</div>
|
||||
|
||||
<div class="copy-params">
|
||||
<el-button type="primary" round @click="drawSameSd(item)">画一张同款的</el-button>
|
||||
<el-button type="primary" round @click="drawSameSd(item)"
|
||||
>画一张同款的</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
|
||||
import {DocumentCopy, Picture} from "@element-plus/icons-vue";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||
import { DocumentCopy, Picture } from "@element-plus/icons-vue";
|
||||
import { httpGet } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Clipboard from "clipboard";
|
||||
import {useRouter} from "vue-router";
|
||||
import { useRouter } from "vue-router";
|
||||
import BackTop from "@/components/BackTop.vue";
|
||||
|
||||
const data = ref({
|
||||
"mj": [],
|
||||
"sd": [],
|
||||
"dall": [],
|
||||
})
|
||||
const loading = ref(true)
|
||||
const isOver = ref(false)
|
||||
const imgType = ref("mj") // 图片类别
|
||||
const listBoxHeight = window.innerHeight - 124
|
||||
const colWidth = ref(220)
|
||||
const fullImgHeight = ref(window.innerHeight - 60)
|
||||
const showTaskDialog = ref(false)
|
||||
const item = ref({})
|
||||
mj: [],
|
||||
sd: [],
|
||||
dall: []
|
||||
});
|
||||
const loading = ref(true);
|
||||
const isOver = ref(false);
|
||||
const imgType = ref("mj"); // 图片类别
|
||||
const listBoxHeight = window.innerHeight - 124;
|
||||
const colWidth = ref(220);
|
||||
const fullImgHeight = ref(window.innerHeight - 60);
|
||||
const showTaskDialog = ref(false);
|
||||
const item = ref({});
|
||||
|
||||
// 计算瀑布流列宽度
|
||||
const calcColWidth = () => {
|
||||
const listBoxWidth = window.innerWidth - 60 - 80
|
||||
const rows = Math.floor(listBoxWidth / colWidth.value)
|
||||
colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows)
|
||||
}
|
||||
calcColWidth()
|
||||
const listBoxWidth = window.innerWidth - 60 - 80;
|
||||
const rows = Math.floor(listBoxWidth / colWidth.value);
|
||||
colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows);
|
||||
};
|
||||
calcColWidth();
|
||||
window.onresize = () => {
|
||||
calcColWidth()
|
||||
}
|
||||
calcColWidth();
|
||||
};
|
||||
|
||||
const page = ref(0)
|
||||
const pageSize = ref(15)
|
||||
const page = ref(0);
|
||||
const pageSize = ref(15);
|
||||
// 获取下一页数据
|
||||
const getNext = () => {
|
||||
if (isOver.value) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
page.value = page.value + 1
|
||||
let url = ""
|
||||
loading.value = true;
|
||||
page.value = page.value + 1;
|
||||
let url = "";
|
||||
switch (imgType.value) {
|
||||
case "mj":
|
||||
url = "/api/mj/imgWall"
|
||||
break
|
||||
url = "/api/mj/imgWall";
|
||||
break;
|
||||
case "sd":
|
||||
url = "/api/sd/imgWall"
|
||||
break
|
||||
url = "/api/sd/imgWall";
|
||||
break;
|
||||
case "dall":
|
||||
url = "/api/dall/imgWall"
|
||||
break
|
||||
url = "/api/dall/imgWall";
|
||||
break;
|
||||
}
|
||||
httpGet(`${url}?page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
||||
loading.value = false
|
||||
httpGet(`${url}?page=${page.value}&page_size=${pageSize.value}`)
|
||||
.then((res) => {
|
||||
loading.value = false;
|
||||
if (!res.data.items || res.data.items.length === 0) {
|
||||
isOver.value = true
|
||||
return
|
||||
isOver.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成缩略图
|
||||
const imageList = res.data.items
|
||||
const imageList = res.data.items;
|
||||
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 (data.value[imgType.value].length === 0) {
|
||||
data.value[imgType.value] = imageList
|
||||
return
|
||||
data.value[imgType.value] = imageList;
|
||||
return;
|
||||
}
|
||||
|
||||
if (imageList.length < pageSize.value) {
|
||||
isOver.value = true
|
||||
isOver.value = true;
|
||||
}
|
||||
data.value[imgType.value] = data.value[imgType.value].concat(imageList)
|
||||
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取图片失败:" + e.message)
|
||||
data.value[imgType.value] = data.value[imgType.value].concat(imageList);
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取图片失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
getNext()
|
||||
getNext();
|
||||
|
||||
const clipboard = ref(null)
|
||||
const clipboard = ref(null);
|
||||
onMounted(() => {
|
||||
clipboard.value = new Clipboard('.copy-prompt-wall');
|
||||
clipboard.value.on('success', () => {
|
||||
clipboard.value = new Clipboard(".copy-prompt-wall");
|
||||
clipboard.value.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.value.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
clipboard.value.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
})
|
||||
clipboard.value.destroy();
|
||||
});
|
||||
|
||||
const changeImgType = () => {
|
||||
console.log(imgType.value)
|
||||
document.getElementById('waterfall-box').scrollTo(0, 0)
|
||||
page.value = 0
|
||||
console.log(imgType.value);
|
||||
document.getElementById("waterfall-box").scrollTo(0, 0);
|
||||
page.value = 0;
|
||||
data.value = {
|
||||
"mj": [],
|
||||
"sd": [],
|
||||
"dall": [],
|
||||
}
|
||||
loading.value = true
|
||||
isOver.value = false
|
||||
nextTick(() => getNext())
|
||||
}
|
||||
mj: [],
|
||||
sd: [],
|
||||
dall: []
|
||||
};
|
||||
loading.value = true;
|
||||
isOver.value = false;
|
||||
nextTick(() => getNext());
|
||||
};
|
||||
|
||||
const showTask = (row) => {
|
||||
item.value = row
|
||||
showTaskDialog.value = true
|
||||
}
|
||||
item.value = row;
|
||||
showTaskDialog.value = true;
|
||||
};
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
const router = useRouter();
|
||||
const drawSameSd = (row) => {
|
||||
router.push({name: "image-sd", params: {copyParams: JSON.stringify(row.params)}})
|
||||
}
|
||||
router.push({
|
||||
name: "image-sd",
|
||||
params: { copyParams: JSON.stringify(row.params) }
|
||||
});
|
||||
};
|
||||
|
||||
const drawSameMj = (row) => {
|
||||
router.push({name: "image-mj", params: {prompt: row.prompt}})
|
||||
}
|
||||
router.push({ name: "image-mj", params: { prompt: row.prompt } });
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
@ -15,7 +15,6 @@
|
||||
<el-tooltip
|
||||
v-if="!license.de_copy"
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="部署文档"
|
||||
placement="bottom"
|
||||
>
|
||||
@ -26,7 +25,6 @@
|
||||
<el-tooltip
|
||||
v-if="!license.de_copy"
|
||||
class="box-item"
|
||||
effect="light"
|
||||
content="项目源码"
|
||||
placement="bottom"
|
||||
>
|
||||
|
@ -5,18 +5,25 @@
|
||||
<h2>会员推广计划</h2>
|
||||
<div class="share-box">
|
||||
<div class="info">
|
||||
我们非常欢迎您把此应用分享给您身边的朋友,分享成功注册后您和被邀请人都将获得 <strong>{{ invitePower }}</strong>
|
||||
我们非常欢迎您把此应用分享给您身边的朋友,分享成功注册后您和被邀请人都将获得
|
||||
<strong>{{ invitePower }}</strong>
|
||||
算力额度作为奖励。
|
||||
你可以保存下面的二维码或者直接复制分享您的专属推广链接发送给微信好友。
|
||||
</div>
|
||||
|
||||
<div class="invite-qrcode">
|
||||
<el-image :src="qrImg"/>
|
||||
<el-image :src="qrImg" />
|
||||
</div>
|
||||
|
||||
<div class="invite-url">
|
||||
<span>{{ inviteURL }}</span>
|
||||
<el-button type="primary" plain class="copy-link" :data-clipboard-text="inviteURL">复制链接</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
class="copy-link"
|
||||
:data-clipboard-text="inviteURL"
|
||||
>复制链接</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -76,10 +83,10 @@
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<h2>您推荐用户</h2>
|
||||
<h2>您推荐的用户</h2>
|
||||
|
||||
<div class="invite-logs">
|
||||
<invite-list v-if="isLogin"/>
|
||||
<invite-list v-if="isLogin" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -87,69 +94,79 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import { onMounted, ref } from "vue";
|
||||
import QRCode from "qrcode";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import { httpGet } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Clipboard from "clipboard";
|
||||
import InviteList from "@/components/InviteList.vue";
|
||||
import {checkSession, getSystemInfo} from "@/store/cache";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import { checkSession, getSystemInfo } from "@/store/cache";
|
||||
import { useSharedStore } from "@/store/sharedata";
|
||||
|
||||
const inviteURL = ref("")
|
||||
const qrImg = ref("/images/wx.png")
|
||||
const invitePower = ref(0)
|
||||
const hits = ref(0)
|
||||
const regNum = ref(0)
|
||||
const rate = ref(0)
|
||||
const isLogin = ref(false)
|
||||
const store = useSharedStore()
|
||||
const inviteURL = ref("");
|
||||
const qrImg = ref("/images/wx.png");
|
||||
const invitePower = ref(0);
|
||||
const hits = ref(0);
|
||||
const regNum = ref(0);
|
||||
const rate = ref(0);
|
||||
const isLogin = ref(false);
|
||||
const store = useSharedStore();
|
||||
|
||||
onMounted(() => {
|
||||
initData()
|
||||
initData();
|
||||
|
||||
// 复制链接
|
||||
const clipboard = new Clipboard('.copy-link');
|
||||
clipboard.on('success', () => {
|
||||
ElMessage.success('复制成功!');
|
||||
})
|
||||
const clipboard = new Clipboard(".copy-link");
|
||||
clipboard.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
});
|
||||
|
||||
clipboard.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
clipboard.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
});
|
||||
|
||||
const initData = () => {
|
||||
checkSession().then(() => {
|
||||
isLogin.value = true
|
||||
httpGet("/api/invite/code").then(res => {
|
||||
const text = `${location.protocol}//${location.host}/register?invite_code=${res.data.code}`
|
||||
hits.value = res.data["hits"]
|
||||
regNum.value = res.data["reg_num"]
|
||||
checkSession()
|
||||
.then(() => {
|
||||
isLogin.value = true;
|
||||
httpGet("/api/invite/code")
|
||||
.then((res) => {
|
||||
const text = `${location.protocol}//${location.host}/register?invite_code=${res.data.code}`;
|
||||
hits.value = res.data["hits"];
|
||||
regNum.value = res.data["reg_num"];
|
||||
if (hits.value > 0) {
|
||||
rate.value = ((regNum.value / hits.value) * 100).toFixed(2)
|
||||
rate.value = ((regNum.value / hits.value) * 100).toFixed(2);
|
||||
}
|
||||
QRCode.toDataURL(text, {width: 400, height: 400, margin: 2}, (error, url) => {
|
||||
QRCode.toDataURL(
|
||||
text,
|
||||
{ width: 400, height: 400, margin: 2 },
|
||||
(error, url) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
} else {
|
||||
qrImg.value = url;
|
||||
}
|
||||
});
|
||||
inviteURL.value = text
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取邀请码失败:" + e.message)
|
||||
}
|
||||
);
|
||||
inviteURL.value = text;
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取邀请码失败:" + e.message);
|
||||
});
|
||||
|
||||
getSystemInfo().then(res => {
|
||||
invitePower.value = res.data["invite_power"]
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
getSystemInfo()
|
||||
.then((res) => {
|
||||
invitePower.value = res.data["invite_power"];
|
||||
})
|
||||
}).catch(() => {
|
||||
store.setShowLoginDialog(true)
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
store.setShowLoginDialog(true);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@ -157,7 +174,7 @@ const initData = () => {
|
||||
.page-invitation {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: #282c34;
|
||||
// background-color: #282c34;
|
||||
height 100%
|
||||
overflow-x hidden
|
||||
overflow-y visible
|
||||
@ -167,17 +184,18 @@ const initData = () => {
|
||||
flex-flow column
|
||||
max-width 1000px
|
||||
width 100%
|
||||
color #e1e1e1
|
||||
color: var(--text-theme-color);
|
||||
|
||||
h2 {
|
||||
color #ffffff;
|
||||
color: var(--theme-textcolor-normal);
|
||||
text-align center
|
||||
}
|
||||
|
||||
.share-box {
|
||||
.info {
|
||||
line-height 1.5
|
||||
border 1px solid #444444
|
||||
// border 1px solid #444444
|
||||
background:var(--chat-bg)
|
||||
border-radius 10px
|
||||
padding 10px
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="images">
|
||||
<template v-for="(img, index) in images" :key="img">
|
||||
<div class="item">
|
||||
<el-image :src="replaceImg(img)" fit="cover"/>
|
||||
<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">
|
||||
@ -30,7 +30,8 @@
|
||||
:rows="row"
|
||||
v-model="formData.prompt"
|
||||
placeholder="请输入提示词或者上传图片"
|
||||
autofocus>
|
||||
autofocus
|
||||
>
|
||||
</textarea>
|
||||
<div class="send-icon" @click="create">
|
||||
<i class="iconfont icon-send"></i>
|
||||
@ -39,26 +40,34 @@
|
||||
|
||||
<div class="params">
|
||||
<div class="item-group">
|
||||
<el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2" :disabled="isGenerating">
|
||||
<el-button
|
||||
class="generate-btn"
|
||||
size="small"
|
||||
@click="generatePrompt"
|
||||
color="#5865f2"
|
||||
:disabled="isGenerating"
|
||||
>
|
||||
<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" style="--el-switch-on-color:#BF78BF;" />
|
||||
<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" style="--el-switch-on-color:#BF78BF;" />
|
||||
<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)">
|
||||
<el-container
|
||||
class="video-container"
|
||||
v-loading="loading"
|
||||
element-loading-background="rgba(100,100,100,0.3)"
|
||||
>
|
||||
<h2 class="h-title">你的作品</h2>
|
||||
|
||||
<div class="list-box" v-if="!noData">
|
||||
@ -67,36 +76,63 @@
|
||||
<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
|
||||
class="video"
|
||||
:src="replaceImg(item.video_url)"
|
||||
preload="auto"
|
||||
loop="loop"
|
||||
muted="muted"
|
||||
>
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
<button class="play" @click="play(item)">
|
||||
<img src="/images/play.svg" alt=""/>
|
||||
<img src="/images/play.svg" alt="" />
|
||||
</button>
|
||||
</div>
|
||||
<el-image :src="item.cover_url" fit="cover" v-else-if="item.progress > 100" />
|
||||
<el-image
|
||||
:src="item.cover_url"
|
||||
fit="cover"
|
||||
v-else-if="item.progress > 100"
|
||||
/>
|
||||
<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 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" />
|
||||
<black-switch
|
||||
v-model:value="item.publish"
|
||||
@change="publishJob(item)"
|
||||
size="small"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<el-tooltip effect="light" 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 />
|
||||
<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 effect="light" content="删除" placement="top">
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<button class="btn btn-icon" @click="removeJob(item)">
|
||||
<i class="iconfont icon-remove"></i>
|
||||
</button>
|
||||
@ -111,25 +147,43 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty :image-size="100" description="没有任何作品,赶紧去创作吧!" v-else />
|
||||
<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:#414141;
|
||||
--el-pagination-button-color:#d1d1d1;
|
||||
--el-disabled-bg-color:#414141;
|
||||
--el-color-primary:#666666;
|
||||
--el-pagination-hover-color:#e1e1e1"
|
||||
<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"/>
|
||||
:total="total"
|
||||
/>
|
||||
</div>
|
||||
</el-container>
|
||||
<black-dialog v-model:show="showDialog" title="预览视频" hide-footer @cancal="showDialog = false" width="auto">
|
||||
<video style="width: 100%; max-height: 90vh;" :src="currentVideoUrl" preload="auto" :autoplay="true" loop="loop" muted="muted" v-show="showDialog">
|
||||
<black-dialog
|
||||
v-model:show="showDialog"
|
||||
title="预览视频"
|
||||
hide-footer
|
||||
@cancal="showDialog = false"
|
||||
width="auto"
|
||||
>
|
||||
<video
|
||||
style="width: 100%; max-height: 90vh"
|
||||
:src="currentVideoUrl"
|
||||
preload="auto"
|
||||
:autoplay="true"
|
||||
loop="loop"
|
||||
muted="muted"
|
||||
v-show="showDialog"
|
||||
>
|
||||
您的浏览器不支持视频播放
|
||||
</video>
|
||||
</black-dialog>
|
||||
@ -137,184 +191,199 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, onUnmounted, reactive, ref} from "vue";
|
||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
|
||||
import {httpDownload, httpPost, httpGet} from "@/utils/http";
|
||||
import {checkSession, getClientId} from "@/store/cache";
|
||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
||||
import { replaceImg } from "@/utils/libs"
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
import { onMounted, onUnmounted, reactive, ref } from "vue";
|
||||
import { CircleCloseFilled } from "@element-plus/icons-vue";
|
||||
import { httpDownload, httpPost, httpGet } from "@/utils/http";
|
||||
import { checkSession, getClientId } from "@/store/cache";
|
||||
import { showMessageError, showMessageOK } from "@/utils/dialog";
|
||||
import { replaceImg } from "@/utils/libs";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import BlackSwitch from "@/components/ui/BlackSwitch.vue";
|
||||
import Generating from "@/components/ui/Generating.vue";
|
||||
import BlackDialog from "@/components/ui/BlackDialog.vue";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import { useSharedStore } from "@/store/sharedata";
|
||||
|
||||
const showDialog = ref(false)
|
||||
const currentVideoUrl = ref('')
|
||||
const row = ref(1)
|
||||
const images = ref([])
|
||||
const showDialog = ref(false);
|
||||
const currentVideoUrl = ref("");
|
||||
const row = ref(1);
|
||||
const images = ref([]);
|
||||
|
||||
const formData = reactive({
|
||||
client_id: getClientId(),
|
||||
prompt: '',
|
||||
prompt: "",
|
||||
expand_prompt: false,
|
||||
loop: false,
|
||||
first_frame_img: '',
|
||||
end_frame_img: ''
|
||||
})
|
||||
first_frame_img: "",
|
||||
end_frame_img: ""
|
||||
});
|
||||
|
||||
const store = useSharedStore()
|
||||
onMounted(()=>{
|
||||
const store = useSharedStore();
|
||||
onMounted(() => {
|
||||
checkSession().then(() => {
|
||||
fetchData(1)
|
||||
})
|
||||
fetchData(1);
|
||||
});
|
||||
|
||||
store.addMessageHandler("luma",(data) => {
|
||||
store.addMessageHandler("luma", (data) => {
|
||||
// 丢弃无关消息
|
||||
if (data.channel !== "luma" || data.clientId !== getClientId()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.body === "FINISH" || data.body === "FAIL") {
|
||||
fetchData(1)
|
||||
fetchData(1);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
store.removeMessageHandler("luma")
|
||||
})
|
||||
store.removeMessageHandler("luma");
|
||||
});
|
||||
|
||||
const download = (item) => {
|
||||
const url = replaceImg(item.video_url)
|
||||
const downloadURL = `${process.env.VUE_APP_API_HOST}/api/download?url=${url}`
|
||||
const url = replaceImg(item.video_url);
|
||||
const downloadURL = `${process.env.VUE_APP_API_HOST}/api/download?url=${url}`;
|
||||
// parse filename
|
||||
const urlObj = new URL(url);
|
||||
const fileName = urlObj.pathname.split('/').pop();
|
||||
item.downloading = true
|
||||
httpDownload(downloadURL).then(response => {
|
||||
const fileName = urlObj.pathname.split("/").pop();
|
||||
item.downloading = true;
|
||||
httpDownload(downloadURL)
|
||||
.then((response) => {
|
||||
const blob = new Blob([response.data]);
|
||||
const link = document.createElement('a');
|
||||
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
|
||||
item.downloading = false;
|
||||
})
|
||||
}
|
||||
.catch(() => {
|
||||
showMessageError("下载失败");
|
||||
item.downloading = false;
|
||||
});
|
||||
};
|
||||
|
||||
const play = (item) => {
|
||||
currentVideoUrl.value = replaceImg(item.video_url)
|
||||
showDialog.value = true
|
||||
}
|
||||
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)
|
||||
ElMessageBox.confirm("此操作将会删除任务相关文件,继续操作码?", "删除提示", {
|
||||
confirmButtonText: "确认",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
})
|
||||
}).catch(() => {
|
||||
.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)
|
||||
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);
|
||||
formData.append("file", file.file, file.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/upload', formData).then((res) => {
|
||||
images.value.push(res.data.url)
|
||||
ElMessage.success({message: "上传成功", duration: 500})
|
||||
}).catch((e) => {
|
||||
ElMessage.error('图片上传失败:' + e.message)
|
||||
httpPost("/api/upload", formData)
|
||||
.then((res) => {
|
||||
images.value.push(res.data.url);
|
||||
ElMessage.success({ message: "上传成功", duration: 500 });
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("图片上传失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const remove = (img) => {
|
||||
images.value = images.value.filter(item => item !== img)
|
||||
}
|
||||
images.value = images.value.filter((item) => item !== img);
|
||||
};
|
||||
|
||||
const switchReverse = () => {
|
||||
images.value = images.value.reverse()
|
||||
}
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const noData = ref(true)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
images.value = images.value.reverse();
|
||||
};
|
||||
const loading = ref(false);
|
||||
const list = ref([]);
|
||||
const noData = ref(true);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const total = ref(0);
|
||||
const fetchData = (_page) => {
|
||||
if (_page) {
|
||||
page.value = _page
|
||||
page.value = _page;
|
||||
}
|
||||
httpGet("/api/video/list",{page:page.value, page_size:pageSize.value, type: 'luma'}).then(res => {
|
||||
total.value = res.data.total
|
||||
loading.value = false
|
||||
list.value = res.data.items
|
||||
noData.value = list.value.length === 0
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
noData.value = true
|
||||
httpGet("/api/video/list", {
|
||||
page: page.value,
|
||||
page_size: pageSize.value,
|
||||
type: "luma"
|
||||
})
|
||||
}
|
||||
.then((res) => {
|
||||
total.value = res.data.total;
|
||||
loading.value = false;
|
||||
list.value = res.data.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 = images.value[0]
|
||||
if(len === 2){
|
||||
formData.end_frame_img = images.value[1]
|
||||
if (len) {
|
||||
formData.first_frame_img = images.value[0];
|
||||
if (len === 2) {
|
||||
formData.end_frame_img = images.value[1];
|
||||
}
|
||||
}
|
||||
|
||||
httpPost("/api/video/luma/create", formData).then(() => {
|
||||
fetchData(1)
|
||||
showMessageOK("创建任务成功")
|
||||
}).catch(e => {
|
||||
showMessageError("创建任务失败:"+e.message)
|
||||
httpPost("/api/video/luma/create", formData)
|
||||
.then(() => {
|
||||
fetchData(1);
|
||||
showMessageOK("创建任务成功");
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
showMessageError("创建任务失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const isGenerating = ref(false)
|
||||
const isGenerating = ref(false);
|
||||
const generatePrompt = () => {
|
||||
if (formData.prompt === "") {
|
||||
return showMessageError("请输入原始提示词")
|
||||
return showMessageError("请输入原始提示词");
|
||||
}
|
||||
isGenerating.value = true
|
||||
httpPost("/api/prompt/image", {prompt: formData.prompt}).then(res => {
|
||||
formData.prompt = res.data
|
||||
isGenerating.value = false
|
||||
}).catch(e => {
|
||||
showMessageError("生成提示词失败:"+e.message)
|
||||
isGenerating.value = false
|
||||
isGenerating.value = true;
|
||||
httpPost("/api/prompt/image", { prompt: formData.prompt })
|
||||
.then((res) => {
|
||||
formData.prompt = res.data;
|
||||
isGenerating.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
.catch((e) => {
|
||||
showMessageError("生成提示词失败:" + e.message);
|
||||
isGenerating.value = false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
@ -7,9 +7,7 @@
|
||||
|
||||
<div class="mark-map-params">
|
||||
<el-form label-width="80px" label-position="left">
|
||||
<div class="param-line">
|
||||
你的需求?
|
||||
</div>
|
||||
<div class="param-line">你的需求?</div>
|
||||
<div class="param-line">
|
||||
<el-input
|
||||
v-model="prompt"
|
||||
@ -19,11 +17,13 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="param-line">请选择生成思维导图的AI模型</div>
|
||||
<div class="param-line">
|
||||
请选择生成思维导图的AI模型
|
||||
</div>
|
||||
<div class="param-line">
|
||||
<el-select v-model="modelID" placeholder="请选择模型" style="width:100%">
|
||||
<el-select
|
||||
v-model="modelID"
|
||||
placeholder="请选择模型"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in models"
|
||||
:key="item.id"
|
||||
@ -31,27 +31,38 @@
|
||||
:value="item.id"
|
||||
>
|
||||
<span>{{ item.name }}</span>
|
||||
<el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
|
||||
item.power
|
||||
}}算力
|
||||
<el-tag
|
||||
style="margin-left: 5px; position: relative; top: -2px"
|
||||
type="info"
|
||||
size="small"
|
||||
>{{ item.power }}算力
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
|
||||
<div class="text-info">
|
||||
<el-tag type="success">当前可用算力:{{ loginUser.power }}</el-tag>
|
||||
<el-text type="primary"
|
||||
>当前可用算力:<el-text type="warning">{{
|
||||
loginUser.power
|
||||
}}</el-text></el-text
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="param-line">
|
||||
<el-button color="#47fff1" :dark="false" round @click="generateAI" :loading="loading">
|
||||
<div class="submit-btn flex-center">
|
||||
<el-button
|
||||
style="width: 200px"
|
||||
type="primary"
|
||||
:dark="false"
|
||||
round
|
||||
@click="generateAI"
|
||||
:loading="loading"
|
||||
>
|
||||
生成思维导图
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="param-line">
|
||||
使用已有内容生成?
|
||||
</div>
|
||||
<div class="param-line">使用已有内容生成?</div>
|
||||
<div class="param-line">
|
||||
<el-input
|
||||
v-model="content"
|
||||
@ -61,10 +72,16 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="param-line">
|
||||
<el-button color="#C5F9AE" :dark="false" round @click="generate">直接生成(免费)</el-button>
|
||||
<div class="param-line flex-center">
|
||||
<el-button
|
||||
color="rgb(78, 51, 254)"
|
||||
style="width: 200px"
|
||||
:dark="false"
|
||||
round
|
||||
@click="generate"
|
||||
>直接生成(免费)</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
@ -73,188 +90,196 @@
|
||||
<div class="top-bar">
|
||||
<el-button @click="downloadImage" type="primary">
|
||||
<el-icon>
|
||||
<Download/>
|
||||
<Download />
|
||||
</el-icon>
|
||||
<span>下载图片</span>
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="body" id="markmap">
|
||||
<svg ref="svgRef" :style="{ height: rightBoxHeight + 'px' }"/>
|
||||
<svg ref="svgRef" :style="{ height: rightBoxHeight + 'px' }" />
|
||||
<div id="toolbar"></div>
|
||||
</div>
|
||||
</div><!-- end task list box -->
|
||||
</div>
|
||||
|
||||
<!-- end task list box -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {nextTick, ref} from 'vue';
|
||||
import {Markmap} from 'markmap-view';
|
||||
import {Transformer} from 'markmap-lib';
|
||||
import {checkSession, getSystemInfo} from "@/store/cache";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {Download} from "@element-plus/icons-vue";
|
||||
import {Toolbar} from 'markmap-toolbar';
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import { nextTick, ref } from "vue";
|
||||
import { Markmap } from "markmap-view";
|
||||
import { Transformer } from "markmap-lib";
|
||||
import { checkSession, getSystemInfo } from "@/store/cache";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { Download } from "@element-plus/icons-vue";
|
||||
import { Toolbar } from "markmap-toolbar";
|
||||
import { useSharedStore } from "@/store/sharedata";
|
||||
|
||||
const leftBoxHeight = ref(window.innerHeight - 105)
|
||||
const rightBoxHeight = ref(window.innerHeight - 115)
|
||||
const leftBoxHeight = ref(window.innerHeight - 105);
|
||||
const rightBoxHeight = ref(window.innerHeight - 115);
|
||||
|
||||
const prompt = ref("")
|
||||
const text = ref("")
|
||||
const content = ref(text.value)
|
||||
const html = ref("")
|
||||
const prompt = ref("");
|
||||
const text = ref("");
|
||||
const content = ref(text.value);
|
||||
const html = ref("");
|
||||
|
||||
const isLogin = ref(false)
|
||||
const loginUser = ref({power: 0})
|
||||
const isLogin = ref(false);
|
||||
const loginUser = ref({ power: 0 });
|
||||
const transformer = new Transformer();
|
||||
const store = useSharedStore();
|
||||
const loading = ref(false)
|
||||
const loading = ref(false);
|
||||
|
||||
const svgRef = ref(null)
|
||||
const markMap = ref(null)
|
||||
const models = ref([])
|
||||
const modelID = ref(0)
|
||||
const svgRef = ref(null);
|
||||
const markMap = ref(null);
|
||||
const models = ref([]);
|
||||
const modelID = ref(0);
|
||||
|
||||
getSystemInfo().then(res => {
|
||||
text.value = res.data['mark_map_text']
|
||||
content.value = text.value
|
||||
initData()
|
||||
getSystemInfo()
|
||||
.then((res) => {
|
||||
text.value = res.data["mark_map_text"];
|
||||
content.value = text.value;
|
||||
initData();
|
||||
nextTick(() => {
|
||||
try {
|
||||
markMap.value = Markmap.create(svgRef.value)
|
||||
const {el} = Toolbar.create(markMap.value);
|
||||
document.getElementById('toolbar').append(el);
|
||||
update()
|
||||
markMap.value = Markmap.create(svgRef.value);
|
||||
const { el } = Toolbar.create(markMap.value);
|
||||
document.getElementById("toolbar").append(el);
|
||||
update();
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
})
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message);
|
||||
});
|
||||
|
||||
const initData = () => {
|
||||
httpGet("/api/model/list").then(res => {
|
||||
httpGet("/api/model/list")
|
||||
.then((res) => {
|
||||
for (let v of res.data) {
|
||||
models.value.push(v)
|
||||
models.value.push(v);
|
||||
}
|
||||
modelID.value = models.value[0].id
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取模型失败:" + e.message)
|
||||
modelID.value = models.value[0].id;
|
||||
})
|
||||
|
||||
checkSession().then(user => {
|
||||
loginUser.value = user
|
||||
isLogin.value = true
|
||||
}).catch(() => {
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取模型失败:" + e.message);
|
||||
});
|
||||
}
|
||||
|
||||
checkSession()
|
||||
.then((user) => {
|
||||
loginUser.value = user;
|
||||
isLogin.value = true;
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const update = () => {
|
||||
try {
|
||||
const {root} = transformer.transform(processContent(text.value))
|
||||
markMap.value.setData(root)
|
||||
markMap.value.fit()
|
||||
const { root } = transformer.transform(processContent(text.value));
|
||||
markMap.value.setData(root);
|
||||
markMap.value.fit();
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const processContent = (text) => {
|
||||
if (!text) {
|
||||
return text
|
||||
return text;
|
||||
}
|
||||
|
||||
const arr = []
|
||||
const lines = text.split("\n")
|
||||
const arr = [];
|
||||
const lines = text.split("\n");
|
||||
for (let line of lines) {
|
||||
if (line.indexOf("```") !== -1) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
line = line.replace(/([*_~`>])|(\d+\.)\s/g, '')
|
||||
arr.push(line)
|
||||
line = line.replace(/([*_~`>])|(\d+\.)\s/g, "");
|
||||
arr.push(line);
|
||||
}
|
||||
return arr.join("\n")
|
||||
}
|
||||
return arr.join("\n");
|
||||
};
|
||||
|
||||
window.onresize = () => {
|
||||
leftBoxHeight.value = window.innerHeight - 145
|
||||
rightBoxHeight.value = window.innerHeight - 85
|
||||
}
|
||||
leftBoxHeight.value = window.innerHeight - 145;
|
||||
rightBoxHeight.value = window.innerHeight - 85;
|
||||
};
|
||||
|
||||
const generate = () => {
|
||||
text.value = content.value
|
||||
update()
|
||||
}
|
||||
text.value = content.value;
|
||||
update();
|
||||
};
|
||||
|
||||
// 使用 AI 智能生成
|
||||
const generateAI = () => {
|
||||
html.value = ''
|
||||
text.value = ''
|
||||
if (prompt.value === '') {
|
||||
return ElMessage.error("请输入你的需求")
|
||||
html.value = "";
|
||||
text.value = "";
|
||||
if (prompt.value === "") {
|
||||
return ElMessage.error("请输入你的需求");
|
||||
}
|
||||
if (!isLogin.value) {
|
||||
store.setShowLoginDialog(true)
|
||||
return
|
||||
store.setShowLoginDialog(true);
|
||||
return;
|
||||
}
|
||||
loading.value = true
|
||||
loading.value = true;
|
||||
httpPost("/api/markMap/gen", {
|
||||
prompt:prompt.value,
|
||||
prompt: prompt.value,
|
||||
model_id: modelID.value
|
||||
}).then(res => {
|
||||
text.value = res.data
|
||||
content.value = processContent(text.value)
|
||||
const model = getModelById(modelID.value)
|
||||
loginUser.value.power -= model.power
|
||||
nextTick(() => update())
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("生成思维导图失败:" + e.message)
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
.then((res) => {
|
||||
text.value = res.data;
|
||||
content.value = processContent(text.value);
|
||||
const model = getModelById(modelID.value);
|
||||
loginUser.value.power -= model.power;
|
||||
nextTick(() => update());
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("生成思维导图失败:" + e.message);
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const getModelById = (modelId) => {
|
||||
for (let m of models.value) {
|
||||
if (m.id === modelId) {
|
||||
return m
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// download SVG to png file
|
||||
const downloadImage = () => {
|
||||
const svgElement = document.getElementById("markmap");
|
||||
// 将 SVG 渲染到图片对象
|
||||
const serializer = new XMLSerializer()
|
||||
const source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(svgRef.value)
|
||||
const image = new Image()
|
||||
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source)
|
||||
const serializer = new XMLSerializer();
|
||||
const source =
|
||||
'<?xml version="1.0" standalone="no"?>\r\n' +
|
||||
serializer.serializeToString(svgRef.value);
|
||||
const image = new Image();
|
||||
image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
|
||||
|
||||
// 将图片对象渲染
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = svgElement.offsetWidth
|
||||
canvas.height = svgElement.offsetHeight
|
||||
let context = canvas.getContext('2d')
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = svgElement.offsetWidth;
|
||||
canvas.height = svgElement.offsetHeight;
|
||||
let context = canvas.getContext("2d");
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
context.fillStyle = 'white';
|
||||
context.fillStyle = "white";
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
image.onload = function () {
|
||||
context.drawImage(image, 0, 0)
|
||||
const a = document.createElement('a')
|
||||
a.download = "geek-ai-xmind.png"
|
||||
a.href = canvas.toDataURL(`image/png`)
|
||||
a.click()
|
||||
}
|
||||
}
|
||||
|
||||
context.drawImage(image, 0, 0);
|
||||
const a = document.createElement("a");
|
||||
a.download = "geek-ai-xmind.png";
|
||||
a.href = canvas.toDataURL(`image/png`);
|
||||
a.click();
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
@ -1,25 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="member custom-scroll" v-loading="loading" element-loading-background="rgba(255,255,255,.3)" :element-loading-text="loadingText">
|
||||
<div
|
||||
class="member custom-scroll"
|
||||
v-loading="loading"
|
||||
element-loading-background="rgba(255,255,255,.3)"
|
||||
:element-loading-text="loadingText"
|
||||
>
|
||||
<div class="inner">
|
||||
<div class="user-profile">
|
||||
<user-profile :key="profileKey"/>
|
||||
<user-profile :key="profileKey" />
|
||||
|
||||
<el-row class="user-opt" :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-button type="primary" @click="showBindEmailDialog = true">绑定邮箱</el-button>
|
||||
<el-button type="primary" @click="showBindEmailDialog = true"
|
||||
>绑定邮箱</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-button type="primary" @click="showBindMobileDialog = true">绑定手机</el-button>
|
||||
<el-button type="primary" @click="showBindMobileDialog = true"
|
||||
>绑定手机</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-button type="primary" @click="showThirdLoginDialog = true">第三方登录</el-button>
|
||||
<el-button type="primary" @click="showThirdLoginDialog = true"
|
||||
>第三方登录</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-button type="primary" @click="showPasswordDialog = true">修改密码</el-button>
|
||||
<el-button type="primary" @click="showPasswordDialog = true"
|
||||
>修改密码</el-button
|
||||
>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<el-button type="primary" @click="showRedeemVerifyDialog = true">卡密兑换
|
||||
<el-button type="primary" @click="showRedeemVerifyDialog = true"
|
||||
>卡密兑换
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -36,7 +50,7 @@
|
||||
<el-col v-for="item in list" :key="item" :span="6">
|
||||
<div class="product-item">
|
||||
<div class="image-container">
|
||||
<el-image :src="vipImg" fit="cover"/>
|
||||
<el-image :src="vipImg" fit="cover" />
|
||||
</div>
|
||||
<div class="product-title">
|
||||
<span class="name">{{ item.name }}</span>
|
||||
@ -44,7 +58,9 @@
|
||||
<div class="product-info">
|
||||
<div class="info-line">
|
||||
<span class="label">商品原价:</span>
|
||||
<span class="price"><del>¥{{ item.price }}</del></span>
|
||||
<span class="price"
|
||||
><del>¥{{ item.price }}</del></span
|
||||
>
|
||||
</div>
|
||||
<div class="info-line">
|
||||
<span class="label">优惠价:</span>
|
||||
@ -52,7 +68,9 @@
|
||||
</div>
|
||||
<div class="info-line">
|
||||
<span class="label">有效期:</span>
|
||||
<span class="expire" v-if="item.days > 0">{{ item.days }}天</span>
|
||||
<span class="expire" v-if="item.days > 0"
|
||||
>{{ item.days }}天</span
|
||||
>
|
||||
<span class="expire" v-else>长期有效</span>
|
||||
</div>
|
||||
|
||||
@ -62,21 +80,41 @@
|
||||
</div>
|
||||
|
||||
<div class="pay-way">
|
||||
|
||||
<span type="primary" v-for="payWay in payWays" @click="pay(item,payWay)" :key="payWay">
|
||||
<el-button v-if="payWay.pay_type==='alipay'" color="#15A6E8" circle>
|
||||
<i class="iconfont icon-alipay" ></i>
|
||||
<span
|
||||
type="primary"
|
||||
v-for="payWay in payWays"
|
||||
@click="pay(item, payWay)"
|
||||
:key="payWay"
|
||||
>
|
||||
<el-button
|
||||
v-if="payWay.pay_type === 'alipay'"
|
||||
color="#15A6E8"
|
||||
circle
|
||||
>
|
||||
<i class="iconfont icon-alipay"></i>
|
||||
</el-button>
|
||||
<el-button v-else-if="payWay.pay_type==='qqpay'" circle>
|
||||
<el-button v-else-if="payWay.pay_type === 'qqpay'" circle>
|
||||
<i class="iconfont icon-qq"></i>
|
||||
</el-button>
|
||||
<el-button v-else-if="payWay.pay_type==='paypal'" class="paypal" round>
|
||||
<el-button
|
||||
v-else-if="payWay.pay_type === 'paypal'"
|
||||
class="paypal"
|
||||
round
|
||||
>
|
||||
<i class="iconfont icon-paypal"></i>
|
||||
</el-button>
|
||||
<el-button v-else-if="payWay.pay_type==='jdpay'" color="#E1251B" circle>
|
||||
<el-button
|
||||
v-else-if="payWay.pay_type === 'jdpay'"
|
||||
color="#E1251B"
|
||||
circle
|
||||
>
|
||||
<i class="iconfont icon-jd-pay"></i>
|
||||
</el-button>
|
||||
<el-button v-else-if="payWay.pay_type==='douyin'" class="douyin" circle>
|
||||
<el-button
|
||||
v-else-if="payWay.pay_type === 'douyin'"
|
||||
class="douyin"
|
||||
circle
|
||||
>
|
||||
<i class="iconfont icon-douyin"></i>
|
||||
</el-button>
|
||||
<el-button v-else circle class="wechat" color="#67C23A">
|
||||
@ -93,116 +131,155 @@
|
||||
<h2 class="headline">消费账单</h2>
|
||||
|
||||
<div class="user-order">
|
||||
<user-order v-if="isLogin" :key="userOrderKey"/>
|
||||
<user-order v-if="isLogin" :key="userOrderKey" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false"/>
|
||||
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" @hide="showBindMobileDialog = false"/>
|
||||
<bind-email v-if="isLogin" :show="showBindEmailDialog" @hide="showBindEmailDialog = false"/>
|
||||
<third-login v-if="isLogin" :show="showThirdLoginDialog" @hide="showThirdLoginDialog = false"/>
|
||||
<redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/>
|
||||
|
||||
<password-dialog
|
||||
v-if="isLogin"
|
||||
:show="showPasswordDialog"
|
||||
@hide="showPasswordDialog = false"
|
||||
/>
|
||||
<bind-mobile
|
||||
v-if="isLogin"
|
||||
:show="showBindMobileDialog"
|
||||
@hide="showBindMobileDialog = false"
|
||||
/>
|
||||
<bind-email
|
||||
v-if="isLogin"
|
||||
:show="showBindEmailDialog"
|
||||
@hide="showBindEmailDialog = false"
|
||||
/>
|
||||
<third-login
|
||||
v-if="isLogin"
|
||||
:show="showThirdLoginDialog"
|
||||
@hide="showThirdLoginDialog = false"
|
||||
/>
|
||||
<redeem-verify
|
||||
v-if="isLogin"
|
||||
:show="showRedeemVerifyDialog"
|
||||
@hide="redeemCallback"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-dialog v-model="showDialog" :show-close=false :close-on-click-modal="false" hide-footer width="auto" class="pay-dialog">
|
||||
<el-dialog
|
||||
v-model="showDialog"
|
||||
:show-close="false"
|
||||
:close-on-click-modal="false"
|
||||
hide-footer
|
||||
width="auto"
|
||||
class="pay-dialog"
|
||||
>
|
||||
<div v-if="qrImg !== ''">
|
||||
<div class="product-info">请使用微信扫码支付:<span class="price">¥{{price}}</span></div>
|
||||
<div class="product-info">
|
||||
请使用微信扫码支付:<span class="price">¥{{ price }}</span>
|
||||
</div>
|
||||
<el-image :src="qrImg" fit="cover" />
|
||||
</div>
|
||||
<div style="padding-bottom: 10px; text-align: center">
|
||||
<el-button type="success" @click="payCallback(true)">支付成功</el-button>
|
||||
<el-button type="danger" @click="payCallback(false)">支付失败</el-button>
|
||||
<el-button type="success" @click="payCallback(true)"
|
||||
>支付成功</el-button
|
||||
>
|
||||
<el-button type="danger" @click="payCallback(false)"
|
||||
>支付失败</el-button
|
||||
>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import {ElMessage} from "element-plus";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {checkSession, getSystemInfo} from "@/store/cache";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { checkSession, getSystemInfo } from "@/store/cache";
|
||||
import UserProfile from "@/components/UserProfile.vue";
|
||||
import PasswordDialog from "@/components/PasswordDialog.vue";
|
||||
import BindMobile from "@/components/BindMobile.vue";
|
||||
import RedeemVerify from "@/components/RedeemVerify.vue";
|
||||
import UserOrder from "@/components/UserOrder.vue";
|
||||
import {useSharedStore} from "@/store/sharedata";
|
||||
import { useSharedStore } from "@/store/sharedata";
|
||||
import BindEmail from "@/components/BindEmail.vue";
|
||||
import ThirdLogin from "@/components/ThirdLogin.vue";
|
||||
import QRCode from "qrcode";
|
||||
|
||||
const list = ref([])
|
||||
const vipImg = ref("/images/vip.png")
|
||||
const enableReward = ref(false) // 是否启用众筹功能
|
||||
const rewardImg = ref('/images/reward.png')
|
||||
const showPasswordDialog = ref(false)
|
||||
const showBindMobileDialog = ref(false)
|
||||
const showBindEmailDialog = ref(false)
|
||||
const showRedeemVerifyDialog = ref(false)
|
||||
const showThirdLoginDialog = ref(false)
|
||||
const user = ref(null)
|
||||
const isLogin = ref(false)
|
||||
const orderTimeout = ref(1800)
|
||||
const loading = ref(true)
|
||||
const loadingText = ref("加载中...")
|
||||
const orderPayInfoText = ref("")
|
||||
|
||||
const payWays = ref([])
|
||||
const vipInfoText = ref("")
|
||||
const store = useSharedStore()
|
||||
const profileKey = ref(0)
|
||||
const userOrderKey = ref(0)
|
||||
const showDialog = ref(false)
|
||||
const qrImg = ref("")
|
||||
const price = ref(0)
|
||||
const list = ref([]);
|
||||
const vipImg = ref("/images/menu/member.png");
|
||||
const enableReward = ref(false); // 是否启用众筹功能
|
||||
const rewardImg = ref("/images/reward.png");
|
||||
const showPasswordDialog = ref(false);
|
||||
const showBindMobileDialog = ref(false);
|
||||
const showBindEmailDialog = ref(false);
|
||||
const showRedeemVerifyDialog = ref(false);
|
||||
const showThirdLoginDialog = ref(false);
|
||||
const user = ref(null);
|
||||
const isLogin = ref(false);
|
||||
const orderTimeout = ref(1800);
|
||||
const loading = ref(true);
|
||||
const loadingText = ref("加载中...");
|
||||
const orderPayInfoText = ref("");
|
||||
|
||||
const payWays = ref([]);
|
||||
const vipInfoText = ref("");
|
||||
const store = useSharedStore();
|
||||
const profileKey = ref(0);
|
||||
const userOrderKey = ref(0);
|
||||
const showDialog = ref(false);
|
||||
const qrImg = ref("");
|
||||
const price = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(_user => {
|
||||
user.value = _user
|
||||
isLogin.value = true
|
||||
}).catch(() => {
|
||||
store.setShowLoginDialog(true)
|
||||
checkSession()
|
||||
.then((_user) => {
|
||||
user.value = _user;
|
||||
isLogin.value = true;
|
||||
})
|
||||
.catch(() => {
|
||||
store.setShowLoginDialog(true);
|
||||
});
|
||||
|
||||
httpGet("/api/product/list").then((res) => {
|
||||
list.value = res.data
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取产品套餐失败:" + e.message)
|
||||
httpGet("/api/product/list")
|
||||
.then((res) => {
|
||||
list.value = res.data;
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取产品套餐失败:" + e.message);
|
||||
});
|
||||
|
||||
getSystemInfo().then(res => {
|
||||
rewardImg.value = res.data['reward_img']
|
||||
enableReward.value = res.data['enabled_reward']
|
||||
orderPayInfoText.value = res.data['order_pay_info_text']
|
||||
if (res.data['order_pay_timeout'] > 0) {
|
||||
orderTimeout.value = res.data['order_pay_timeout']
|
||||
getSystemInfo()
|
||||
.then((res) => {
|
||||
rewardImg.value = res.data["reward_img"];
|
||||
enableReward.value = res.data["enabled_reward"];
|
||||
orderPayInfoText.value = res.data["order_pay_info_text"];
|
||||
if (res.data["order_pay_timeout"] > 0) {
|
||||
orderTimeout.value = res.data["order_pay_timeout"];
|
||||
}
|
||||
vipInfoText.value = res.data['vip_info_text']
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
vipInfoText.value = res.data["vip_info_text"];
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message);
|
||||
});
|
||||
|
||||
httpGet("/api/payment/payWays").then(res => {
|
||||
payWays.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取支付方式失败:" + e.message)
|
||||
httpGet("/api/payment/payWays")
|
||||
.then((res) => {
|
||||
payWays.value = res.data;
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取支付方式失败:" + e.message);
|
||||
});
|
||||
});
|
||||
|
||||
const pay = (product, payWay) => {
|
||||
if (!isLogin.value) {
|
||||
store.setShowLoginDialog(true)
|
||||
return
|
||||
store.setShowLoginDialog(true);
|
||||
return;
|
||||
}
|
||||
loading.value = true
|
||||
loadingText.value = "正在生成支付订单..."
|
||||
let host = process.env.VUE_APP_API_HOST
|
||||
if (host === '') {
|
||||
loading.value = true;
|
||||
loadingText.value = "正在生成支付订单...";
|
||||
let host = process.env.VUE_APP_API_HOST;
|
||||
if (host === "") {
|
||||
host = `${location.protocol}//${location.host}`;
|
||||
}
|
||||
httpPost(`${process.env.VUE_APP_API_HOST}/api/payment/doPay`, {
|
||||
@ -212,45 +289,49 @@ const pay = (product, payWay) => {
|
||||
user_id: user.value.id,
|
||||
host: host,
|
||||
device: "jump"
|
||||
}).then(res => {
|
||||
showDialog.value = true
|
||||
loading.value = false
|
||||
if (payWay.pay_way === 'wechat') {
|
||||
price.value = Number(product.discount)
|
||||
QRCode.toDataURL(res.data, {width: 300, height: 300, margin: 2}, (error, url) => {
|
||||
})
|
||||
.then((res) => {
|
||||
showDialog.value = true;
|
||||
loading.value = false;
|
||||
if (payWay.pay_way === "wechat") {
|
||||
price.value = Number(product.discount);
|
||||
QRCode.toDataURL(
|
||||
res.data,
|
||||
{ width: 300, height: 300, margin: 2 },
|
||||
(error, url) => {
|
||||
if (error) {
|
||||
console.error(error)
|
||||
console.error(error);
|
||||
} else {
|
||||
qrImg.value = url;
|
||||
}
|
||||
})
|
||||
} else {
|
||||
window.open(res.data, '_blank');
|
||||
}
|
||||
}).catch(e => {
|
||||
setTimeout(() => {
|
||||
ElMessage.error("生成支付订单失败:" + e.message)
|
||||
loading.value = false
|
||||
}, 500)
|
||||
);
|
||||
} else {
|
||||
window.open(res.data, "_blank");
|
||||
}
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
setTimeout(() => {
|
||||
ElMessage.error("生成支付订单失败:" + e.message);
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
const redeemCallback = (success) => {
|
||||
showRedeemVerifyDialog.value = false
|
||||
showRedeemVerifyDialog.value = false;
|
||||
if (success) {
|
||||
profileKey.value += 1
|
||||
profileKey.value += 1;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const payCallback = (success) => {
|
||||
showDialog.value = false
|
||||
showDialog.value = false;
|
||||
if (success) {
|
||||
profileKey.value += 1
|
||||
userOrderKey.value += 1
|
||||
profileKey.value += 1;
|
||||
userOrderKey.value += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
|
@ -1,9 +1,15 @@
|
||||
<template>
|
||||
<div class="power-log custom-scroll" v-loading="loading">
|
||||
<div class="inner" :style="{height: listBoxHeight + 'px'}">
|
||||
<!-- :style="{ height: listBoxHeight + 'px' }" -->
|
||||
<div class="inner">
|
||||
<div class="list-box">
|
||||
<div class="handle-box">
|
||||
<el-input v-model="query.model" placeholder="模型" class="handle-input mr10" clearable></el-input>
|
||||
<el-input
|
||||
v-model="query.model"
|
||||
placeholder="模型"
|
||||
class="handle-input mr10"
|
||||
clearable
|
||||
></el-input>
|
||||
<el-date-picker
|
||||
v-model="query.date"
|
||||
type="daterange"
|
||||
@ -11,117 +17,144 @@
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="margin: 0 10px;width: 200px;"
|
||||
style="margin: 0 10px; width: 200px"
|
||||
/>
|
||||
<el-button color="#21aa93" :icon="Search" @click="fetchData">搜索</el-button>
|
||||
<el-button type="primary" :icon="Search" @click="fetchData"
|
||||
>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-row v-if="items.length > 0">
|
||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
||||
style="--el-table-border-color:#373C47;
|
||||
--el-table-tr-bg-color:#2D323B;
|
||||
--el-table-row-hover-bg-color:#373C47;
|
||||
--el-table-header-bg-color:#474E5C;
|
||||
--el-table-text-color:#d1d1d1">
|
||||
<el-table-column prop="username" label="用户" width="130px"/>
|
||||
<el-table-column prop="model" label="模型" width="130px"/>
|
||||
<el-table
|
||||
:data="items"
|
||||
:row-key="(row) => row.id"
|
||||
table-layout="auto"
|
||||
border
|
||||
>
|
||||
<el-table-column prop="username" label="用户" width="130px" />
|
||||
<el-table-column prop="model" label="模型" width="130px" />
|
||||
<el-table-column prop="type" label="类型">
|
||||
<template #default="scope">
|
||||
<el-tag size="small" :type="tagColors[scope.row.type]">{{ scope.row.type_str }}</el-tag>
|
||||
<el-tag size="small" :type="tagColors[scope.row.type]">{{
|
||||
scope.row.type_str
|
||||
}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="数额">
|
||||
<template #default="scope">
|
||||
<div>
|
||||
<el-text type="success" v-if="scope.row.mark === 1">+{{ scope.row.amount }}</el-text>
|
||||
<el-text type="danger" v-if="scope.row.mark === 0">-{{ scope.row.amount }}</el-text>
|
||||
<el-text type="success" v-if="scope.row.mark === 1"
|
||||
>+{{ scope.row.amount }}</el-text
|
||||
>
|
||||
<el-text type="danger" v-if="scope.row.mark === 0"
|
||||
>-{{ scope.row.amount }}</el-text
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="balance" label="余额"/>
|
||||
<el-table-column prop="balance" label="余额" />
|
||||
<el-table-column label="发生时间" width="160px">
|
||||
<template #default="scope">
|
||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
||||
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注"/>
|
||||
<el-table-column prop="remark" label="备注" />
|
||||
</el-table>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination v-if="total > 0" background
|
||||
<el-pagination
|
||||
v-if="total > 0"
|
||||
background
|
||||
layout="total,prev, pager, next"
|
||||
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||
:hide-on-single-page="true"
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
@current-change="fetchData()"
|
||||
:total="total"/>
|
||||
|
||||
:total="total"
|
||||
/>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-empty :image-size="100" v-else/>
|
||||
<el-empty
|
||||
:image-size="100"
|
||||
v-else
|
||||
:image="nodata"
|
||||
description="暂无数据"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue"
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import {Search} from "@element-plus/icons-vue";
|
||||
import Clipboard from "clipboard";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {httpPost} from "@/utils/http";
|
||||
import {checkSession} from "@/store/cache";
|
||||
import nodata from "@/assets/img/no-data.png";
|
||||
|
||||
const items = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(20)
|
||||
const loading = ref(false)
|
||||
const listBoxHeight = window.innerHeight - 87
|
||||
import { onMounted, ref } from "vue";
|
||||
import { dateFormat } from "@/utils/libs";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import Clipboard from "clipboard";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { httpPost } from "@/utils/http";
|
||||
import { checkSession } from "@/store/cache";
|
||||
|
||||
const items = ref([]);
|
||||
const total = ref(0);
|
||||
const page = ref(1);
|
||||
const pageSize = ref(20);
|
||||
const loading = ref(false);
|
||||
const listBoxHeight = window.innerHeight - 87;
|
||||
const query = ref({
|
||||
model: "",
|
||||
date: []
|
||||
})
|
||||
const tagColors = ref(["primary", "success", "primary", "danger", "info", "warning"])
|
||||
});
|
||||
const tagColors = ref([
|
||||
"primary",
|
||||
"success",
|
||||
"primary",
|
||||
"danger",
|
||||
"info",
|
||||
"warning"
|
||||
]);
|
||||
|
||||
onMounted(() => {
|
||||
checkSession().then(() => {
|
||||
fetchData()
|
||||
}).catch(() => {
|
||||
checkSession()
|
||||
.then(() => {
|
||||
fetchData();
|
||||
})
|
||||
const clipboard = new Clipboard('.copy-order-no');
|
||||
clipboard.on('success', () => {
|
||||
.catch(() => {});
|
||||
const clipboard = new Clipboard(".copy-order-no");
|
||||
clipboard.on("success", () => {
|
||||
ElMessage.success("复制成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
clipboard.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
});
|
||||
|
||||
// 获取数据
|
||||
const fetchData = () => {
|
||||
loading.value = true
|
||||
httpPost('/api/powerLog/list', {
|
||||
loading.value = true;
|
||||
httpPost("/api/powerLog/list", {
|
||||
model: query.value.model,
|
||||
date: query.value.date,
|
||||
page: page.value,
|
||||
page_size: pageSize.value
|
||||
}).then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items
|
||||
total.value = res.data.total
|
||||
page.value = res.data.page
|
||||
pageSize.value = res.data.page_size
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(e => {
|
||||
loading.value = false
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
})
|
||||
}
|
||||
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
items.value = res.data.items;
|
||||
total.value = res.data.total;
|
||||
page.value = res.data.page;
|
||||
pageSize.value = res.data.page_size;
|
||||
}
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
@ -135,7 +168,10 @@ const fetchData = () => {
|
||||
.list-box {
|
||||
overflow-x hidden
|
||||
//overflow-y auto
|
||||
|
||||
background: var(--chat-bg);
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
border-radius: 10px;
|
||||
.handle-box {
|
||||
padding 20px 0
|
||||
|
||||
|
@ -1,93 +1,104 @@
|
||||
<template>
|
||||
<div class="page-song" :style="{ height: winHeight + 'px' }">
|
||||
<div class="inner">
|
||||
<h2 class="title">{{song.title}}</h2>
|
||||
<h2 class="title">{{ song.title }}</h2>
|
||||
<div class="row tags" v-if="song.tags">
|
||||
<span>{{song.tags}}</span>
|
||||
<span>{{ song.tags }}</span>
|
||||
</div>
|
||||
|
||||
<div class="row author">
|
||||
<span>
|
||||
<el-avatar :size="32" :src="song.user?.avatar" />
|
||||
</span>
|
||||
<span class="nickname">{{song.user?.nickname}}</span>
|
||||
<span class="nickname">{{ song.user?.nickname }}</span>
|
||||
<button class="btn btn-icon" @click="play">
|
||||
<i class="iconfont icon-play"></i> {{song.play_times}}
|
||||
<i class="iconfont icon-play"></i> {{ song.play_times }}
|
||||
</button>
|
||||
|
||||
<el-tooltip effect="light" content="复制歌曲链接" placement="top">
|
||||
<button class="btn btn-icon copy-link" :data-clipboard-text="getShareURL(song)" >
|
||||
<el-tooltip content="复制歌曲链接" placement="top">
|
||||
<button
|
||||
class="btn btn-icon copy-link"
|
||||
:data-clipboard-text="getShareURL(song)"
|
||||
>
|
||||
<i class="iconfont icon-share1"></i>
|
||||
</button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<div class="row date">
|
||||
<span>{{dateFormat(song.created_at)}}</span>
|
||||
<span class="version">{{song.raw_data?.major_model_version}}</span>
|
||||
<span>{{ dateFormat(song.created_at) }}</span>
|
||||
<span class="version">{{ song.raw_data?.major_model_version }}</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<textarea class="prompt" maxlength="2000" rows="18" readonly>{{song.prompt}}</textarea>
|
||||
<textarea class="prompt" maxlength="2000" rows="18" readonly>{{
|
||||
song.prompt
|
||||
}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="music-player" v-if="playList.length > 0">
|
||||
<music-player :songs="playList" ref="playerRef" @play="song.play_times += 1"/>
|
||||
<music-player
|
||||
:songs="playList"
|
||||
ref="playerRef"
|
||||
@play="song.play_times += 1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, onUnmounted, ref} from "vue"
|
||||
import {useRouter} from "vue-router";
|
||||
import {httpGet} from "@/utils/http";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import {dateFormat} from "@/utils/libs";
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { httpGet } from "@/utils/http";
|
||||
import { showMessageError } from "@/utils/dialog";
|
||||
import { dateFormat } from "@/utils/libs";
|
||||
import Clipboard from "clipboard";
|
||||
import {ElMessage} from "element-plus";
|
||||
import { ElMessage } from "element-plus";
|
||||
import MusicPlayer from "@/components/MusicPlayer.vue";
|
||||
|
||||
const router = useRouter()
|
||||
const id = router.currentRoute.value.params.id
|
||||
const song = ref({title:""})
|
||||
const playList = ref([])
|
||||
const playerRef = ref(null)
|
||||
const router = useRouter();
|
||||
const id = router.currentRoute.value.params.id;
|
||||
const song = ref({ title: "" });
|
||||
const playList = ref([]);
|
||||
const playerRef = ref(null);
|
||||
|
||||
httpGet("/api/suno/detail",{song_id:id}).then(res => {
|
||||
song.value = res.data
|
||||
playList.value = [song.value]
|
||||
document.title = song.value?.title+ " | By "+song.value?.user.nickname+" | Suno音乐"
|
||||
}).catch(e => {
|
||||
showMessageError("获取歌曲详情失败:"+e.message)
|
||||
})
|
||||
httpGet("/api/suno/detail", { song_id: id })
|
||||
.then((res) => {
|
||||
song.value = res.data;
|
||||
playList.value = [song.value];
|
||||
document.title =
|
||||
song.value?.title + " | By " + song.value?.user.nickname + " | Suno音乐";
|
||||
})
|
||||
.catch((e) => {
|
||||
showMessageError("获取歌曲详情失败:" + e.message);
|
||||
});
|
||||
|
||||
const clipboard = ref(null)
|
||||
const clipboard = ref(null);
|
||||
onMounted(() => {
|
||||
clipboard.value = new Clipboard('.copy-link');
|
||||
clipboard.value.on('success', () => {
|
||||
clipboard.value = new Clipboard(".copy-link");
|
||||
clipboard.value.on("success", () => {
|
||||
ElMessage.success("复制歌曲链接成功!");
|
||||
})
|
||||
});
|
||||
|
||||
clipboard.value.on('error', () => {
|
||||
ElMessage.error('复制失败!');
|
||||
})
|
||||
})
|
||||
clipboard.value.on("error", () => {
|
||||
ElMessage.error("复制失败!");
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clipboard.value.destroy()
|
||||
})
|
||||
clipboard.value.destroy();
|
||||
});
|
||||
|
||||
// 播放歌曲
|
||||
const play = () => {
|
||||
playerRef.value.play()
|
||||
}
|
||||
playerRef.value.play();
|
||||
};
|
||||
|
||||
|
||||
const winHeight = ref(window.innerHeight-50)
|
||||
const winHeight = ref(window.innerHeight - 50);
|
||||
const getShareURL = (item) => {
|
||||
return `${location.protocol}//${location.host}/song/${item.id}`
|
||||
}
|
||||
return `${location.protocol}//${location.host}/song/${item.id}`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
@ -9,8 +9,8 @@
|
||||
<template #default="props">
|
||||
<div>
|
||||
<el-table :data="props.row.context" :border="childBorder">
|
||||
<el-table-column label="对话应用" prop="role" width="120"/>
|
||||
<el-table-column label="对话内容" prop="content"/>
|
||||
<el-table-column label="对话应用" prop="role" width="120" />
|
||||
<el-table-column label="对话内容" prop="content" />
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
@ -23,24 +23,39 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应用类型" prop="type_name"/>
|
||||
<el-table-column label="应用标识" prop="key"/>
|
||||
<el-table-column label="绑定模型" prop="model_name"/>
|
||||
<el-table-column label="应用类型" prop="type_name" />
|
||||
<el-table-column label="应用标识" prop="key" />
|
||||
<el-table-column label="绑定模型" prop="model_name" />
|
||||
<el-table-column label="启用状态">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row['enable']" @change="roleSet('enable',scope.row)"/>
|
||||
<el-switch
|
||||
v-model="scope.row['enable']"
|
||||
@change="roleSet('enable', scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="应用图标" prop="icon">
|
||||
<template #default="scope">
|
||||
<el-image :src="scope.row.icon" style="width: 45px; height: 45px; border-radius: 50%"/>
|
||||
<el-image
|
||||
:src="scope.row.icon"
|
||||
style="width: 45px; height: 45px; border-radius: 50%"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="打招呼信息" prop="hello_msg"/>
|
||||
<el-table-column label="打招呼信息" prop="hello_msg" />
|
||||
<el-table-column label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" @click="rowEdit(scope.$index, scope.row)">编辑</el-button>
|
||||
<el-popconfirm title="确定要删除当前应用吗?" @confirm="removeRole(scope.row)" :width="200">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="rowEdit(scope.$index, scope.row)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-popconfirm
|
||||
title="确定要删除当前应用吗?"
|
||||
@confirm="removeRole(scope.row)"
|
||||
:width="200"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">删除</el-button>
|
||||
</template>
|
||||
@ -56,12 +71,15 @@
|
||||
:close-on-click-modal="false"
|
||||
width="50%"
|
||||
>
|
||||
<el-form :model="role" label-width="120px" ref="formRef" label-position="left" :rules="rules">
|
||||
<el-form
|
||||
:model="role"
|
||||
label-width="120px"
|
||||
ref="formRef"
|
||||
label-position="left"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form-item label="应用名称:" prop="name">
|
||||
<el-input
|
||||
v-model="role.name"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<el-input v-model="role.name" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用分类:" prop="tid">
|
||||
<el-select
|
||||
@ -80,10 +98,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="应用标志:" prop="key">
|
||||
<el-input
|
||||
v-model="role.key"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<el-input v-model="role.key" autocomplete="off" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="应用图标:" prop="icon">
|
||||
@ -117,10 +132,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="打招呼信息:" prop="hello_msg">
|
||||
<el-input
|
||||
v-model="role.hello_msg"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<el-input v-model="role.hello_msg" autocomplete="off" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="上下文信息:" prop="context">
|
||||
@ -143,9 +155,13 @@
|
||||
<div class="context-msg-key">
|
||||
<span>对话内容</span>
|
||||
<span class="fr">
|
||||
<el-button type="primary" @click="addContext" size="small">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="addContext"
|
||||
size="small"
|
||||
>
|
||||
<el-icon>
|
||||
<Plus/>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
增加一行
|
||||
</el-button>
|
||||
@ -163,15 +179,30 @@
|
||||
v-loading="isGenerating"
|
||||
/>
|
||||
<span class="remove-item">
|
||||
<el-tooltip effect="dark" content="删除当前行" placement="right">
|
||||
<el-tooltip
|
||||
effect="dark"
|
||||
content="删除当前行"
|
||||
placement="right"
|
||||
>
|
||||
<el-button circle type="danger" size="small">
|
||||
<el-icon @click="removeContext(scope.$index)"><Delete /></el-icon>
|
||||
<el-icon @click="removeContext(scope.$index)"
|
||||
><Delete
|
||||
/></el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-popover placement="right" :width="400" trigger="click">
|
||||
<el-popover
|
||||
placement="right"
|
||||
:width="400"
|
||||
trigger="click"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button type="primary" circle size="small" class="icon-btn">
|
||||
<el-button
|
||||
type="primary"
|
||||
circle
|
||||
size="small"
|
||||
class="icon-btn"
|
||||
>
|
||||
<i class="iconfont icon-linggan"></i>
|
||||
</el-button>
|
||||
</template>
|
||||
@ -183,8 +214,16 @@
|
||||
placeholder="请您输入要 AI实现的目标,任务或者需要AI扮演的角色?"
|
||||
/>
|
||||
<el-row class="text-line">
|
||||
<el-text class="mx-1" type="info" size="small">使用 AI 生成 System 预设指令</el-text>
|
||||
<el-button class="generate-btn" size="small" @click="generatePrompt(scope.row)" color="#5865f2" :disabled="isGenerating">
|
||||
<el-text type="info" size="small"
|
||||
>使用 AI 生成 System 预设指令</el-text
|
||||
>
|
||||
<el-button
|
||||
class="generate-btn"
|
||||
size="small"
|
||||
@click="generatePrompt(scope.row)"
|
||||
color="#5865f2"
|
||||
:disabled="isGenerating"
|
||||
>
|
||||
<i class="iconfont icon-chuangzuo"></i>
|
||||
<span>立即生成</span>
|
||||
</el-button>
|
||||
@ -199,7 +238,7 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="role.enable"/>
|
||||
<el-switch v-model="role.enable" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@ -214,62 +253,67 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {Delete, Plus} from "@element-plus/icons-vue";
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {copyObj, removeArrayItem} from "@/utils/libs";
|
||||
import {Sortable} from "sortablejs"
|
||||
import { Delete, Plus } from "@element-plus/icons-vue";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { copyObj, removeArrayItem } from "@/utils/libs";
|
||||
import { Sortable } from "sortablejs";
|
||||
import Compressor from "compressorjs";
|
||||
import {showMessageError} from "@/utils/dialog";
|
||||
import { showMessageError } from "@/utils/dialog";
|
||||
|
||||
const showDialog = ref(false)
|
||||
const parentBorder = ref(true)
|
||||
const childBorder = ref(true)
|
||||
const tableData = ref([])
|
||||
const sortedTableData = ref([])
|
||||
const role = ref({context: []})
|
||||
const formRef = ref(null)
|
||||
const optTitle = ref("")
|
||||
const loading = ref(true)
|
||||
const showDialog = ref(false);
|
||||
const parentBorder = ref(true);
|
||||
const childBorder = ref(true);
|
||||
const tableData = ref([]);
|
||||
const sortedTableData = ref([]);
|
||||
const role = ref({ context: [] });
|
||||
const formRef = ref(null);
|
||||
const optTitle = ref("");
|
||||
const loading = ref(true);
|
||||
|
||||
const rules = reactive({
|
||||
name: [{required: true, message: '请输入用户名', trigger: 'blur',}],
|
||||
key: [{required: true, message: '请输入应用标识', trigger: 'blur',}],
|
||||
icon: [{required: true, message: '请输入应用图标', trigger: 'blur',}],
|
||||
name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
|
||||
key: [{ required: true, message: "请输入应用标识", trigger: "blur" }],
|
||||
icon: [{ required: true, message: "请输入应用图标", trigger: "blur" }],
|
||||
sort: [
|
||||
{required: true, message: '请输入排序数字', trigger: 'blur'},
|
||||
{type: 'number', message: '请输入有效数字'},
|
||||
{ required: true, message: "请输入排序数字", trigger: "blur" },
|
||||
{ type: "number", message: "请输入有效数字" }
|
||||
],
|
||||
hello_msg: [{required: true, message: '请输入打招呼信息', trigger: 'change',}]
|
||||
})
|
||||
hello_msg: [
|
||||
{ required: true, message: "请输入打招呼信息", trigger: "change" }
|
||||
]
|
||||
});
|
||||
|
||||
const appTypes = ref([])
|
||||
const models = ref([])
|
||||
const messageRoles = ref(["system", "user", "assistant"])
|
||||
const appTypes = ref([]);
|
||||
const models = ref([]);
|
||||
const messageRoles = ref(["system", "user", "assistant"]);
|
||||
onMounted(() => {
|
||||
fetchData()
|
||||
fetchData();
|
||||
|
||||
// get chat models
|
||||
httpGet('/api/admin/model/list?enable=1').then((res) => {
|
||||
models.value = res.data
|
||||
}).catch(() => {
|
||||
ElMessage.error("获取AI模型数据失败");
|
||||
httpGet("/api/admin/model/list?enable=1")
|
||||
.then((res) => {
|
||||
models.value = res.data;
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("获取AI模型数据失败");
|
||||
});
|
||||
|
||||
// get app type
|
||||
httpGet('/api/admin/app/type/list?enable=1').then((res) => {
|
||||
appTypes.value = res.data
|
||||
}).catch(() => {
|
||||
ElMessage.error("获取应用分类数据失败");
|
||||
httpGet("/api/admin/app/type/list?enable=1")
|
||||
.then((res) => {
|
||||
appTypes.value = res.data;
|
||||
})
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("获取应用分类数据失败");
|
||||
});
|
||||
});
|
||||
|
||||
const fetchData = () => {
|
||||
// 获取应用列表
|
||||
httpGet('/api/admin/role/list').then((res) => {
|
||||
httpGet("/api/admin/role/list")
|
||||
.then((res) => {
|
||||
// 初始化数据
|
||||
// const arr = res.data;
|
||||
// for (let i = 0; i < arr.length; i++) {
|
||||
@ -277,97 +321,112 @@ const fetchData = () => {
|
||||
// arr[i].model_id = ''
|
||||
// }
|
||||
// }
|
||||
tableData.value = res.data
|
||||
sortedTableData.value = copyObj(tableData.value)
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
ElMessage.error("获取聊天应用失败");
|
||||
tableData.value = res.data;
|
||||
sortedTableData.value = copyObj(tableData.value);
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("获取聊天应用失败");
|
||||
});
|
||||
|
||||
const drawBodyWrapper = document.querySelector('.el-table__body tbody')
|
||||
const drawBodyWrapper = document.querySelector(".el-table__body tbody");
|
||||
// 初始化拖动排序插件
|
||||
Sortable.create(drawBodyWrapper, {
|
||||
sort: true,
|
||||
animation: 500,
|
||||
onEnd({newIndex, oldIndex, from}) {
|
||||
onEnd({ newIndex, oldIndex, from }) {
|
||||
if (oldIndex === newIndex) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const sortedData = Array.from(from.children).map(row => row.querySelector('.sort').getAttribute('data-id'));
|
||||
const ids = []
|
||||
const sorts = []
|
||||
const sortedData = Array.from(from.children).map((row) =>
|
||||
row.querySelector(".sort").getAttribute("data-id")
|
||||
);
|
||||
const ids = [];
|
||||
const sorts = [];
|
||||
sortedData.forEach((id, index) => {
|
||||
ids.push(parseInt(id))
|
||||
sorts.push(index+1)
|
||||
tableData.value[index].sort_num = index + 1
|
||||
})
|
||||
ids.push(parseInt(id));
|
||||
sorts.push(index + 1);
|
||||
tableData.value[index].sort_num = index + 1;
|
||||
});
|
||||
|
||||
httpPost("/api/admin/role/sort", {ids: ids, sorts: sorts}).catch(e => {
|
||||
ElMessage.error("排序失败:" + e.message)
|
||||
})
|
||||
httpPost("/api/admin/role/sort", { ids: ids, sorts: sorts }).catch(
|
||||
(e) => {
|
||||
ElMessage.error("排序失败:" + e.message);
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const roleSet = (filed, row) => {
|
||||
httpPost('/api/admin/role/set', {id: row.id, filed: filed, value: row[filed]}).then(() => {
|
||||
ElMessage.success("操作成功!")
|
||||
}).catch(e => {
|
||||
ElMessage.error("操作失败:" + e.message)
|
||||
httpPost("/api/admin/role/set", {
|
||||
id: row.id,
|
||||
filed: filed,
|
||||
value: row[filed]
|
||||
})
|
||||
}
|
||||
.then(() => {
|
||||
ElMessage.success("操作成功!");
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("操作失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
// 编辑
|
||||
const curIndex = ref(0)
|
||||
const curIndex = ref(0);
|
||||
const rowEdit = function (index, row) {
|
||||
optTitle.value = "修改应用"
|
||||
curIndex.value = index
|
||||
role.value = copyObj(row)
|
||||
showDialog.value = true
|
||||
}
|
||||
optTitle.value = "修改应用";
|
||||
curIndex.value = index;
|
||||
role.value = copyObj(row);
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const addRole = function () {
|
||||
optTitle.value = "添加新应用"
|
||||
role.value = {context: []}
|
||||
showDialog.value = true
|
||||
}
|
||||
optTitle.value = "添加新应用";
|
||||
role.value = { context: [] };
|
||||
showDialog.value = true;
|
||||
};
|
||||
|
||||
const save = function () {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
showDialog.value = false
|
||||
httpPost('/api/admin/role/save', role.value).then(() => {
|
||||
ElMessage.success('操作成功')
|
||||
fetchData()
|
||||
}).catch((e) => {
|
||||
ElMessage.error('操作失败,' + e.message)
|
||||
showDialog.value = false;
|
||||
httpPost("/api/admin/role/save", role.value)
|
||||
.then(() => {
|
||||
ElMessage.success("操作成功");
|
||||
fetchData();
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("操作失败," + e.message);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const removeRole = function (row) {
|
||||
httpGet('/api/admin/role/remove?id=' + row.id).then(() => {
|
||||
ElMessage.success("删除成功!")
|
||||
httpGet("/api/admin/role/remove?id=" + row.id)
|
||||
.then(() => {
|
||||
ElMessage.success("删除成功!");
|
||||
tableData.value = removeArrayItem(tableData.value, row, (v1, v2) => {
|
||||
return v1.id === v2.id
|
||||
return v1.id === v2.id;
|
||||
});
|
||||
})
|
||||
}).catch(() => {
|
||||
ElMessage.error("删除失败!")
|
||||
})
|
||||
}
|
||||
.catch(() => {
|
||||
ElMessage.error("删除失败!");
|
||||
});
|
||||
};
|
||||
|
||||
const addContext = function () {
|
||||
if (!role.value.context) {
|
||||
role.value.context = []
|
||||
role.value.context = [];
|
||||
}
|
||||
role.value.context.push({role: '', content: ''})
|
||||
}
|
||||
role.value.context.push({ role: "", content: "" });
|
||||
};
|
||||
|
||||
const removeContext = function (index) {
|
||||
role.value.context.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// 图片上传
|
||||
const uploadImg = (file) => {
|
||||
@ -376,36 +435,40 @@ const uploadImg = (file) => {
|
||||
quality: 0.6,
|
||||
success(result) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', result, result.name);
|
||||
formData.append("file", result, result.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/admin/upload', formData).then((res) => {
|
||||
role.value.icon = res.data.url
|
||||
ElMessage.success('上传成功')
|
||||
}).catch((e) => {
|
||||
ElMessage.error('上传失败:' + e.message)
|
||||
httpPost("/api/admin/upload", formData)
|
||||
.then((res) => {
|
||||
role.value.icon = res.data.url;
|
||||
ElMessage.success("上传成功");
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("上传失败:" + e.message);
|
||||
});
|
||||
},
|
||||
error(e) {
|
||||
ElMessage.error('上传失败:' + e.message)
|
||||
},
|
||||
ElMessage.error("上传失败:" + e.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isGenerating = ref(false)
|
||||
const metaPrompt = ref("")
|
||||
const isGenerating = ref(false);
|
||||
const metaPrompt = ref("");
|
||||
const generatePrompt = (row) => {
|
||||
if (metaPrompt.value === "") {
|
||||
return showMessageError("请输入元提示词")
|
||||
return showMessageError("请输入元提示词");
|
||||
}
|
||||
isGenerating.value = true
|
||||
httpPost("/api/prompt/meta", {prompt: metaPrompt.value}).then(res => {
|
||||
row.content = res.data
|
||||
isGenerating.value = false
|
||||
}).catch(e => {
|
||||
showMessageError("生成失败:"+e.message)
|
||||
isGenerating.value = false
|
||||
isGenerating.value = true;
|
||||
httpPost("/api/prompt/meta", { prompt: metaPrompt.value })
|
||||
.then((res) => {
|
||||
row.content = res.data;
|
||||
isGenerating.value = false;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
showMessageError("生成失败:" + e.message);
|
||||
isGenerating.value = false;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
@ -3,10 +3,18 @@
|
||||
<el-tabs v-model="activeName" @tab-change="handleChange">
|
||||
<el-tab-pane label="对话列表" name="chat" v-loading="data.chat.loading">
|
||||
<div class="handle-box">
|
||||
<el-input v-model.number="data.chat.query.user_id" placeholder="账户ID" class="handle-input mr10"
|
||||
@keyup="searchChat($event)"></el-input>
|
||||
<el-input v-model="data.chat.query.title" placeholder="对话标题" class="handle-input mr10"
|
||||
@keyup="searchChat($event)"></el-input>
|
||||
<el-input
|
||||
v-model.number="data.chat.query.user_id"
|
||||
placeholder="账户ID"
|
||||
class="handle-input mr10"
|
||||
@keyup="searchChat($event)"
|
||||
></el-input>
|
||||
<el-input
|
||||
v-model="data.chat.query.title"
|
||||
placeholder="对话标题"
|
||||
class="handle-input mr10"
|
||||
@keyup="searchChat($event)"
|
||||
></el-input>
|
||||
<el-date-picker
|
||||
v-model="data.chat.query.created_at"
|
||||
type="daterange"
|
||||
@ -14,18 +22,29 @@
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="margin-right: 10px;width: 200px; position: relative;top:3px;"
|
||||
style="
|
||||
margin-right: 10px;
|
||||
width: 200px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
"
|
||||
/>
|
||||
<el-button type="primary" :icon="Search" @click="fetchChatData">搜索</el-button>
|
||||
<el-button type="primary" :icon="Search" @click="fetchChatData"
|
||||
>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-row>
|
||||
<el-table :data="data.chat.items" :row-key="row => row.id" table-layout="auto">
|
||||
<el-table-column prop="user_id" label="账户ID"/>
|
||||
<el-table-column prop="username" label="账户"/>
|
||||
<el-table
|
||||
:data="data.chat.items"
|
||||
:row-key="(row) => row.id"
|
||||
table-layout="auto"
|
||||
>
|
||||
<el-table-column prop="user_id" label="账户ID" />
|
||||
<el-table-column prop="username" label="账户" />
|
||||
<el-table-column label="图标">
|
||||
<template #default="scope">
|
||||
<el-avatar :size="30" :src="scope.row.role.icon"/>
|
||||
<el-avatar :size="30" :src="scope.row.role.icon" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="角色">
|
||||
@ -33,21 +52,29 @@
|
||||
<span>{{ scope.row.role.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="model" label="模型"/>
|
||||
<el-table-column prop="title" label="标题"/>
|
||||
<el-table-column prop="msg_num" label="消息数量"/>
|
||||
<el-table-column prop="token" label="消耗算力"/>
|
||||
<el-table-column prop="model" label="模型" />
|
||||
<el-table-column prop="title" label="标题" />
|
||||
<el-table-column prop="msg_num" label="消息数量" />
|
||||
<el-table-column prop="token" label="消耗算力" />
|
||||
|
||||
<el-table-column label="创建时间">
|
||||
<template #default="scope">
|
||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
||||
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" @click="showMessages(scope.row)">查看</el-button>
|
||||
<el-popconfirm title="确定要删除当前记录吗?" @confirm="removeChat(scope.row)">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="showMessages(scope.row)"
|
||||
>查看</el-button
|
||||
>
|
||||
<el-popconfirm
|
||||
title="确定要删除当前记录吗?"
|
||||
@confirm="removeChat(scope.row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">删除</el-button>
|
||||
</template>
|
||||
@ -58,24 +85,38 @@
|
||||
</el-row>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination v-if="data.chat.total > 0" background
|
||||
<el-pagination
|
||||
v-if="data.chat.total > 0"
|
||||
background
|
||||
layout="total,prev, pager, next"
|
||||
:hide-on-single-page="true"
|
||||
v-model:current-page="data.chat.page"
|
||||
v-model:page-size="data.chat.pageSize"
|
||||
@current-change="fetchChatData()"
|
||||
:total="data.chat.total"/>
|
||||
|
||||
:total="data.chat.total"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="消息记录" name="message">
|
||||
<div class="handle-box">
|
||||
<el-input v-model.number="data.message.query.user_id" placeholder="账户ID" class="handle-input mr10"
|
||||
@keyup="searchMessage($event)"></el-input>
|
||||
<el-input v-model="data.message.query.content" placeholder="消息内容" class="handle-input mr10"
|
||||
@keyup="searchMessage($event)"></el-input>
|
||||
<el-input v-model="data.message.query.model" placeholder="模型" class="handle-input mr10"
|
||||
@keyup="searchMessage($event)"></el-input>
|
||||
<el-input
|
||||
v-model.number="data.message.query.user_id"
|
||||
placeholder="账户ID"
|
||||
class="handle-input mr10"
|
||||
@keyup="searchMessage($event)"
|
||||
></el-input>
|
||||
<el-input
|
||||
v-model="data.message.query.content"
|
||||
placeholder="消息内容"
|
||||
class="handle-input mr10"
|
||||
@keyup="searchMessage($event)"
|
||||
></el-input>
|
||||
<el-input
|
||||
v-model="data.message.query.model"
|
||||
placeholder="模型"
|
||||
class="handle-input mr10"
|
||||
@keyup="searchMessage($event)"
|
||||
></el-input>
|
||||
<el-date-picker
|
||||
v-model="data.message.query.created_at"
|
||||
type="daterange"
|
||||
@ -83,42 +124,65 @@
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
style="margin-right: 10px;width: 200px; position: relative;top:3px;"
|
||||
style="
|
||||
margin-right: 10px;
|
||||
width: 200px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
"
|
||||
/>
|
||||
<el-button type="primary" :icon="Search" @click="fetchMessageData">搜索</el-button>
|
||||
<el-button type="primary" :icon="Search" @click="fetchMessageData"
|
||||
>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-row>
|
||||
<el-table :data="data.message.items" :row-key="row => row.id" table-layout="auto">
|
||||
<el-table-column prop="user_id" label="账户ID"/>
|
||||
<el-table-column prop="username" label="账户"/>
|
||||
<el-table
|
||||
:data="data.message.items"
|
||||
:row-key="(row) => row.id"
|
||||
table-layout="auto"
|
||||
>
|
||||
<el-table-column prop="user_id" label="账户ID" />
|
||||
<el-table-column prop="username" label="账户" />
|
||||
<el-table-column label="角色">
|
||||
<template #default="scope">
|
||||
<el-avatar :size="30" :src="scope.row.icon"/>
|
||||
<el-avatar :size="30" :src="scope.row.icon" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="model" label="模型"/>
|
||||
<el-table-column prop="model" label="模型" />
|
||||
|
||||
<el-table-column label="消息内容">
|
||||
<template #default="scope">
|
||||
<el-text style="width: 200px" truncated @click="showContent(scope.row.content)">
|
||||
<el-text
|
||||
style="width: 200px"
|
||||
truncated
|
||||
@click="showContent(scope.row.content)"
|
||||
>
|
||||
{{ scope.row.content }}
|
||||
</el-text>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="token" label="算力"/>
|
||||
<el-table-column prop="token" label="算力" />
|
||||
|
||||
<el-table-column label="创建时间">
|
||||
<template #default="scope">
|
||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
||||
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" @click="showContent(scope.row.content)">查看</el-button>
|
||||
<el-popconfirm title="确定要删除当前记录吗?" @confirm="removeMessage(scope.row)">
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="showContent(scope.row.content)"
|
||||
>查看</el-button
|
||||
>
|
||||
<el-popconfirm
|
||||
title="确定要删除当前记录吗?"
|
||||
@confirm="removeMessage(scope.row)"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">删除</el-button>
|
||||
</template>
|
||||
@ -129,24 +193,25 @@
|
||||
</el-row>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination v-if="data.message.total > 0" background
|
||||
<el-pagination
|
||||
v-if="data.message.total > 0"
|
||||
background
|
||||
layout="total,prev, pager, next"
|
||||
:hide-on-single-page="true"
|
||||
v-model:current-page="data.message.page"
|
||||
v-model:page-size="data.message.pageSize"
|
||||
@current-change="fetchMessageData()"
|
||||
:total="data.message.total"/>
|
||||
|
||||
:total="data.message.total"
|
||||
/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
|
||||
<el-dialog
|
||||
v-model="showContentDialog"
|
||||
title="消息详情"
|
||||
class="chat-dialog"
|
||||
style="--el-dialog-width:60%"
|
||||
style="--el-dialog-width: 60%"
|
||||
>
|
||||
<div class="chat-detail">
|
||||
<div class="chat-line" v-html="dialogContent"></div>
|
||||
@ -157,135 +222,144 @@
|
||||
v-model="showChatItemDialog"
|
||||
title="对话详情"
|
||||
class="chat-dialog"
|
||||
style="--el-dialog-width:60%"
|
||||
style="--el-dialog-width: 60%"
|
||||
>
|
||||
<div class="chat-box chat-page">
|
||||
<div v-for="item in messages" :key="item.id">
|
||||
<chat-prompt
|
||||
v-if="item.type==='prompt'"
|
||||
:data="item"/>
|
||||
<chat-reply v-else-if="item.type==='reply'"
|
||||
<chat-prompt v-if="item.type === 'prompt'" :data="item" />
|
||||
<chat-reply
|
||||
v-else-if="item.type === 'reply'"
|
||||
:read-only="true"
|
||||
:data="item"/>
|
||||
:data="item"
|
||||
/>
|
||||
</div>
|
||||
</div><!-- end chat box -->
|
||||
</div>
|
||||
<!-- end chat box -->
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat, processContent} from "@/utils/libs";
|
||||
import {Search} from "@element-plus/icons-vue";
|
||||
import 'highlight.js/styles/a11y-dark.css'
|
||||
import { onMounted, ref } from "vue";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { dateFormat, processContent } from "@/utils/libs";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
import "highlight.js/styles/a11y-dark.css";
|
||||
import hl from "highlight.js";
|
||||
import ChatPrompt from "@/components/ChatPrompt.vue";
|
||||
import ChatReply from "@/components/ChatReply.vue";
|
||||
|
||||
// 变量定义
|
||||
const data = ref({
|
||||
"chat": {
|
||||
chat: {
|
||||
items: [],
|
||||
query: {title: "", created_at: [], page: 1, page_size: 15},
|
||||
query: { title: "", created_at: [], page: 1, page_size: 15 },
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 15,
|
||||
loading: true
|
||||
},
|
||||
"message": {
|
||||
message: {
|
||||
items: [],
|
||||
query: {title: "", created_at: [], page: 1, page_size: 15},
|
||||
query: { title: "", created_at: [], page: 1, page_size: 15 },
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 15,
|
||||
loading: true
|
||||
}
|
||||
})
|
||||
const activeName = ref("chat")
|
||||
});
|
||||
const activeName = ref("chat");
|
||||
|
||||
onMounted(() => {
|
||||
fetchChatData()
|
||||
})
|
||||
fetchChatData();
|
||||
});
|
||||
|
||||
const handleChange = (tab) => {
|
||||
if (tab === "chat") {
|
||||
fetchChatData()
|
||||
fetchChatData();
|
||||
} else if (tab === "message") {
|
||||
fetchMessageData()
|
||||
fetchMessageData();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索对话
|
||||
const searchChat = (evt) => {
|
||||
if (evt.keyCode === 13) {
|
||||
fetchChatData()
|
||||
fetchChatData();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 搜索消息
|
||||
const searchMessage = (evt) => {
|
||||
if (evt.keyCode === 13) {
|
||||
fetchMessageData()
|
||||
fetchMessageData();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 获取数据
|
||||
const fetchChatData = () => {
|
||||
const d = data.value.chat
|
||||
d.query.page = d.page
|
||||
d.query.page_size = d.pageSize
|
||||
httpPost('/api/admin/chat/list', d.query).then((res) => {
|
||||
const d = data.value.chat;
|
||||
d.query.page = d.page;
|
||||
d.query.page_size = d.pageSize;
|
||||
httpPost("/api/admin/chat/list", d.query)
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
d.items = res.data.items
|
||||
d.total = res.data.total
|
||||
d.page = res.data.page
|
||||
d.pageSize = res.data.page_size
|
||||
d.items = res.data.items;
|
||||
d.total = res.data.total;
|
||||
d.page = res.data.page;
|
||||
d.pageSize = res.data.page_size;
|
||||
}
|
||||
d.loading = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
d.loading = false;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchMessageData = () => {
|
||||
const d = data.value.message
|
||||
d.query.page = d.page
|
||||
d.query.page_size = d.pageSize
|
||||
httpPost('/api/admin/chat/message', d.query).then((res) => {
|
||||
const d = data.value.message;
|
||||
d.query.page = d.page;
|
||||
d.query.page_size = d.pageSize;
|
||||
httpPost("/api/admin/chat/message", d.query)
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
d.items = res.data.items
|
||||
d.total = res.data.total
|
||||
d.page = res.data.page
|
||||
d.pageSize = res.data.page_size
|
||||
d.items = res.data.items;
|
||||
d.total = res.data.total;
|
||||
d.page = res.data.page;
|
||||
d.pageSize = res.data.page_size;
|
||||
}
|
||||
d.loading = false
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
d.loading = false;
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取数据失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const removeChat = function (row) {
|
||||
httpGet('/api/admin/chat/remove?chat_id=' + row.chat_id).then(() => {
|
||||
ElMessage.success("删除成功!")
|
||||
fetchChatData()
|
||||
}).catch((e) => {
|
||||
ElMessage.error("删除失败:" + e.message)
|
||||
httpGet("/api/admin/chat/remove?chat_id=" + row.chat_id)
|
||||
.then(() => {
|
||||
ElMessage.success("删除成功!");
|
||||
fetchChatData();
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("删除失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const removeMessage = function (row) {
|
||||
httpGet('/api/admin/chat/message/remove?id=' + row.id).then(() => {
|
||||
ElMessage.success("删除成功!")
|
||||
fetchMessageData()
|
||||
}).catch((e) => {
|
||||
ElMessage.error("删除失败:" + e.message)
|
||||
httpGet("/api/admin/chat/message/remove?id=" + row.id)
|
||||
.then(() => {
|
||||
ElMessage.success("删除成功!");
|
||||
fetchMessageData();
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
ElMessage.error("删除失败:" + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
const mathjaxPlugin = require('markdown-it-mathjax3')
|
||||
const md = require('markdown-it')({
|
||||
const mathjaxPlugin = require("markdown-it-mathjax3");
|
||||
const md = require("markdown-it")({
|
||||
breaks: true,
|
||||
html: true,
|
||||
linkify: true,
|
||||
@ -293,41 +367,43 @@ const md = require('markdown-it')({
|
||||
highlight: function (str, lang) {
|
||||
if (lang && hl.getLanguage(lang)) {
|
||||
// 处理代码高亮
|
||||
const preCode = hl.highlight(lang, str, true).value
|
||||
const preCode = hl.highlight(lang, str, true).value;
|
||||
// 将代码包裹在 pre 中
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`;
|
||||
}
|
||||
|
||||
// 处理代码高亮
|
||||
const preCode = md.utils.escapeHtml(str)
|
||||
const preCode = md.utils.escapeHtml(str);
|
||||
// 将代码包裹在 pre 中
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`
|
||||
return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`;
|
||||
}
|
||||
});
|
||||
md.use(mathjaxPlugin)
|
||||
md.use(mathjaxPlugin);
|
||||
|
||||
const showContentDialog = ref(false)
|
||||
const dialogContent = ref("")
|
||||
const showContentDialog = ref(false);
|
||||
const dialogContent = ref("");
|
||||
const showContent = (content) => {
|
||||
showContentDialog.value = true
|
||||
dialogContent.value = md.render(processContent(content))
|
||||
}
|
||||
showContentDialog.value = true;
|
||||
dialogContent.value = md.render(processContent(content));
|
||||
};
|
||||
|
||||
const showChatItemDialog = ref(false)
|
||||
const messages = ref([])
|
||||
const showChatItemDialog = ref(false);
|
||||
const messages = ref([]);
|
||||
const showMessages = (row) => {
|
||||
showChatItemDialog.value = true
|
||||
messages.value = []
|
||||
httpGet('/api/admin/chat/history?chat_id=' + row.chat_id).then(res => {
|
||||
const data = res.data
|
||||
showChatItemDialog.value = true;
|
||||
messages.value = [];
|
||||
httpGet("/api/admin/chat/history?chat_id=" + row.chat_id)
|
||||
.then((res) => {
|
||||
const data = res.data;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
messages.value.push(data[i]);
|
||||
}
|
||||
}).catch(e => {
|
||||
// TODO: 显示重新加载按钮
|
||||
ElMessage.error('加载聊天记录失败:' + e.message);
|
||||
})
|
||||
}
|
||||
.catch((e) => {
|
||||
// TODO: 显示重新加载按钮
|
||||
ElMessage.error("加载聊天记录失败:" + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|
@ -93,6 +93,7 @@
|
||||
<el-pagination v-if="data.mj.total > 0" background
|
||||
layout="total,prev, pager, next"
|
||||
:hide-on-single-page="true"
|
||||
|
||||
v-model:current-page="data.mj.page"
|
||||
v-model:page-size="data.mj.pageSize"
|
||||
@current-change="fetchMjData()"
|
||||
|
@ -1,27 +1,47 @@
|
||||
<template>
|
||||
<div class="container user-list" v-loading="loading">
|
||||
<div class="handle-box">
|
||||
<el-input v-model="query.username" placeholder="账号" class="handle-input mr10"></el-input>
|
||||
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
|
||||
<el-button type="success" :icon="Plus" @click="addUser">新增用户</el-button>
|
||||
<el-button type="danger" :icon="Delete" @click="multipleDelete">删除</el-button>
|
||||
<el-input
|
||||
v-model="query.username"
|
||||
placeholder="账号"
|
||||
class="handle-input mr10"
|
||||
></el-input>
|
||||
<el-button type="primary" :icon="Search" @click="handleSearch"
|
||||
>搜索</el-button
|
||||
>
|
||||
<el-button type="success" :icon="Plus" @click="addUser"
|
||||
>新增用户</el-button
|
||||
>
|
||||
<el-button type="danger" :icon="Delete" @click="multipleDelete"
|
||||
>删除</el-button
|
||||
>
|
||||
</div>
|
||||
|
||||
<el-row>
|
||||
<el-table :data="users.items" border class="table" :row-key="row => row.id"
|
||||
@selection-change="handleSelectionChange" table-layout="auto">
|
||||
<el-table
|
||||
:data="users.items"
|
||||
border
|
||||
class="table"
|
||||
:row-key="(row) => row.id"
|
||||
@selection-change="handleSelectionChange"
|
||||
table-layout="auto"
|
||||
>
|
||||
<el-table-column type="selection" width="38"></el-table-column>
|
||||
<el-table-column prop="id" label="ID"/>
|
||||
<el-table-column prop="id" label="ID" />
|
||||
<el-table-column label="账号">
|
||||
<template #default="scope">
|
||||
<span>{{ scope.row.username }}</span>
|
||||
<el-image v-if="scope.row.vip" :src="vipImg" style="height: 20px;position: relative; top:5px; left: 5px"/>
|
||||
<el-image
|
||||
v-if="scope.row.vip"
|
||||
:src="vipImg"
|
||||
style="height: 20px; position: relative; top: 5px; left: 5px"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="mobile" label="手机"/>
|
||||
<el-table-column prop="email" label="邮箱"/>
|
||||
<el-table-column prop="nickname" label="昵称"/>
|
||||
<el-table-column prop="power" label="剩余算力"/>
|
||||
<el-table-column prop="mobile" label="手机" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column prop="nickname" label="昵称" />
|
||||
<el-table-column prop="power" label="剩余算力" />
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.status" type="success">正常</el-tag>
|
||||
@ -30,30 +50,48 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="过期时间">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row['expired_time']">{{ scope.row['expired_time'] }}</span>
|
||||
<span v-if="scope.row['expired_time']">{{
|
||||
scope.row["expired_time"]
|
||||
}}</span>
|
||||
<el-tag v-else>长期有效</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="注册时间">
|
||||
<template #default="scope">
|
||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
||||
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column fixed="right" label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<el-button-group class="ml-4">
|
||||
<el-button size="small" type="primary" @click="userEdit(scope.row)">编辑</el-button>
|
||||
<el-button size="small" type="danger" @click="removeUser(scope.row)">删除</el-button>
|
||||
<el-button size="small" type="success" @click="resetPass(scope.row)">重置密码</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="userEdit(scope.row)"
|
||||
>编辑</el-button
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="removeUser(scope.row)"
|
||||
>删除</el-button
|
||||
>
|
||||
<el-button
|
||||
size="small"
|
||||
type="success"
|
||||
@click="resetPass(scope.row)"
|
||||
>重置密码</el-button
|
||||
>
|
||||
</el-button-group>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination">
|
||||
<el-pagination v-if="users.total > 0"
|
||||
<el-pagination
|
||||
v-if="users.total > 0"
|
||||
background
|
||||
layout="total, prev, pager, next"
|
||||
v-model:current-page="users.page"
|
||||
@ -61,7 +99,6 @@
|
||||
:total="users.total"
|
||||
@current-change="fetchUserList(users.page, users.page_size)"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</el-row>
|
||||
|
||||
@ -71,21 +108,34 @@
|
||||
:close-on-click-modal="false"
|
||||
width="50%"
|
||||
>
|
||||
<el-form :model="user" label-width="100px" ref="userEditFormRef" :rules="rules">
|
||||
<el-form
|
||||
:model="user"
|
||||
label-width="100px"
|
||||
ref="userEditFormRef"
|
||||
:rules="rules"
|
||||
>
|
||||
<el-form-item label="账号:" prop="username">
|
||||
<el-input v-model="user.username" autocomplete="off"/>
|
||||
<el-input v-model="user.username" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机:" prop="mobile">
|
||||
<el-input v-model="user.mobile" autocomplete="off"/>
|
||||
<el-input v-model="user.mobile" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱:" prop="email">
|
||||
<el-input v-model="user.email" autocomplete="off"/>
|
||||
<el-input v-model="user.email" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="add" label="密码:" prop="password">
|
||||
<el-input v-model="user.password" autocomplete="off" placeholder="8-16位"/>
|
||||
<el-input
|
||||
v-model="user.password"
|
||||
autocomplete="off"
|
||||
placeholder="8-16位"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="剩余算力:" prop="power">
|
||||
<el-input v-model.number="user.power" autocomplete="off" placeholder="0"/>
|
||||
<el-input
|
||||
v-model.number="user.power"
|
||||
autocomplete="off"
|
||||
placeholder="0"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="有效期:" prop="expired_time">
|
||||
@ -131,13 +181,12 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="启用状态">
|
||||
<el-switch v-model="user.status"/>
|
||||
<el-switch v-model="user.status" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="开通VIP">
|
||||
<el-switch v-model="user.vip"/>
|
||||
<el-switch v-model="user.vip" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@ -149,18 +198,19 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="showResetPassDialog"
|
||||
title="重置密码"
|
||||
width="50%"
|
||||
>
|
||||
<el-dialog v-model="showResetPassDialog" title="重置密码" width="50%">
|
||||
<el-form label-width="100px" ref="userEditFormRef">
|
||||
<el-form-item label="账户:">
|
||||
<el-input v-model="pass.username" autocomplete="off" readonly disabled/>
|
||||
<el-input
|
||||
v-model="pass.username"
|
||||
autocomplete="off"
|
||||
readonly
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="新密码:">
|
||||
<el-input v-model="pass.password" autocomplete="off"/>
|
||||
<el-input v-model="pass.password" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@ -169,198 +219,215 @@
|
||||
<el-button type="primary" @click="doResetPass">提交</el-button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {dateFormat, disabledDate} from "@/utils/libs";
|
||||
import {Delete, Plus, Search} from "@element-plus/icons-vue";
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { httpGet, httpPost } from "@/utils/http";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { dateFormat, disabledDate } from "@/utils/libs";
|
||||
import { Delete, Plus, Search } from "@element-plus/icons-vue";
|
||||
|
||||
// 变量定义
|
||||
const users = ref({page: 1, page_size: 15, items: []})
|
||||
const query = ref({username: '', page: 1, page_size: 15})
|
||||
const users = ref({ page: 1, page_size: 15, items: [] });
|
||||
const query = ref({ username: "", page: 1, page_size: 15 });
|
||||
|
||||
const title = ref('添加用户')
|
||||
const vipImg = ref("/images/vip.png")
|
||||
const add = ref(true)
|
||||
const user = ref({chat_roles: [], chat_models: []})
|
||||
const pass = ref({username: '', password: '', id: 0})
|
||||
const roles = ref([])
|
||||
const models = ref([])
|
||||
const showUserEditDialog = ref(false)
|
||||
const showResetPassDialog = ref(false)
|
||||
const title = ref("添加用户");
|
||||
const vipImg = ref("/images/menu/member.png");
|
||||
const add = ref(true);
|
||||
const user = ref({ chat_roles: [], chat_models: [] });
|
||||
const pass = ref({ username: "", password: "", id: 0 });
|
||||
const roles = ref([]);
|
||||
const models = ref([]);
|
||||
const showUserEditDialog = ref(false);
|
||||
const showResetPassDialog = ref(false);
|
||||
const rules = reactive({
|
||||
username: [{required: true, message: '请输入账号', trigger: 'blur',}],
|
||||
username: [{ required: true, message: "请输入账号", trigger: "blur" }],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
validator: (rule, value) => {
|
||||
return !(value.length > 16 || value.length < 8);
|
||||
|
||||
}, message: '密码必须为8-16',
|
||||
trigger: 'blur'
|
||||
},
|
||||
message: "密码必须为8-16",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
calls: [
|
||||
{required: true, message: '请输入提问次数'},
|
||||
{type: 'number', message: '请输入有效数字'},
|
||||
{ required: true, message: "请输入提问次数" },
|
||||
{ type: "number", message: "请输入有效数字" }
|
||||
],
|
||||
chat_roles: [{required: true, message: '请选择聊天角色', trigger: 'change'}],
|
||||
chat_models: [{required: true, message: '请选择AI模型', trigger: 'change'}],
|
||||
})
|
||||
const loading = ref(true)
|
||||
chat_roles: [
|
||||
{ required: true, message: "请选择聊天角色", trigger: "change" }
|
||||
],
|
||||
chat_models: [{ required: true, message: "请选择AI模型", trigger: "change" }]
|
||||
});
|
||||
const loading = ref(true);
|
||||
|
||||
const userEditFormRef = ref(null)
|
||||
const userEditFormRef = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
fetchUserList(users.value.page, users.value.page_size)
|
||||
fetchUserList(users.value.page, users.value.page_size);
|
||||
// 获取角色列表
|
||||
httpGet('/api/admin/role/list').then((res) => {
|
||||
httpGet("/api/admin/role/list")
|
||||
.then((res) => {
|
||||
roles.value = res.data;
|
||||
}).catch(() => {
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("获取聊天角色失败");
|
||||
})
|
||||
});
|
||||
|
||||
httpGet('/api/admin/model/list').then(res => {
|
||||
models.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取模型失败:" + e.message)
|
||||
httpGet("/api/admin/model/list")
|
||||
.then((res) => {
|
||||
models.value = res.data;
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("获取模型失败:" + e.message);
|
||||
});
|
||||
});
|
||||
|
||||
const fetchUserList = function (page, pageSize) {
|
||||
query.value.page = page
|
||||
query.value.page_size = pageSize
|
||||
httpGet('/api/admin/user/list', query.value).then((res) => {
|
||||
query.value.page = page;
|
||||
query.value.page_size = pageSize;
|
||||
httpGet("/api/admin/user/list", query.value)
|
||||
.then((res) => {
|
||||
if (res.data) {
|
||||
// 初始化数据
|
||||
const arr = res.data.items;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i].expired_time = dateFormat(arr[i].expired_time)
|
||||
arr[i].expired_time = dateFormat(arr[i].expired_time);
|
||||
}
|
||||
users.value.items = arr
|
||||
users.value.total = res.data.total
|
||||
users.value.page = res.data.page
|
||||
user.value.page_size = res.data.page_size
|
||||
users.value.items = arr;
|
||||
users.value.total = res.data.total;
|
||||
users.value.page = res.data.page;
|
||||
user.value.page_size = res.data.page_size;
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
ElMessage.error('加载用户列表失败')
|
||||
loading.value = false;
|
||||
})
|
||||
}
|
||||
.catch(() => {
|
||||
ElMessage.error("加载用户列表失败");
|
||||
});
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
fetchUserList(users.value.page, users.value.page_size)
|
||||
}
|
||||
fetchUserList(users.value.page, users.value.page_size);
|
||||
};
|
||||
|
||||
// 删除用户
|
||||
const removeUser = function (user) {
|
||||
ElMessageBox.confirm(
|
||||
'此操作将会永久删除用户信息和聊天记录,确认操作吗?',
|
||||
'警告',
|
||||
"此操作将会永久删除用户信息和聊天记录,确认操作吗?",
|
||||
"警告",
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}
|
||||
).then(() => {
|
||||
httpGet('/api/admin/user/remove', {id: user.id}).then(() => {
|
||||
ElMessage.success('操作成功!')
|
||||
fetchUserList(users.value.page, users.value.page_size)
|
||||
}).catch((e) => {
|
||||
ElMessage.error('操作失败,' + e.message)
|
||||
)
|
||||
.then(() => {
|
||||
httpGet("/api/admin/user/remove", { id: user.id })
|
||||
.then(() => {
|
||||
ElMessage.success("操作成功!");
|
||||
fetchUserList(users.value.page, users.value.page_size);
|
||||
})
|
||||
}).catch(() => {
|
||||
ElMessage.info('操作被取消')
|
||||
.catch((e) => {
|
||||
ElMessage.error("操作失败," + e.message);
|
||||
});
|
||||
})
|
||||
|
||||
}
|
||||
.catch(() => {
|
||||
ElMessage.info("操作被取消");
|
||||
});
|
||||
};
|
||||
|
||||
const userEdit = function (row) {
|
||||
user.value = row
|
||||
title.value = '编辑用户'
|
||||
showUserEditDialog.value = true
|
||||
add.value = false
|
||||
}
|
||||
user.value = row;
|
||||
title.value = "编辑用户";
|
||||
showUserEditDialog.value = true;
|
||||
add.value = false;
|
||||
};
|
||||
|
||||
const addUser = () => {
|
||||
user.value = {chat_id: 0, chat_roles: [], chat_models: []}
|
||||
title.value = '添加用户'
|
||||
showUserEditDialog.value = true
|
||||
add.value = true
|
||||
}
|
||||
user.value = { chat_id: 0, chat_roles: [], chat_models: [] };
|
||||
title.value = "添加用户";
|
||||
showUserEditDialog.value = true;
|
||||
add.value = true;
|
||||
};
|
||||
|
||||
const saveUser = function () {
|
||||
userEditFormRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
showUserEditDialog.value = false
|
||||
console.log(user.value)
|
||||
httpPost('/api/admin/user/save', user.value).then((res) => {
|
||||
ElMessage.success('操作成功!')
|
||||
showUserEditDialog.value = false;
|
||||
console.log(user.value);
|
||||
httpPost("/api/admin/user/save", user.value)
|
||||
.then((res) => {
|
||||
ElMessage.success("操作成功!");
|
||||
if (add.value) {
|
||||
users.value.items.push(res.data)
|
||||
users.value.items.push(res.data);
|
||||
}
|
||||
}).catch((e) => {
|
||||
ElMessage.error('操作失败,' + e.message)
|
||||
})
|
||||
.catch((e) => {
|
||||
ElMessage.error("操作失败," + e.message);
|
||||
});
|
||||
} else {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const userIds = ref([])
|
||||
const userIds = ref([]);
|
||||
const handleSelectionChange = function (rows) {
|
||||
userIds.value = []
|
||||
userIds.value = [];
|
||||
rows.forEach((row) => {
|
||||
userIds.value.push(row.id)
|
||||
})
|
||||
}
|
||||
userIds.value.push(row.id);
|
||||
});
|
||||
};
|
||||
|
||||
const multipleDelete = function () {
|
||||
ElMessageBox.confirm(
|
||||
'此操作将会永久删除用户信息和聊天记录,确认操作吗?',
|
||||
'警告',
|
||||
"此操作将会永久删除用户信息和聊天记录,确认操作吗?",
|
||||
"警告",
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}
|
||||
).then(() => {
|
||||
loading.value = true
|
||||
httpGet('/api/admin/user/remove', {ids: userIds.value}).then(() => {
|
||||
ElMessage.success('操作成功!')
|
||||
fetchUserList(users.value.page, users.value.page_size)
|
||||
loading.value = false
|
||||
}).catch((e) => {
|
||||
ElMessage.error('操作失败,' + e.message)
|
||||
loading.value = false
|
||||
)
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
httpGet("/api/admin/user/remove", { ids: userIds.value })
|
||||
.then(() => {
|
||||
ElMessage.success("操作成功!");
|
||||
fetchUserList(users.value.page, users.value.page_size);
|
||||
loading.value = false;
|
||||
})
|
||||
}).catch(() => {
|
||||
ElMessage.info('操作被取消')
|
||||
.catch((e) => {
|
||||
ElMessage.error("操作失败," + e.message);
|
||||
loading.value = false;
|
||||
});
|
||||
})
|
||||
}
|
||||
.catch(() => {
|
||||
ElMessage.info("操作被取消");
|
||||
});
|
||||
};
|
||||
|
||||
const resetPass = (row) => {
|
||||
showResetPassDialog.value = true
|
||||
pass.value.id = row.id
|
||||
pass.value.username = row.username
|
||||
}
|
||||
showResetPassDialog.value = true;
|
||||
pass.value.id = row.id;
|
||||
pass.value.username = row.username;
|
||||
};
|
||||
|
||||
const doResetPass = () => {
|
||||
httpPost('/api/admin/user/resetPass', pass.value).then(() => {
|
||||
ElMessage.success('操作成功!')
|
||||
showResetPassDialog.value = false
|
||||
}).catch((e) => {
|
||||
ElMessage.error('操作失败,' + e.message)
|
||||
httpPost("/api/admin/user/resetPass", pass.value)
|
||||
.then(() => {
|
||||
ElMessage.success("操作成功!");
|
||||
showResetPassDialog.value = false;
|
||||
})
|
||||
}
|
||||
|
||||
.catch((e) => {
|
||||
ElMessage.error("操作失败," + e.message);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
|