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;
|
color: #999;
|
||||||
}
|
}
|
||||||
.page-apps .inner .list-box .app-item:hover {
|
.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像素 */
|
transform: translateY(-10px); /* 向上移动10像素 */
|
||||||
}
|
}
|
||||||
|
@ -1,30 +1,38 @@
|
|||||||
.page-apps {
|
.page-apps {
|
||||||
background-color: #282c34;
|
// background-color: #282c34;
|
||||||
height 100%
|
height 100%
|
||||||
|
|
||||||
.apps-type-nav{
|
.apps-type-nav{
|
||||||
height 43px
|
height 50px
|
||||||
padding 8px 0;
|
padding 8px 0;
|
||||||
margin-bottom 3px
|
margin 10px auto
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbar-type-nav{
|
.scrollbar-type-nav{
|
||||||
display flex
|
display flex
|
||||||
align-items center
|
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{
|
li{
|
||||||
flex-shrink 0
|
flex-shrink 0
|
||||||
display flex
|
display flex
|
||||||
align-items center
|
align-items center
|
||||||
justify-content center
|
justify-content center
|
||||||
margin 0 10px
|
margin 5px 8px
|
||||||
height 26px
|
height 26px
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
border 1px solid rgb(80,80,80)
|
// border 1px solid rgb(80,80,80)
|
||||||
padding 2px 12px
|
padding 2px 12px
|
||||||
background rgba(60,60,60 0.9)
|
// background rgba(60,60,60 0.9)
|
||||||
color #fff
|
color var(--theme-text-tertiary)
|
||||||
|
font-weight: bold
|
||||||
font-size 14px
|
font-size 14px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
@ -34,9 +42,12 @@
|
|||||||
overflow hidden
|
overflow hidden
|
||||||
margin-right 5px
|
margin-right 5px
|
||||||
border-radius 50%
|
border-radius 50%
|
||||||
|
|
||||||
}
|
}
|
||||||
&.active{
|
&.active{
|
||||||
background #21aa93;
|
background #fff;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
border-radius 20px
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,16 +64,24 @@
|
|||||||
.item {
|
.item {
|
||||||
display flex
|
display flex
|
||||||
flex-flow row
|
flex-flow row
|
||||||
border 1px solid rgb(80,80,80)
|
// border 1px solid rgb(80,80,80)
|
||||||
padding 10px
|
padding 10px
|
||||||
background rgba(60,60,60 0.5)
|
background: var(--chat-bg);
|
||||||
|
border-radius 8px
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
width 80px
|
width 80px
|
||||||
height 80px
|
height 80px
|
||||||
min-width 80px
|
min-width 80px
|
||||||
border-radius 5px
|
border-radius 50%
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
object-fit: contain
|
||||||
|
display: flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
flex-shrink 0
|
||||||
|
border: 2px solid #f5f7fd
|
||||||
|
background: #fff
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
@ -75,7 +94,7 @@
|
|||||||
text-align left
|
text-align left
|
||||||
|
|
||||||
.info-title {
|
.info-title {
|
||||||
color var(--el-text-color)
|
color: var(--text-theme-color)
|
||||||
font-size 1.25rem
|
font-size 1.25rem
|
||||||
line-height 1.75rem
|
line-height 1.75rem
|
||||||
letter-spacing: .025em;
|
letter-spacing: .025em;
|
||||||
@ -96,7 +115,8 @@
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
height 34px
|
height 34px
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
color #999999
|
color var(--el-text-color-primary)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
$sideBgColor = #252526;
|
|
||||||
$borderColor = #4676d0;
|
|
||||||
#app {
|
#app {
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -33,7 +32,7 @@ $borderColor = #4676d0;
|
|||||||
// .search-input {
|
// .search-input {
|
||||||
// --el-input-bg-color: #363535
|
// --el-input-bg-color: #363535
|
||||||
// --el-input-border-color: #464545
|
// --el-input-border-color: #464545
|
||||||
// --el-input-focus-border-color: #47fff1
|
// --el-input-focus-border-color:#b0a0f8
|
||||||
// --el-input-hover-border-color: #2DA39A
|
// --el-input-hover-border-color: #2DA39A
|
||||||
// box-shadow: none
|
// box-shadow: none
|
||||||
// }
|
// }
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
--sm-txt:rgba(163, 174, 208, 1);
|
--sm-txt:rgba(163, 174, 208, 1);
|
||||||
--text-secondary: #8a939d;
|
--text-secondary: #8a939d;
|
||||||
--el-color-primary: rgb(107, 80, 225);
|
--el-color-primary: rgb(107, 80, 225);
|
||||||
|
--theme-textcolor-normal:#b0a0f8;
|
||||||
--el-border-radius-base: 8px;
|
--el-border-radius-base: 8px;
|
||||||
--el-color-primary-light-5:rgb(107, 85, 255);
|
--el-color-primary-light-5:rgb(107, 85, 255);
|
||||||
--el-color-primary-light-3:rgb(78, 51, 254);
|
--el-color-primary-light-3:rgb(78, 51, 254);
|
||||||
@ -19,9 +20,18 @@
|
|||||||
--theme-border-primary: rgba(86, 86, 95, .322); //主题边框颜色
|
--theme-border-primary: rgba(86, 86, 95, .322); //主题边框颜色
|
||||||
--theme-border-hover: rgb(107, 85, 255);//主题边框hover颜色
|
--theme-border-hover: rgb(107, 85, 255);//主题边框hover颜色
|
||||||
--text--hover:rgba(215, 211, 240, 0.581) //主题色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-dialog{
|
||||||
--el-border-radius-base: 20px;
|
--el-border-radius-base: 20px;
|
||||||
@ -90,13 +100,13 @@
|
|||||||
/* 滚动条的轨道背景颜色 */
|
/* 滚动条的轨道背景颜色 */
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background: #f1f1f1; /* 滚动条轨道颜色 */
|
background: #f1f1f1 !important; /* 滚动条轨道颜色 */
|
||||||
border-radius: 6px; /* 可选:轨道圆角 */
|
border-radius: 6px; /* 可选:轨道圆角 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 滚动条滑块的颜色 */
|
/* 滚动条滑块的颜色 */
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #888; /* 滑块颜色 */
|
background: #888 !important; /* 滑块颜色 */
|
||||||
border-radius: 6px; /* 可选:滑块圆角 */
|
border-radius: 6px; /* 可选:滑块圆角 */
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,4 +125,13 @@
|
|||||||
--text-color:var(--theme-text-color-primary)
|
--text-color:var(--theme-text-color-primary)
|
||||||
font-size: 16px
|
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 {
|
.custom-scroll ::-webkit-scrollbar {
|
||||||
width: 8px; /* 滚动条宽度 */
|
width: 8px; /* 滚动条宽度 */
|
||||||
|
display:none;
|
||||||
}
|
}
|
||||||
.custom-scroll ::-webkit-scrollbar-track {
|
.custom-scroll ::-webkit-scrollbar-track {
|
||||||
background-color: #282c34;
|
background-color: #282c34;
|
||||||
|
display:none;
|
||||||
}
|
}
|
||||||
.custom-scroll ::-webkit-scrollbar-thumb {
|
.custom-scroll ::-webkit-scrollbar-thumb {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
display:none;
|
||||||
}
|
}
|
||||||
.custom-scroll ::-webkit-scrollbar-thumb:hover {
|
.custom-scroll ::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: #666;
|
background-color: #666;
|
||||||
|
display:none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
.tab-box{
|
.tab-box{
|
||||||
align-items: center
|
align-items: center
|
||||||
background-color: var(--card-bg)
|
background-color: var(--card-bg)
|
||||||
height: 100%
|
// height: 100%
|
||||||
|
// position: fixed;
|
||||||
|
height: 100vh;
|
||||||
.title{
|
.title{
|
||||||
font-size: 28px
|
font-size: 28px
|
||||||
font-weight: 700
|
font-weight: 700
|
||||||
@ -173,6 +175,13 @@
|
|||||||
background: var(--theme-bg-all);
|
background: var(--theme-bg-all);
|
||||||
// background-image: linear-gradient(180deg, rgba(247, 232, 255, .54), rgba(191, 223, 255, .35));
|
// background-image: linear-gradient(180deg, rgba(247, 232, 255, .54), rgba(191, 223, 255, .35));
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
.loginMask{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
.topheader{
|
.topheader{
|
||||||
display: flex;
|
display: flex;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
.page-dall {
|
.page-dall {
|
||||||
background-color: #282c34;
|
// background-color: #282c34;
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.sd-box {
|
.sd-box {
|
||||||
margin 10px
|
margin 10px
|
||||||
background-color #262626
|
// background-color #262626
|
||||||
border 1px solid #454545
|
// border 1px solid #454545
|
||||||
min-width 300px
|
min-width 300px
|
||||||
max-width 300px
|
max-width 300px
|
||||||
padding 10px
|
padding 10px
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
color #ffffff;
|
color var(--text-theme-color);
|
||||||
font-size 14px
|
font-size 14px
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size 20px
|
font-size 20px
|
||||||
text-align center
|
text-align center
|
||||||
color #47fff1
|
color#b0a0f8
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
@ -66,25 +66,24 @@
|
|||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
width 100%
|
width 200px
|
||||||
|
|
||||||
span {
|
|
||||||
color #2D3A4B
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-form {
|
.el-form {
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box {
|
.task-list-box {
|
||||||
|
background: var(--chat-bg);
|
||||||
width 100%
|
width 100%
|
||||||
padding 10px
|
padding 10px
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
|
|
||||||
.task-list-inner {
|
.task-list-inner {
|
||||||
@ -93,26 +92,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__item {
|
.el-tabs__item {
|
||||||
color: #fff;
|
color: var(--text-theme-color);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-tabs .el-tabs__item.is-active {
|
.title-tabs .el-tabs__item.is-active {
|
||||||
color: #47FFF1;
|
color:#b0a0f8;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-tabs .el-tabs__active-bar {
|
.title-tabs .el-tabs__active-bar {
|
||||||
background-color: #47FFF1;
|
background-color:#b0a0f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-textarea {
|
.el-textarea {
|
||||||
--el-input-focus-border-color: #47FFF1;
|
// --el-input-focus-border-color:#b0a0f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-textarea__inner {
|
.el-textarea__inner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #fff;
|
color: var(--text-theme-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input__wrapper {
|
.el-input__wrapper {
|
||||||
@ -141,7 +140,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片上传样式
|
// 图片上传样式
|
||||||
|
@ -365,7 +365,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover {
|
.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像素 */
|
transform: translateY(-10px); /* 向上移动10像素 */
|
||||||
}
|
}
|
||||||
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image {
|
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image {
|
||||||
|
@ -1,26 +1,30 @@
|
|||||||
.page-mj {
|
.page-mj {
|
||||||
background-color: #282c34;
|
// background-color: #282c34;
|
||||||
height 100%
|
height 100%
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
// height: 100%
|
||||||
|
|
||||||
.mj-box {
|
.mj-box {
|
||||||
margin 10px
|
margin 10px
|
||||||
background-color #262626
|
// background-color #262626
|
||||||
border 1px solid #454545
|
// border 1px solid #454545
|
||||||
|
// height: calc(100vh - 50px)
|
||||||
|
// overflow: scroll
|
||||||
min-width 300px
|
min-width 300px
|
||||||
max-width 300px
|
max-width 300px
|
||||||
padding 10px
|
padding 10px
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
color #ffffff;
|
color var(--text-theme-color);
|
||||||
font-size 14px
|
font-size 14px
|
||||||
|
overflow auto
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size 20px
|
font-size 20px
|
||||||
text-align center
|
text-align center
|
||||||
color #47fff1
|
color var( --theme-textcolor-normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
@ -44,16 +48,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.grid-content {
|
.grid-content {
|
||||||
background-color #383838
|
// background-color #383838
|
||||||
border-radius 5px
|
background: var(--card-bg);
|
||||||
|
border-radius: 8px;
|
||||||
padding 8px 14px
|
padding 8px 14px
|
||||||
display flex
|
display flex
|
||||||
cursor pointer
|
cursor pointer
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
border 1px solid #383838
|
// border 1px solid #383838
|
||||||
|
border 1px solid var(--chat-bg)
|
||||||
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color #585858
|
border 1px solid var(--theme-border-hover)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
@ -70,28 +78,30 @@
|
|||||||
|
|
||||||
|
|
||||||
.grid-content.active {
|
.grid-content.active {
|
||||||
color #47fff1
|
// color #47fff1
|
||||||
background-color #585858
|
// background-color #585858
|
||||||
border 1px solid #47fff1
|
border 1px solid var(--theme-border-hover)
|
||||||
}
|
}
|
||||||
|
|
||||||
.model {
|
.model {
|
||||||
background-color #383838
|
background: var(--card-bg);
|
||||||
border 1px solid #454545
|
// border 1px solid #454545
|
||||||
border-radius 5px
|
border-radius 8px
|
||||||
padding 5px
|
padding 5px
|
||||||
margin-bottom 10px
|
margin-bottom 10px
|
||||||
display flex
|
display flex
|
||||||
flex-flow column
|
flex-flow column
|
||||||
align-items center
|
align-items center
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
border 1px solid var(--chat-bg)
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color #585858
|
border 1px solid var(--theme-border-hover)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-image {
|
.el-image {
|
||||||
height 30px
|
height 40px
|
||||||
width 100%
|
width 100%
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,9 +113,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.model.active {
|
.model.active {
|
||||||
color #47fff1
|
// color #47fff1
|
||||||
background-color #585858
|
// background-color #585858
|
||||||
border 1px solid #47fff1
|
border 1px solid var(--theme-border-hover)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-item-inner {
|
.form-item-inner {
|
||||||
@ -113,16 +124,16 @@
|
|||||||
align-items: center
|
align-items: center
|
||||||
|
|
||||||
.el-select {
|
.el-select {
|
||||||
--el-select-input-focus-border-color: #47FFF1;
|
--el-select-input-focus-border-color: var(--el-color-primary)
|
||||||
--el-input-focus-border-color: #47FFF1;
|
--el-input-focus-border-color: var(--el-color-primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input__wrapper {
|
.el-input__wrapper {
|
||||||
background: #383838;
|
background: var(--chat-bg)
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
color: #fff
|
color: var(--text-theme-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
@ -167,7 +178,7 @@
|
|||||||
|
|
||||||
.el-form {
|
.el-form {
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input, .el-slider {
|
.el-input, .el-slider {
|
||||||
@ -182,9 +193,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box {
|
.task-list-box {
|
||||||
|
background: var(--chat-bg);
|
||||||
width 100%
|
width 100%
|
||||||
padding 0 10px 10px 10px
|
padding 0 10px 10px 10px
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
|
|
||||||
.task-list-inner {
|
.task-list-inner {
|
||||||
@ -193,26 +205,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-tabs__item {
|
.el-tabs__item {
|
||||||
color: #fff;
|
color: var(--text-theme-color);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-tabs .el-tabs__item.is-active {
|
.title-tabs .el-tabs__item.is-active {
|
||||||
color: #47FFF1;
|
color: var( --theme-textcolor-normal);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-tabs .el-tabs__active-bar {
|
.title-tabs .el-tabs__active-bar {
|
||||||
background-color: #47FFF1;
|
background-color: var( --theme-textcolor-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-textarea {
|
.el-textarea {
|
||||||
--el-input-focus-border-color: #47FFF1;
|
--el-input-focus-border-color: var(--el-color-primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-textarea__inner {
|
.el-textarea__inner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #fff;
|
color: var(--text-theme-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-input__wrapper {
|
.el-input__wrapper {
|
||||||
@ -241,7 +253,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片上传样式
|
// 图片上传样式
|
||||||
@ -367,7 +379,7 @@
|
|||||||
display block
|
display block
|
||||||
cursor pointer
|
cursor pointer
|
||||||
background-color #4E5058
|
background-color #4E5058
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color #6D6F78
|
background-color #6D6F78
|
||||||
@ -422,7 +434,7 @@
|
|||||||
justify-content center
|
justify-content center
|
||||||
align-items center
|
align-items center
|
||||||
min-height 220px
|
min-height 220px
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
.err-msg-container {
|
.err-msg-container {
|
||||||
|
@ -250,7 +250,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover {
|
.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像素 */
|
transform: translateY(-10px); /* 向上移动10像素 */
|
||||||
}
|
}
|
||||||
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image {
|
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image {
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
.page-sd {
|
.page-sd {
|
||||||
background-color: #282c34;
|
// background-color: #282c34;
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.sd-box {
|
.sd-box {
|
||||||
margin 10px
|
margin 10px
|
||||||
background-color #262626
|
// background-color #262626
|
||||||
border 1px solid #454545
|
// border 1px solid #454545
|
||||||
min-width 300px
|
min-width 300px
|
||||||
max-width 300px
|
max-width 300px
|
||||||
padding 10px 10px 20px 10px
|
padding 10px 10px 20px 10px
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
color #ffffff;
|
color var(--text-theme-color);
|
||||||
font-size 14px
|
font-size 14px
|
||||||
overflow auto
|
overflow auto
|
||||||
|
|
||||||
@ -20,7 +20,8 @@
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size 20px
|
font-size 20px
|
||||||
text-align center
|
text-align center
|
||||||
color #47fff1
|
color var( --theme-textcolor-normal)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
@ -67,25 +68,24 @@
|
|||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
.el-button {
|
.el-button {
|
||||||
width 100%
|
width 200px
|
||||||
|
|
||||||
span {
|
|
||||||
color #2D3A4B
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-form {
|
.el-form {
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color #ffffff
|
color: var(--text-theme-color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-list-box {
|
.task-list-box {
|
||||||
|
background: var(--chat-bg);
|
||||||
width 100%
|
width 100%
|
||||||
padding 0 10px 10px 10px
|
padding 0 10px 10px 10px
|
||||||
color #ffffff
|
color: var(--text-theme-color)
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
|
|
||||||
.task-list-inner {
|
.task-list-inner {
|
||||||
@ -108,7 +108,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-textarea {
|
.el-textarea {
|
||||||
--el-input-focus-border-color: #47FFF1;
|
// --el-input-focus-border-color: #47FFF1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-textarea__inner {
|
.el-textarea__inner {
|
||||||
@ -142,7 +142,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color #ffffff
|
color: var(--text-theme-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片上传样式
|
// 图片上传样式
|
||||||
|
@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
.page-images-wall {
|
.page-images-wall {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #282c34;
|
// background-color: #282c34;
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
width 100%
|
width 100%
|
||||||
color #ffffff
|
color var(--text-theme-color);
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@ -33,7 +33,7 @@
|
|||||||
font-size 16px
|
font-size 16px
|
||||||
|
|
||||||
.el-radio {
|
.el-radio {
|
||||||
color #ffffff
|
color var(--text-theme-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@
|
|||||||
width 100%
|
width 100%
|
||||||
bottom 0
|
bottom 0
|
||||||
left 0
|
left 0
|
||||||
color #ffffff
|
color var(--text-theme-color);
|
||||||
padding 8px 10px
|
padding 8px 10px
|
||||||
line-height 1.2
|
line-height 1.2
|
||||||
border-top-right-radius 10px
|
border-top-right-radius 10px
|
||||||
@ -77,11 +77,15 @@
|
|||||||
span {
|
span {
|
||||||
word-break break-all
|
word-break break-all
|
||||||
}
|
}
|
||||||
|
.iconfont{
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
.el-icon, .iconfont {
|
.el-icon, .iconfont {
|
||||||
top 2px
|
top 2px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
border 1px solid #ffffff
|
color: #fff !important;
|
||||||
|
border 1px solid #fff !important;
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
padding 2px
|
padding 2px
|
||||||
font-size 16px;
|
font-size 16px;
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
.page-luma {
|
.page-luma {
|
||||||
display flex
|
display flex
|
||||||
height 100%
|
height 100%
|
||||||
background-color #0E0808
|
// background-color #0E0808
|
||||||
|
// background: var(--chat-bg);
|
||||||
|
|
||||||
overflow auto
|
overflow auto
|
||||||
//justify-content center
|
//justify-content center
|
||||||
flex-flow column
|
flex-flow column
|
||||||
align-items center
|
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 {
|
.prompt-box {
|
||||||
@ -50,7 +52,7 @@
|
|||||||
.btn-swap {
|
.btn-swap {
|
||||||
margin-right 10px
|
margin-right 10px
|
||||||
.icon-exchange{
|
.icon-exchange{
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
cursor pointer
|
cursor pointer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,18 +62,20 @@
|
|||||||
.prompt-container {
|
.prompt-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
.input-container {
|
.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;
|
border-radius: 28px;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
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 {
|
.prompt-input {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
color: white;
|
color var(--text-theme-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -82,16 +86,14 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
|
|
||||||
scrollbar-width: none; /* 隐藏滚动条 */
|
scrollbar-width: none; /* 隐藏滚动条 */
|
||||||
&::placeholder {
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
}
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload-icon, .send-icon {
|
.upload-icon, .send-icon {
|
||||||
color #e1e1e1
|
color var( --el-color-primary)
|
||||||
.iconfont {
|
.iconfont {
|
||||||
font-size 20px
|
font-size 20px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
@ -104,7 +106,7 @@
|
|||||||
.params {
|
.params {
|
||||||
display flex
|
display flex
|
||||||
justify-content right
|
justify-content right
|
||||||
color #e1e1e1
|
color var(--text-theme-color);
|
||||||
font-size 14px
|
font-size 14px
|
||||||
padding 10px 30px
|
padding 10px 30px
|
||||||
|
|
||||||
@ -129,9 +131,9 @@
|
|||||||
padding 0 40px
|
padding 0 40px
|
||||||
|
|
||||||
.h-title {
|
.h-title {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
width 100%
|
width 100%
|
||||||
font-size 36px
|
// font-size 36px
|
||||||
text-align left
|
text-align left
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +149,7 @@
|
|||||||
cursor pointer
|
cursor pointer
|
||||||
margin-bottom 10px
|
margin-bottom 10px
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color #2A2525
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
.container {
|
.container {
|
||||||
@ -189,7 +189,8 @@
|
|||||||
border-radius 5px
|
border-radius 5px
|
||||||
background rgba(100, 100, 100, 0.3)
|
background rgba(100, 100, 100, 0.3)
|
||||||
cursor pointer
|
cursor pointer
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
|
|
||||||
opacity 0
|
opacity 0
|
||||||
transform: translate(-50%, 0px);
|
transform: translate(-50%, 0px);
|
||||||
transition opacity 0.3s ease 0s
|
transition opacity 0.3s ease 0s
|
||||||
@ -222,7 +223,8 @@
|
|||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
}
|
}
|
||||||
.prompt {
|
.prompt {
|
||||||
color rgb(250 247 245)
|
color var( --el-text-color-primary)
|
||||||
|
cursor: text
|
||||||
}
|
}
|
||||||
.failed {
|
.failed {
|
||||||
color #E4696B
|
color #E4696B
|
||||||
@ -248,7 +250,7 @@
|
|||||||
|
|
||||||
.text {
|
.text {
|
||||||
margin-right 10px
|
margin-right 10px
|
||||||
color #e1e1e1
|
color #000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,8 +261,9 @@
|
|||||||
color #726E6C
|
color #726E6C
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background #5f5958
|
// background #5f5958
|
||||||
color #e1e1e1
|
// color #e1e1e1
|
||||||
|
color:var(--el-color-primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloading {
|
.downloading {
|
||||||
@ -317,7 +320,7 @@
|
|||||||
// border-radius 20px
|
// border-radius 20px
|
||||||
// padding 3px 15px
|
// padding 3px 15px
|
||||||
// cursor pointer
|
// cursor pointer
|
||||||
// color #ffffff
|
// color var(--text-theme-color)
|
||||||
// font-size 14px
|
// font-size 14px
|
||||||
//
|
//
|
||||||
// .iconfont {
|
// .iconfont {
|
||||||
@ -344,14 +347,14 @@
|
|||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
margin-right 10px
|
margin-right 10px
|
||||||
background-color #363030
|
|
||||||
border none
|
border none
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
padding 5px 10px
|
padding 5px 10px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
color #000
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color #5F5958
|
opacity: 0.7
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.page-mark-map {
|
.page-mark-map {
|
||||||
background-color: #282c34;
|
// background-color: #282c34;
|
||||||
height 100%
|
height 100%
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
@ -7,20 +7,20 @@
|
|||||||
|
|
||||||
.mark-map-box {
|
.mark-map-box {
|
||||||
margin 10px
|
margin 10px
|
||||||
background-color #262626
|
// background-color #262626
|
||||||
border 1px solid #454545
|
// border 1px solid #454545
|
||||||
min-width 300px
|
min-width 300px
|
||||||
max-width 300px
|
max-width 300px
|
||||||
padding 10px
|
padding 10px
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
color #ffffff;
|
color var(--text-theme-color);
|
||||||
font-size 14px
|
font-size 14px
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size 20px
|
font-size 20px
|
||||||
text-align center
|
text-align center
|
||||||
color #47fff1
|
color var( --theme-textcolor-normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 隐藏滚动条
|
// 隐藏滚动条
|
||||||
@ -43,7 +43,7 @@
|
|||||||
width 100%
|
width 100%
|
||||||
|
|
||||||
span {
|
span {
|
||||||
color #2D3A4B
|
color #fff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,13 +61,15 @@
|
|||||||
|
|
||||||
.el-form {
|
.el-form {
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.chat-box {
|
.chat-box {
|
||||||
width 100%
|
width 100%
|
||||||
|
background: var(--chat-bg);
|
||||||
|
|
||||||
|
|
||||||
.top-bar {
|
.top-bar {
|
||||||
display flex
|
display flex
|
||||||
@ -77,13 +79,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown {
|
.markdown {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
display flex
|
display flex
|
||||||
justify-content center
|
justify-content center
|
||||||
align-items center
|
align-items center
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
color: #47fff1;
|
color: var( --theme-textcolor-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@ -116,7 +118,7 @@
|
|||||||
|
|
||||||
.markmap {
|
.markmap {
|
||||||
width 100%
|
width 100%
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
font-size 12px
|
font-size 12px
|
||||||
|
|
||||||
.markmap-foreign {
|
.markmap-foreign {
|
||||||
@ -139,7 +141,7 @@
|
|||||||
|
|
||||||
.mm-toolbar-item {
|
.mm-toolbar-item {
|
||||||
cursor pointer
|
cursor pointer
|
||||||
color var(--el-color-white)
|
color var( --el-text-color-primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,11 @@
|
|||||||
list-style: normal;
|
list-style: normal;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #42b983;
|
|
||||||
font-weight: 600;
|
color :var(--a-link-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
|
@ -141,7 +141,7 @@
|
|||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
.member .inner .product-box .list-box .product-item:hover {
|
.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像素 */
|
transform: translateY(-10px); /* 向上移动10像素 */
|
||||||
}
|
}
|
||||||
.member .inner .product-box .headline {
|
.member .inner .product-box .headline {
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
.member {
|
.member {
|
||||||
background-color: #282c34;
|
// background-color: #282c34;
|
||||||
height 100%
|
height 100%
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
text-align center
|
text-align center
|
||||||
background-color #25272d
|
background-color #25272d
|
||||||
font-size 24px
|
font-size 24px
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
padding 10px
|
padding 10px
|
||||||
border-bottom 1px solid #3c3c3c
|
border-bottom 1px solid #3c3c3c
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
padding 15px 0 15px 15px;
|
padding 15px 0 15px 15px;
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
overflow-y visible
|
overflow-y visible
|
||||||
@ -22,13 +22,13 @@
|
|||||||
.user-profile {
|
.user-profile {
|
||||||
padding 10px 20px 20px 20px
|
padding 10px 20px 20px 20px
|
||||||
width 300px
|
width 300px
|
||||||
background-color #393F4A
|
background-color var(--chat-bg)
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
//height 100vh
|
//height 100vh
|
||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
justify-content start
|
justify-content start
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,8 @@
|
|||||||
|
|
||||||
.list-box {
|
.list-box {
|
||||||
.product-item {
|
.product-item {
|
||||||
border 1px solid #666666
|
// border 1px solid #666666
|
||||||
|
background-color var(--chat-bg)
|
||||||
border-radius 6px
|
border-radius 6px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
cursor pointer
|
cursor pointer
|
||||||
@ -87,7 +88,7 @@
|
|||||||
text-align center
|
text-align center
|
||||||
font-size 16px
|
font-size 16px
|
||||||
font-weight bold
|
font-weight bold
|
||||||
color #47fff1
|
color var( --el-color-primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,14 +139,14 @@
|
|||||||
padding 0
|
padding 0
|
||||||
|
|
||||||
.icon-alipay,.icon-wechat-pay {
|
.icon-alipay,.icon-wechat-pay {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
}
|
}
|
||||||
.icon-qq {
|
.icon-qq {
|
||||||
color #15A6E8
|
color #15A6E8
|
||||||
font-size 24px
|
font-size 24px
|
||||||
}
|
}
|
||||||
.icon-jd-pay {
|
.icon-jd-pay {
|
||||||
color #ffffff
|
color var(--text-theme-color)
|
||||||
font-size 24px
|
font-size 24px
|
||||||
}
|
}
|
||||||
.icon-douyin {
|
.icon-douyin {
|
||||||
@ -161,7 +162,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&: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像素 */
|
transform: translateY(-10px); /* 向上移动10像素 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
height 200px
|
height 200px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
padding 2px
|
padding 2px
|
||||||
background-color #555555
|
background-color var( --gray-btn-bg)
|
||||||
|
|
||||||
.job-item-inner {
|
.job-item-inner {
|
||||||
position relative
|
position relative
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.page-suno {
|
.page-suno {
|
||||||
display flex
|
display flex
|
||||||
height 100%
|
height 100%
|
||||||
background-color #0E0808
|
// background-color #0E0808
|
||||||
overflow auto
|
overflow auto
|
||||||
|
|
||||||
.left-bar {
|
.left-bar {
|
||||||
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
.params {
|
.params {
|
||||||
padding 20px 0
|
padding 20px 0
|
||||||
color rgb(250 247 245)
|
color: var(--text-theme-color);
|
||||||
position relative
|
position relative
|
||||||
|
|
||||||
.pure-music {
|
.pure-music {
|
||||||
@ -77,6 +77,7 @@
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.song {
|
.song {
|
||||||
display flex
|
display flex
|
||||||
@ -137,6 +138,8 @@
|
|||||||
bottom 10px
|
bottom 10px
|
||||||
font-size 12px
|
font-size 12px
|
||||||
padding 2px 5px
|
padding 2px 5px
|
||||||
|
background-color var(--sm-btn-bg)
|
||||||
|
color: #fff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,12 +157,16 @@
|
|||||||
.tag {
|
.tag {
|
||||||
margin-right 10px
|
margin-right 10px
|
||||||
word-break keep-all
|
word-break keep-all
|
||||||
background-color #312C2C
|
background: var(--card-bg);
|
||||||
color #e1e1e1
|
color:var(--theme-text-color-primary);
|
||||||
border-radius 5px
|
opacity 0.7
|
||||||
|
border-radius 8px
|
||||||
padding 3px 6px
|
padding 3px 6px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
font-size 13px
|
font-size 13px
|
||||||
|
&:hover{
|
||||||
|
color:var( --el-color-primary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,6 +176,8 @@
|
|||||||
width 100%
|
width 100%
|
||||||
color rgb(250 247 245)
|
color rgb(250 247 245)
|
||||||
overflow auto
|
overflow auto
|
||||||
|
background: var(--chat-bg)
|
||||||
|
|
||||||
|
|
||||||
.list-box {
|
.list-box {
|
||||||
padding 0 0 0 20px
|
padding 0 0 0 20px
|
||||||
@ -180,7 +189,7 @@
|
|||||||
margin-bottom 10px
|
margin-bottom 10px
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color #2A2525
|
background: rgba(188,149,236,0.08)
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
@ -247,18 +256,18 @@
|
|||||||
font-weight 700
|
font-weight 700
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color rgb(250 247 245)
|
color vae( --a-link-color)
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration underline
|
text-decoration underline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.model {
|
.model {
|
||||||
color #E2E8F0
|
color #8f8f8f
|
||||||
background-color #1C1616
|
// background-color #1C1616
|
||||||
border 1px solid #8f8f8f
|
// border 1px solid #8f8f8f
|
||||||
font-weight normal
|
font-weight normal
|
||||||
font-size 14px
|
font-size 12px
|
||||||
padding 1px 3px
|
padding 1px 3px
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
margin-left 10px
|
margin-left 10px
|
||||||
@ -271,7 +280,7 @@
|
|||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
font-size 14px
|
font-size 14px
|
||||||
color #d1d1d1
|
color var(--el-text-color-primary)
|
||||||
padding 3px 0
|
padding 3px 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,8 +312,9 @@
|
|||||||
color #726E6C
|
color #726E6C
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background #5f5958
|
// background #5f5958
|
||||||
color #e1e1e1
|
// color #e1e1e1
|
||||||
|
color:var(--el-color-primary)
|
||||||
}
|
}
|
||||||
|
|
||||||
.downloading {
|
.downloading {
|
||||||
@ -372,14 +382,25 @@
|
|||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
margin-right 10px
|
margin-right 10px
|
||||||
background-color #363030
|
color: #000
|
||||||
border none
|
border none
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
padding 5px 10px
|
padding 5px 10px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
|
|
||||||
&:hover {
|
&: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);
|
--btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
|
||||||
--border-active:rgba(255, 255, 255, 0.1);
|
--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:rgb(13, 20, 53);
|
||||||
--theme-bg-all:rgb(13, 20, 53);
|
--theme-bg-all:rgb(13, 20, 53);
|
||||||
--sign-bg: rgba(27, 37, 75, 1);
|
--sign-bg: rgba(27, 37, 75, 1);
|
||||||
@ -28,7 +30,7 @@
|
|||||||
--text-color-primary: #d1c7ff;
|
--text-color-primary: #d1c7ff;
|
||||||
--el-text-color-regular: rgba(163, 174, 208, 1)
|
--el-text-color-regular: rgba(163, 174, 208, 1)
|
||||||
--el-border-color:rgb(79, 80, 85)
|
--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-bg-color-overlay: rgba(17, 28, 68, 1);
|
||||||
--el-border-color-light: rgba(255, 255, 255, 0.2);
|
--el-border-color-light: rgba(255, 255, 255, 0.2);
|
||||||
--line-box:rgba(255, 255, 255, 0.1);
|
--line-box:rgba(255, 255, 255, 0.1);
|
||||||
@ -39,6 +41,7 @@
|
|||||||
--el-color-primary-light-9:rgba(86, 86, 95, .2);
|
--el-color-primary-light-9:rgba(86, 86, 95, .2);
|
||||||
--chat-wel-bg:#2d2f388a;
|
--chat-wel-bg:#2d2f388a;
|
||||||
--theme-text-color-secondary: #a3aed0;
|
--theme-text-color-secondary: #a3aed0;
|
||||||
|
// --el-pagination-button-bg-color: rgba(86,86,95,0.2);
|
||||||
|
|
||||||
//layout
|
//layout
|
||||||
.more-menus li.moreTitle,
|
.more-menus li.moreTitle,
|
||||||
@ -52,9 +55,14 @@
|
|||||||
.more-menus span.title{
|
.more-menus span.title{
|
||||||
color:#000;
|
color:#000;
|
||||||
}
|
}
|
||||||
|
.menu-list .menu-list-item.active .el-icon{
|
||||||
|
background: #0080006e;
|
||||||
|
}
|
||||||
|
|
||||||
--theme-text-color-primary: #fff;
|
--theme-text-color-primary: #fff;
|
||||||
--theme-text-primary: #f3f3f3;
|
--theme-text-primary: #f3f3f3;
|
||||||
--chat-content-bg:rgba(86, 86, 95, .2);
|
--chat-content-bg:rgba(86, 86, 95, .2);
|
||||||
--chat-content-bg-list: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-list-bg: #0302020a;
|
||||||
--chat-content-bg-list:#fff;
|
--chat-content-bg-list:#fff;
|
||||||
--chat-wel-bg:rgba(247, 247, 248, 1);
|
--chat-wel-bg:rgba(247, 247, 248, 1);
|
||||||
|
--el-pagination-button-bg-color: rgba(86,86,95,0.2);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
.animate {
|
.animate {
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
|
box-shadow: 0 0 10px var(--shadow-color); /* 添加阴影效果 */
|
||||||
transform: translateY(-10px); /* 向上移动10像素 */
|
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 {
|
code {
|
||||||
color:var(--theme-text-color-primary);
|
color:var(--theme-text-color-primary);
|
||||||
background-color #e7e7e8
|
background-color var(--el-color-primary-light-3)
|
||||||
padding 0 3px;
|
padding 0 3px;
|
||||||
border-radius 5px;
|
border-radius 5px;
|
||||||
}
|
}
|
||||||
@ -348,7 +348,7 @@ const reGenerate = (prompt) => {
|
|||||||
padding 10px 10px 10px 0;
|
padding 10px 10px 10px 0;
|
||||||
|
|
||||||
.bar-item {
|
.bar-item {
|
||||||
background-color #e7e7e8;
|
background-color var( --little-btn-bg);
|
||||||
color #888
|
color #888
|
||||||
padding 3px 5px;
|
padding 3px 5px;
|
||||||
margin-right 10px;
|
margin-right 10px;
|
||||||
@ -433,7 +433,7 @@ const reGenerate = (prompt) => {
|
|||||||
|
|
||||||
code {
|
code {
|
||||||
color:var(--theme-text-color-primary);
|
color:var(--theme-text-color-primary);
|
||||||
background-color #e7e7e8
|
background-color var( --little-btn-bg)
|
||||||
padding 0 3px;
|
padding 0 3px;
|
||||||
border-radius 5px;
|
border-radius 5px;
|
||||||
}
|
}
|
||||||
@ -539,7 +539,7 @@ const reGenerate = (prompt) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bar-item.bg {
|
.bar-item.bg {
|
||||||
background-color #e7e7e8
|
background-color var( --gray-btn-bg)
|
||||||
cursor pointer
|
cursor pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,76 +1,90 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="invite-list" v-loading="loading">
|
<div class="invite-list" v-loading="loading">
|
||||||
<el-row v-if="items.length > 0">
|
<el-row v-if="items.length > 0">
|
||||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
<el-table
|
||||||
style="--el-table-border-color:#373C47;
|
:data="items"
|
||||||
--el-table-tr-bg-color:#2D323B;
|
:row-key="(row) => row.id"
|
||||||
--el-table-row-hover-bg-color:#373C47;
|
table-layout="auto"
|
||||||
--el-table-header-bg-color:#474E5C;
|
border
|
||||||
--el-table-text-color:#d1d1d1">
|
style="
|
||||||
<el-table-column prop="username" label="用户"/>
|
--el-table-border-color: #373c47;
|
||||||
<el-table-column prop="invite_code" label="邀请码"/>
|
--el-table-tr-bg-color: #2d323b;
|
||||||
<el-table-column prop="remark" label="邀请奖励"/>
|
--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="注册时间">
|
<el-table-column label="注册时间">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-empty :image-size="100" v-else/>
|
<el-empty :image-size="100" :image="nodata" description="暂无数据" v-else />
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<el-pagination v-if="total > 0" background
|
<el-pagination
|
||||||
layout="total,prev, pager, next"
|
v-if="total > 0"
|
||||||
:hide-on-single-page="true"
|
background
|
||||||
v-model:current-page="page"
|
layout="total,prev, pager, next"
|
||||||
v-model:page-size="pageSize"
|
:hide-on-single-page="true"
|
||||||
@current-change="fetchData()"
|
v-model:current-page="page"
|
||||||
:total="total"/>
|
v-model:page-size="pageSize"
|
||||||
|
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||||
|
@current-change="fetchData()"
|
||||||
|
:total="total"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue";
|
import nodata from "@/assets/img/no-data.png";
|
||||||
import {httpGet} from "@/utils/http";
|
|
||||||
import {ElMessage} from "element-plus";
|
import { onMounted, ref } from "vue";
|
||||||
import {dateFormat} from "@/utils/libs";
|
import { httpGet } from "@/utils/http";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { dateFormat } from "@/utils/libs";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
|
|
||||||
const items = ref([])
|
const items = ref([]);
|
||||||
const total = ref(0)
|
const total = ref(0);
|
||||||
const page = ref(1)
|
const page = ref(1);
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10);
|
||||||
const loading = ref(true)
|
const loading = ref(true);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchData()
|
fetchData();
|
||||||
const clipboard = new Clipboard('.copy-order-no');
|
const clipboard = new Clipboard(".copy-order-no");
|
||||||
clipboard.on('success', () => {
|
clipboard.on("success", () => {
|
||||||
ElMessage.success("复制成功!");
|
ElMessage.success("复制成功!");
|
||||||
})
|
});
|
||||||
|
|
||||||
clipboard.on('error', () => {
|
clipboard.on("error", () => {
|
||||||
ElMessage.error('复制失败!');
|
ElMessage.error("复制失败!");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const fetchData = () => {
|
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 })
|
||||||
if (res.data) {
|
.then((res) => {
|
||||||
items.value = res.data.items
|
if (res.data) {
|
||||||
total.value = res.data.total
|
items.value = res.data.items;
|
||||||
page.value = res.data.page
|
total.value = res.data.total;
|
||||||
pageSize.value = res.data.page_size
|
page.value = res.data.page;
|
||||||
}
|
pageSize.value = res.data.page_size;
|
||||||
loading.value = false
|
}
|
||||||
}).catch(e => {
|
loading.value = false;
|
||||||
ElMessage.error("获取数据失败:" + e.message);
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("获取数据失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
@ -90,4 +104,4 @@ const fetchData = () => {
|
|||||||
color #20a0ff
|
color #20a0ff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-container class="realtime-conversation" :style="{height: height}">
|
<el-container class="realtime-conversation" :style="{ height: height }">
|
||||||
<!-- connection animation -->
|
<!-- connection animation -->
|
||||||
<el-container class="connection-container" v-if="!isConnected">
|
<el-container class="connection-container" v-if="!isConnected">
|
||||||
<div class="phone-container">
|
<div class="phone-container">
|
||||||
@ -36,14 +36,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="call-controls">
|
<div class="call-controls">
|
||||||
<el-tooltip content="长按发送语音" placement="top" effect="light">
|
<el-tooltip content="长按发送语音" placement="top">
|
||||||
<ripple-button>
|
<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>
|
<i class="iconfont icon-mic-bold"></i>
|
||||||
</button>
|
</button>
|
||||||
</ripple-button>
|
</ripple-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip content="结束通话" placement="top" effect="light">
|
<el-tooltip content="结束通话" placement="top">
|
||||||
<button class="call-button hangup" @click="hangUp">
|
<button class="call-button hangup" @click="hangUp">
|
||||||
<i class="iconfont icon-hung-up"></i>
|
<i class="iconfont icon-hung-up"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -51,32 +55,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-container>
|
</el-container>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import RippleButton from "@/components/ui/RippleButton.vue";
|
import RippleButton from "@/components/ui/RippleButton.vue";
|
||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import { RealtimeClient } from '@openai/realtime-api-beta';
|
import { RealtimeClient } from "@openai/realtime-api-beta";
|
||||||
import { WavRecorder, WavStreamPlayer } from '@/lib/wavtools/index.js';
|
import { WavRecorder, WavStreamPlayer } from "@/lib/wavtools/index.js";
|
||||||
import { instructions } from '@/utils/conversation_config.js';
|
import { instructions } from "@/utils/conversation_config.js";
|
||||||
import { WavRenderer } from '@/utils/wav_renderer';
|
import { WavRenderer } from "@/utils/wav_renderer";
|
||||||
import {showMessageError} from "@/utils/dialog";
|
import { showMessageError } from "@/utils/dialog";
|
||||||
import {getUserToken} from "@/store/session";
|
import { getUserToken } from "@/store/session";
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars,no-undef
|
// eslint-disable-next-line no-unused-vars,no-undef
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
height: {
|
height: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '100vh'
|
default: "100vh"
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
const emits = defineEmits(['close']);
|
const emits = defineEmits(["close"]);
|
||||||
|
|
||||||
/********************** connection animation code *************************/
|
/********************** connection animation code *************************/
|
||||||
const fullText = "正在接通中...";
|
const fullText = "正在接通中...";
|
||||||
const connectingText = ref("")
|
const connectingText = ref("");
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const typeText = () => {
|
const typeText = () => {
|
||||||
if (index < fullText.length) {
|
if (index < fullText.length) {
|
||||||
@ -85,12 +88,12 @@ const typeText = () => {
|
|||||||
setTimeout(typeText, 200); // 每300毫秒显示一个字
|
setTimeout(typeText, 200); // 每300毫秒显示一个字
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
connectingText.value = '';
|
connectingText.value = "";
|
||||||
index = 0;
|
index = 0;
|
||||||
typeText();
|
typeText();
|
||||||
}, 1000); // 等待1秒后重新开始
|
}, 1000); // 等待1秒后重新开始
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
/*************************** end of code ****************************************/
|
/*************************** end of code ****************************************/
|
||||||
|
|
||||||
/********************** conversation process code ***************************/
|
/********************** conversation process code ***************************/
|
||||||
@ -102,31 +105,29 @@ const animateVoice = () => {
|
|||||||
rightVoiceActive.value = Math.random() > 0.5;
|
rightVoiceActive.value = Math.random() > 0.5;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const wavRecorder = ref(new WavRecorder({ sampleRate: 24000 }));
|
const wavRecorder = ref(new WavRecorder({ sampleRate: 24000 }));
|
||||||
const wavStreamPlayer = ref(new WavStreamPlayer({ sampleRate: 24000 }));
|
const wavStreamPlayer = ref(new WavStreamPlayer({ sampleRate: 24000 }));
|
||||||
let host = process.env.VUE_APP_WS_HOST
|
let host = process.env.VUE_APP_WS_HOST;
|
||||||
if (host === '') {
|
if (host === "") {
|
||||||
if (location.protocol === 'https:') {
|
if (location.protocol === "https:") {
|
||||||
host = 'wss://' + location.host;
|
host = "wss://" + location.host;
|
||||||
} else {
|
} else {
|
||||||
host = 'ws://' + location.host;
|
host = "ws://" + location.host;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const client = ref(
|
const client = ref(
|
||||||
new RealtimeClient({
|
new RealtimeClient({
|
||||||
url: `${host}/api/realtime`,
|
url: `${host}/api/realtime`,
|
||||||
apiKey: getUserToken(),
|
apiKey: getUserToken(),
|
||||||
dangerouslyAllowAPIKeyInBrowser: true,
|
dangerouslyAllowAPIKeyInBrowser: true
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
// // Set up client instructions and transcription
|
// // Set up client instructions and transcription
|
||||||
client.value.updateSession({
|
client.value.updateSession({
|
||||||
instructions: instructions,
|
instructions: instructions,
|
||||||
turn_detection: null,
|
turn_detection: null,
|
||||||
input_audio_transcription: { model: 'whisper-1' },
|
input_audio_transcription: { model: "whisper-1" },
|
||||||
voice: 'alloy',
|
voice: "alloy"
|
||||||
});
|
});
|
||||||
|
|
||||||
// set voice wave canvas
|
// set voice wave canvas
|
||||||
@ -137,62 +138,66 @@ const isRecording = ref(false);
|
|||||||
const backgroundAudio = ref(null);
|
const backgroundAudio = ref(null);
|
||||||
const hangUpAudio = ref(null);
|
const hangUpAudio = ref(null);
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
const connect = async () => {
|
const connect = async () => {
|
||||||
if (isConnected.value) {
|
if (isConnected.value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
// 播放背景音乐
|
// 播放背景音乐
|
||||||
if (backgroundAudio.value) {
|
if (backgroundAudio.value) {
|
||||||
backgroundAudio.value.play().catch(error => {
|
backgroundAudio.value.play().catch((error) => {
|
||||||
console.error('播放失败,可能是浏览器的自动播放策略导致的:', error);
|
console.error("播放失败,可能是浏览器的自动播放策略导致的:", error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 模拟拨号延时
|
// 模拟拨号延时
|
||||||
await sleep(3000)
|
await sleep(3000);
|
||||||
try {
|
try {
|
||||||
await client.value.connect();
|
await client.value.connect();
|
||||||
await wavRecorder.value.begin();
|
await wavRecorder.value.begin();
|
||||||
await wavStreamPlayer.value.connect();
|
await wavStreamPlayer.value.connect();
|
||||||
console.log("对话连接成功!")
|
console.log("对话连接成功!");
|
||||||
if (!client.value.isConnected()) {
|
if (!client.value.isConnected()) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected.value = true;
|
isConnected.value = true;
|
||||||
backgroundAudio.value?.pause()
|
backgroundAudio.value?.pause();
|
||||||
backgroundAudio.value.currentTime = 0
|
backgroundAudio.value.currentTime = 0;
|
||||||
client.value.sendUserMessageContent([
|
client.value.sendUserMessageContent([
|
||||||
{
|
{
|
||||||
type: 'input_text',
|
type: "input_text",
|
||||||
text: '你好,我是极客学长!',
|
text: "你好,我是极客学长!"
|
||||||
},
|
}
|
||||||
]);
|
]);
|
||||||
if (client.value.getTurnDetectionType() === 'server_vad') {
|
if (client.value.getTurnDetectionType() === "server_vad") {
|
||||||
await wavRecorder.value.record((data) => client.value.appendInputAudio(data.mono));
|
await wavRecorder.value.record((data) =>
|
||||||
|
client.value.appendInputAudio(data.mono)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 开始语音输入
|
// 开始语音输入
|
||||||
const startRecording = async () => {
|
const startRecording = async () => {
|
||||||
if (isRecording.value) {
|
if (isRecording.value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isRecording.value = true;
|
isRecording.value = true;
|
||||||
try {
|
try {
|
||||||
const trackSampleOffset = await wavStreamPlayer.value.interrupt();
|
const trackSampleOffset = await wavStreamPlayer.value.interrupt();
|
||||||
if (trackSampleOffset?.trackId) {
|
if (trackSampleOffset?.trackId) {
|
||||||
const { trackId, offset } = trackSampleOffset;
|
const { trackId, offset } = trackSampleOffset;
|
||||||
client.value.cancelResponse(trackId, offset);
|
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) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -203,7 +208,7 @@ const stopRecording = async () => {
|
|||||||
await wavRecorder.value.pause();
|
await wavRecorder.value.pause();
|
||||||
client.value.createResponse();
|
client.value.createResponse();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -232,13 +237,13 @@ const initialize = async () => {
|
|||||||
canvas.width = canvas.offsetWidth;
|
canvas.width = canvas.offsetWidth;
|
||||||
canvas.height = canvas.offsetHeight;
|
canvas.height = canvas.offsetHeight;
|
||||||
}
|
}
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext("2d");
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
const result = wavRecorder.value.recording
|
const result = wavRecorder.value.recording
|
||||||
? wavRecorder.value.getFrequencies('voice')
|
? wavRecorder.value.getFrequencies("voice")
|
||||||
: { values: new Float32Array([0]) };
|
: { 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) {
|
if (serverCanvasRef.value) {
|
||||||
@ -247,13 +252,13 @@ const initialize = async () => {
|
|||||||
canvas.width = canvas.offsetWidth;
|
canvas.width = canvas.offsetWidth;
|
||||||
canvas.height = canvas.offsetHeight;
|
canvas.height = canvas.offsetHeight;
|
||||||
}
|
}
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext("2d");
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
const result = wavStreamPlayer.value.analyser
|
const result = wavStreamPlayer.value.analyser
|
||||||
? wavStreamPlayer.value.getFrequencies('voice')
|
? wavStreamPlayer.value.getFrequencies("voice")
|
||||||
: { values: new Float32Array([0]) };
|
: { 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);
|
requestAnimationFrame(render);
|
||||||
@ -261,17 +266,17 @@ const initialize = async () => {
|
|||||||
};
|
};
|
||||||
render();
|
render();
|
||||||
|
|
||||||
client.value.on('error', (event) => {
|
client.value.on("error", (event) => {
|
||||||
showMessageError(event.error)
|
showMessageError(event.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.value.on('realtime.event', (re) => {
|
client.value.on("realtime.event", (re) => {
|
||||||
if (re.event.type === 'error') {
|
if (re.event.type === "error") {
|
||||||
showMessageError(re.event.error)
|
showMessageError(re.event.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.value.on('conversation.interrupted', async () => {
|
client.value.on("conversation.interrupted", async () => {
|
||||||
const trackSampleOffset = await wavStreamPlayer.value.interrupt();
|
const trackSampleOffset = await wavStreamPlayer.value.interrupt();
|
||||||
if (trackSampleOffset?.trackId) {
|
if (trackSampleOffset?.trackId) {
|
||||||
const { trackId, offset } = trackSampleOffset;
|
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)
|
// console.log('item updated', item, delta)
|
||||||
if (delta?.audio) {
|
if (delta?.audio) {
|
||||||
wavStreamPlayer.value.add16BitPCM(delta.audio, item.id);
|
wavStreamPlayer.value.add16BitPCM(delta.audio, item.id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const voiceInterval = ref(null);
|
const voiceInterval = ref(null);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initialize()
|
initialize();
|
||||||
// 启动聊天进行中的动画
|
// 启动聊天进行中的动画
|
||||||
voiceInterval.value = setInterval(animateVoice, 200);
|
voiceInterval.value = setInterval(animateVoice, 200);
|
||||||
typeText()
|
typeText();
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -304,32 +308,31 @@ onUnmounted(() => {
|
|||||||
// 挂断通话
|
// 挂断通话
|
||||||
const hangUp = async () => {
|
const hangUp = async () => {
|
||||||
try {
|
try {
|
||||||
isConnected.value = false
|
isConnected.value = false;
|
||||||
// 停止播放拨号音乐
|
// 停止播放拨号音乐
|
||||||
if (backgroundAudio.value?.currentTime) {
|
if (backgroundAudio.value?.currentTime) {
|
||||||
backgroundAudio.value?.pause()
|
backgroundAudio.value?.pause();
|
||||||
backgroundAudio.value.currentTime = 0
|
backgroundAudio.value.currentTime = 0;
|
||||||
}
|
}
|
||||||
// 断开客户端的连接
|
// 断开客户端的连接
|
||||||
client.value.reset()
|
client.value.reset();
|
||||||
// 中断语音输入和输出服务
|
// 中断语音输入和输出服务
|
||||||
await wavRecorder.value.end()
|
await wavRecorder.value.end();
|
||||||
await wavStreamPlayer.value.interrupt()
|
await wavStreamPlayer.value.interrupt();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
// 播放挂断音乐
|
// 播放挂断音乐
|
||||||
hangUpAudio.value?.play()
|
hangUpAudio.value?.play();
|
||||||
emits('close')
|
emits("close");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
defineExpose({ connect,hangUp });
|
defineExpose({ connect, hangUp });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
|
|
||||||
@import "@/assets/css/realtime.styl"
|
@import "@/assets/css/realtime.styl"
|
||||||
|
</style>
|
||||||
</style>
|
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
<div class="running-job-box" v-if="list.length > 0">
|
<div class="running-job-box" v-if="list.length > 0">
|
||||||
<div class="job-item" v-for="item in list" :key="item.id">
|
<div class="job-item" v-for="item in list" :key="item.id">
|
||||||
<div v-if="item.progress > 0" class="job-item-inner">
|
<div v-if="item.progress > 0" class="job-item-inner">
|
||||||
|
<div class="progress" v-if="item.progress > 0">
|
||||||
<div class="progress" v-if="item.progress > 0">
|
<el-progress
|
||||||
<el-progress type="circle" :percentage="item.progress" :width="100"
|
type="circle"
|
||||||
color="#47fff1"/>
|
:percentage="item.progress"
|
||||||
</div>
|
:width="100"
|
||||||
|
color="#47fff1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-image fit="cover" v-else>
|
<el-image fit="cover" v-else>
|
||||||
<template #error>
|
<template #error>
|
||||||
@ -20,21 +22,22 @@
|
|||||||
</el-image>
|
</el-image>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-empty :image-size="100" v-else/>
|
<el-empty :image-size="100" v-else :image="nodata" description="暂无任务" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import nodata from "@/assets/img/no-data.png";
|
||||||
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
list: {
|
list: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default:[],
|
default: []
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
@import "~@/assets/css/running-job-list.styl"
|
@import "~@/assets/css/running-job-list.styl"
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,95 +1,111 @@
|
|||||||
<template>
|
<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-row v-if="items.length > 0">
|
||||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
<el-table
|
||||||
style="--el-table-border-color:#373C47;
|
:data="items"
|
||||||
--el-table-tr-bg-color:#2D323B;
|
:row-key="(row) => row.id"
|
||||||
--el-table-row-hover-bg-color:#373C47;
|
table-layout="auto"
|
||||||
--el-table-header-bg-color:#474E5C;
|
border
|
||||||
--el-table-text-color:#d1d1d1">
|
>
|
||||||
<el-table-column prop="order_no" label="订单号">
|
<el-table-column prop="order_no" label="订单号">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ scope.row.order_no }}</span>
|
<span>{{ scope.row.order_no }}</span>
|
||||||
<el-icon class="copy-order-no" :data-clipboard-text="scope.row.order_no">
|
<el-icon
|
||||||
<DocumentCopy/>
|
class="copy-order-no"
|
||||||
|
:data-clipboard-text="scope.row.order_no"
|
||||||
|
>
|
||||||
|
<DocumentCopy />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="subject" label="产品名称"/>
|
<el-table-column prop="subject" label="产品名称" />
|
||||||
<el-table-column prop="amount" label="订单金额"/>
|
<el-table-column prop="amount" label="订单金额" />
|
||||||
<el-table-column label="订单算力">
|
<el-table-column label="订单算力">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ scope.row.remark?.power }}</span>
|
<span>{{ scope.row.remark?.power }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="pay_method" label="支付渠道"/>
|
<el-table-column prop="pay_method" label="支付渠道" />
|
||||||
<el-table-column prop="pay_name" label="支付名称"/>
|
<el-table-column prop="pay_name" label="支付名称" />
|
||||||
<el-table-column label="支付时间">
|
<el-table-column label="支付时间">
|
||||||
<template #default="scope">
|
<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>
|
<el-tag v-else>未支付</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-empty :image-size="100" v-else/>
|
<el-empty :image-size="100" v-else />
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<el-pagination v-if="total > 0" background
|
<el-pagination
|
||||||
layout="total,prev, pager, next"
|
v-if="total > 0"
|
||||||
:hide-on-single-page="true"
|
background
|
||||||
v-model:current-page="page"
|
layout="total,prev, pager, next"
|
||||||
v-model:page-size="pageSize"
|
:hide-on-single-page="true"
|
||||||
@current-change="fetchData()"
|
v-model:current-page="page"
|
||||||
:total="total"/>
|
v-model:page-size="pageSize"
|
||||||
|
@current-change="fetchData()"
|
||||||
|
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||||
|
:total="total"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import {httpGet} from "@/utils/http";
|
import { httpGet } from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import {dateFormat} from "@/utils/libs";
|
import { dateFormat } from "@/utils/libs";
|
||||||
import {DocumentCopy} from "@element-plus/icons-vue";
|
import { DocumentCopy } from "@element-plus/icons-vue";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
|
|
||||||
const items = ref([])
|
const items = ref([]);
|
||||||
const total = ref(0)
|
const total = ref(0);
|
||||||
const page = ref(1)
|
const page = ref(1);
|
||||||
const pageSize = ref(12)
|
const pageSize = ref(12);
|
||||||
const loading = ref(true)
|
const loading = ref(true);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchData()
|
fetchData();
|
||||||
const clipboard = new Clipboard('.copy-order-no');
|
const clipboard = new Clipboard(".copy-order-no");
|
||||||
clipboard.on('success', () => {
|
clipboard.on("success", () => {
|
||||||
ElMessage.success("复制成功!");
|
ElMessage.success("复制成功!");
|
||||||
})
|
});
|
||||||
|
|
||||||
clipboard.on('error', () => {
|
clipboard.on("error", () => {
|
||||||
ElMessage.error('复制失败!');
|
ElMessage.error("复制失败!");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const fetchData = () => {
|
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 })
|
||||||
if (res.data) {
|
.then((res) => {
|
||||||
items.value = res.data.items
|
if (res.data) {
|
||||||
total.value = res.data.total
|
items.value = res.data.items;
|
||||||
page.value = res.data.page
|
total.value = res.data.total;
|
||||||
pageSize.value = res.data.page_size
|
page.value = res.data.page;
|
||||||
}
|
pageSize.value = res.data.page_size;
|
||||||
loading.value = false
|
}
|
||||||
}).catch(e => {
|
loading.value = false;
|
||||||
ElMessage.error("获取数据失败:" + e.message);
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("获取数据失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="stylus">
|
<style scoped lang="stylus">
|
||||||
.user-bill {
|
.user-bill {
|
||||||
|
background-color: var(--chat-bg);
|
||||||
|
|
||||||
.pagination {
|
.pagination {
|
||||||
margin: 20px 0 0 0;
|
margin: 20px 0 0 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -105,4 +121,4 @@ const fetchData = () => {
|
|||||||
color #20a0ff
|
color #20a0ff
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,77 +3,92 @@
|
|||||||
<el-form :model="user" label-width="100px">
|
<el-form :model="user" label-width="100px">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-upload
|
<el-upload
|
||||||
class="avatar-uploader"
|
class="avatar-uploader"
|
||||||
:auto-upload="true"
|
:auto-upload="true"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
:http-request="afterRead"
|
:http-request="afterRead"
|
||||||
accept=".png,.jpg,.jpeg,.bmp"
|
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">
|
<el-icon v-else class="avatar-uploader-icon">
|
||||||
<Plus/>
|
<Plus />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-form-item label="昵称">
|
<el-form-item label="昵称">
|
||||||
<el-input v-model="user['nickname']"/>
|
<el-input v-model="user['nickname']" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="账号">
|
<el-form-item label="账号">
|
||||||
<span>{{ user.username }}</span>
|
<div class="flex">
|
||||||
<el-tooltip
|
<span>{{ user.username }}</span>
|
||||||
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="light"
|
|
||||||
content="您已经是 VIP 会员"
|
content="您已经是 VIP 会员"
|
||||||
placement="right"
|
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-tooltip>
|
><el-image
|
||||||
|
v-if="user.vip"
|
||||||
|
:src="vipImg"
|
||||||
|
style="height: 25px; margin-left: 10px"
|
||||||
|
/></span>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="剩余算力">
|
<el-form-item label="剩余算力">
|
||||||
<el-tag>{{ user['power'] }}</el-tag>
|
<el-text type="warning">{{ user["power"] }}</el-text>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="会员到期时间" v-if="user['expired_time'] > 0">
|
<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-form-item>
|
||||||
|
|
||||||
<el-row class="opt-line">
|
<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-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue"
|
import { onMounted, ref } from "vue";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import { httpGet, httpPost } from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import {Plus} from "@element-plus/icons-vue";
|
import { Plus } from "@element-plus/icons-vue";
|
||||||
import Compressor from "compressorjs";
|
import Compressor from "compressorjs";
|
||||||
import {dateFormat} from "@/utils/libs";
|
import { dateFormat } from "@/utils/libs";
|
||||||
import {checkSession} from "@/store/cache";
|
import { checkSession } from "@/store/cache";
|
||||||
|
|
||||||
const user = ref({
|
const user = ref({
|
||||||
vip: false,
|
vip: false,
|
||||||
username: '演示数据',
|
username: "演示数据",
|
||||||
nickname: '演示数据',
|
nickname: "演示数据",
|
||||||
avatar: '/images/vip.png',
|
avatar: "/images/menu/member.png",
|
||||||
mobile: '演示数据',
|
mobile: "演示数据",
|
||||||
power: 99999,
|
power: 99999
|
||||||
})
|
});
|
||||||
const vipImg = ref("/images/vip.png")
|
const vipImg = ref("/images/menu/member.png");
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkSession().then(() => {
|
checkSession()
|
||||||
// 获取最新用户信息
|
.then(() => {
|
||||||
httpGet('/api/user/profile').then(res => {
|
// 获取最新用户信息
|
||||||
user.value = res.data
|
httpGet("/api/user/profile")
|
||||||
}).catch(e => {
|
.then((res) => {
|
||||||
ElMessage.error("获取用户信息失败:" + e.message)
|
user.value = res.data;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取用户信息失败:" + e.message);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
});
|
});
|
||||||
}).catch(e => {
|
});
|
||||||
console.log(e)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const afterRead = (file) => {
|
const afterRead = (file) => {
|
||||||
// 压缩图片并上传
|
// 压缩图片并上传
|
||||||
@ -81,28 +96,32 @@ const afterRead = (file) => {
|
|||||||
quality: 0.6,
|
quality: 0.6,
|
||||||
success(result) {
|
success(result) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', result, result.name);
|
formData.append("file", result, result.name);
|
||||||
// 执行上传操作
|
// 执行上传操作
|
||||||
httpPost('/api/upload', formData).then((res) => {
|
httpPost("/api/upload", formData)
|
||||||
user.value.avatar = res.data.url
|
.then((res) => {
|
||||||
ElMessage.success({message: "上传成功", duration: 500})
|
user.value.avatar = res.data.url;
|
||||||
}).catch((e) => {
|
ElMessage.success({ message: "上传成功", duration: 500 });
|
||||||
ElMessage.error('图片上传失败:' + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("图片上传失败:" + e.message);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
error(err) {
|
error(err) {
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
httpPost('/api/user/profile/update', user.value).then(() => {
|
httpPost("/api/user/profile/update", user.value)
|
||||||
ElMessage.success({message: '更新成功', duration: 500})
|
.then(() => {
|
||||||
}).catch((e) => {
|
ElMessage.success({ message: "更新成功", duration: 500 });
|
||||||
ElMessage.error('更新失败:' + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("更新失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@ -127,4 +146,4 @@ const save = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="title">{{ title }}-{{ version }}</h1>
|
<h2 class="title">{{ title }}-{{ version }}</h2>
|
||||||
|
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
@ -128,10 +128,11 @@ const send = (text) => {
|
|||||||
width 100%
|
width 100%
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 2.25rem
|
// font-size: 2.25rem
|
||||||
line-height: 2.5rem
|
line-height: 2.5rem
|
||||||
font-weight 600
|
font-weight 600
|
||||||
margin-bottom: 4rem
|
margin-bottom: 4rem
|
||||||
|
color var( --theme-textcolor-normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-content {
|
.grid-content {
|
||||||
|
@ -1,57 +1,58 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="black-input-wrapper">
|
<div class="black-input-wrapper">
|
||||||
<el-input v-model="model" :type="type" :rows="rows"
|
<el-input
|
||||||
@input="onInput"
|
v-model="model"
|
||||||
style="--el-input-bg-color:#252020;
|
:type="type"
|
||||||
--el-input-border-color:#414141;
|
:rows="rows"
|
||||||
--el-input-focus-border-color:#414141;
|
@input="onInput"
|
||||||
--el-text-color-regular: #f1f1f1;
|
resize="none"
|
||||||
--el-input-border-radius: 10px;
|
:placeholder="placeholder"
|
||||||
--el-border-color-hover:#616161"
|
:maxlength="maxlength"
|
||||||
resize="none"
|
/>
|
||||||
:placeholder="placeholder" :maxlength="maxlength"/>
|
|
||||||
<div class="word-stat" v-if="rows > 1">
|
<div class="word-stat" v-if="rows > 1">
|
||||||
<span>{{value.length}}</span>/<span>{{maxlength}}</span>
|
<span>{{ value.length }}</span
|
||||||
|
>/<span>{{ maxlength }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, watch } from "vue";
|
||||||
import {ref, watch} from "vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value : {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: ""
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: ""
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'input',
|
default: "input"
|
||||||
},
|
},
|
||||||
rows: {
|
rows: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 5,
|
default: 5
|
||||||
},
|
},
|
||||||
maxlength: {
|
maxlength: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1024
|
default: 1024
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
watch(() => props.value, (newValue) => {
|
watch(
|
||||||
model.value = newValue
|
() => props.value,
|
||||||
})
|
(newValue) => {
|
||||||
const model = ref(props.value)
|
model.value = newValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const model = ref(props.value);
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
const emits = defineEmits(['update:value']);
|
const emits = defineEmits(["update:value"]);
|
||||||
const onInput = (value) => {
|
const onInput = (value) => {
|
||||||
emits('update:value',value)
|
emits("update:value", value);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
@ -77,4 +78,4 @@ const onInput = (value) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,33 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<el-select
|
||||||
<el-select v-model="model" :placeholder="placeholder"
|
v-model="model"
|
||||||
:value="value" @change="$emit('update:value', $event)"
|
:placeholder="placeholder"
|
||||||
style="--el-fill-color-blank:#252020;
|
:value="value"
|
||||||
--el-text-color-regular: #a1a1a1;
|
@change="$emit('update:value', $event)"
|
||||||
--el-select-disabled-color:#0E0808;
|
style="--el-border-radius-base: 20px"
|
||||||
--el-color-primary-light-9:#0E0808;
|
>
|
||||||
--el-border-radius-base:20px;
|
<el-option
|
||||||
--el-border-color:#0E0808;">
|
v-for="item in options"
|
||||||
<el-option v-for="item in options"
|
:key="item.value"
|
||||||
:key="item.value"
|
:label="item.label"
|
||||||
:label="item.label"
|
:value="item.value"
|
||||||
:value="item.value">
|
>
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'BlackSelect',
|
name: "BlackSelect",
|
||||||
props: {
|
props: {
|
||||||
value : {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: ""
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '请选择',
|
default: "请选择"
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -37,7 +36,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
model: this.value
|
model: this.value
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<el-switch
|
||||||
<el-switch v-model="model" :size="size"
|
v-model="model"
|
||||||
@change="$emit('update:value', $event)"
|
:size="size"
|
||||||
style="--el-switch-on-color:#555555;--el-color-white:#0E0808"/>
|
@change="$emit('update:value', $event)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref, watch } from "vue";
|
||||||
import {ref, watch} from "vue";
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value : Boolean,
|
value: Boolean,
|
||||||
size: {
|
size: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'default',
|
default: "default"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const model = ref(props.value)
|
const model = ref(props.value);
|
||||||
|
|
||||||
watch(() => props.value, (newValue) => {
|
watch(
|
||||||
model.value = newValue
|
() => props.value,
|
||||||
})
|
(newValue) => {
|
||||||
|
model.value = newValue;
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,26 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
|
||||||
<div class="page-apps custom-scroll">
|
<div class="page-apps custom-scroll">
|
||||||
<div class="apps-type-nav">
|
<div class="apps-type-nav">
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<ul class="scrollbar-type-nav">
|
<ul class="scrollbar-type-nav">
|
||||||
<li :class="{active: typeId === ''}" @click="getAppList('')">全部分类</li>
|
<li :class="{ active: typeId === '' }" @click="getAppList('')">
|
||||||
<li v-for="item in appTypes" :key="item.id" :class="{active: typeId === item.id}" @click="getAppList(item.id)">
|
全部分类
|
||||||
|
</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">
|
<div class="image" v-if="item.icon">
|
||||||
<el-image :src="item.icon" fit="cover"/>
|
<el-image :src="item.icon" fit="cover" />
|
||||||
</div>
|
</div>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</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">
|
<ItemList :items="list" v-if="list.length > 0" :gap="15" :width="300">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<el-image :src="scope.item.icon" fit="cover"/>
|
<el-image :src="scope.item.icon" fit="cover" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
@ -29,17 +35,34 @@
|
|||||||
<div class="info-text">{{ scope.item.hello_msg }}</div>
|
<div class="info-text">{{ scope.item.hello_msg }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn">
|
<div class="btn">
|
||||||
<el-button size="small" color="#21aa93" @click="useRole(scope.item)">使用</el-button>
|
<el-button
|
||||||
<el-tooltip effect="light" content="从工作区移除" placement="top" v-if="hasRole(scope.item.key)">
|
size="small"
|
||||||
<el-button size="small" type="danger" @click="updateRole(scope.item,'remove')">移除</el-button>
|
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>
|
||||||
<el-tooltip effect="light" content="添加到工作区" placement="top" v-else>
|
<el-tooltip content="添加到工作区" placement="top" v-else>
|
||||||
<el-button size="small" style="--el-color-primary:#009999" @click="updateRole(scope.item, 'add')">添加</el-button>
|
<el-button
|
||||||
|
size="small"
|
||||||
|
style="--el-color-primary: #009999"
|
||||||
|
@click="updateRole(scope.item, 'add')"
|
||||||
|
>添加</el-button
|
||||||
|
>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="app-item">-->
|
<!-- <div class="app-item">-->
|
||||||
<!-- <el-image :src="scope.item.icon" fit="cover"/>-->
|
<!-- <el-image :src="scope.item.icon" fit="cover"/>-->
|
||||||
@ -73,95 +96,108 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue"
|
import { onMounted, ref } from "vue";
|
||||||
import {ElMessage} from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import { httpGet, httpPost } from "@/utils/http";
|
||||||
import {checkSession} from "@/store/cache";
|
import { checkSession } from "@/store/cache";
|
||||||
import {arrayContains, removeArrayItem, substr} from "@/utils/libs";
|
import { arrayContains, removeArrayItem, substr } from "@/utils/libs";
|
||||||
import {useRouter} from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import { useSharedStore } from "@/store/sharedata";
|
||||||
import ItemList from "@/components/ItemList.vue";
|
import ItemList from "@/components/ItemList.vue";
|
||||||
|
|
||||||
const listBoxHeight = window.innerHeight - 133
|
const listBoxHeight = window.innerHeight - 133;
|
||||||
|
|
||||||
const typeId = ref('')
|
const typeId = ref("");
|
||||||
const appTypes = ref([])
|
const appTypes = ref([]);
|
||||||
const list = ref([])
|
const list = ref([]);
|
||||||
const roles = ref([])
|
const roles = ref([]);
|
||||||
const store = useSharedStore();
|
const store = useSharedStore();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getAppType()
|
getAppType();
|
||||||
getAppList()
|
getAppList();
|
||||||
getRoles()
|
getRoles();
|
||||||
})
|
});
|
||||||
|
|
||||||
const getRoles = () => {
|
const getRoles = () => {
|
||||||
checkSession().then(user => {
|
checkSession()
|
||||||
roles.value = user.chat_roles
|
.then((user) => {
|
||||||
}).catch(e => {
|
roles.value = user.chat_roles;
|
||||||
console.log(e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
console.log(e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getAppType = () => {
|
const getAppType = () => {
|
||||||
httpGet("/api/app/type/list").then((res) => {
|
httpGet("/api/app/type/list")
|
||||||
appTypes.value = res.data
|
.then((res) => {
|
||||||
}).catch(e => {
|
appTypes.value = res.data;
|
||||||
ElMessage.error("获取分类失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("获取分类失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const getAppList = (tid = '') => {
|
const getAppList = (tid = "") => {
|
||||||
typeId.value = tid;
|
typeId.value = tid;
|
||||||
httpGet("/api/app/list", { tid }).then((res) => {
|
httpGet("/api/app/list", { tid })
|
||||||
const items = res.data
|
.then((res) => {
|
||||||
// 处理 hello message
|
const items = res.data;
|
||||||
for (let i = 0; i < items.length; i++) {
|
// 处理 hello message
|
||||||
items[i].intro = substr(items[i].hello_msg, 80)
|
for (let i = 0; i < items.length; i++) {
|
||||||
}
|
items[i].intro = substr(items[i].hello_msg, 80);
|
||||||
list.value = items
|
}
|
||||||
}).catch(e => {
|
list.value = items;
|
||||||
ElMessage.error("获取应用失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("获取应用失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const updateRole = (row, opt) => {
|
const updateRole = (row, opt) => {
|
||||||
checkSession().then(() => {
|
checkSession()
|
||||||
const title = ref("")
|
.then(() => {
|
||||||
if (opt === "add") {
|
const title = ref("");
|
||||||
title.value = "添加应用"
|
if (opt === "add") {
|
||||||
const exists = arrayContains(roles.value, row.key)
|
title.value = "添加应用";
|
||||||
if (exists) {
|
const exists = arrayContains(roles.value, row.key);
|
||||||
return
|
if (exists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roles.value.push(row.key);
|
||||||
|
} else {
|
||||||
|
title.value = "移除应用";
|
||||||
|
const exists = arrayContains(roles.value, row.key);
|
||||||
|
if (!exists) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roles.value = removeArrayItem(roles.value, row.key);
|
||||||
}
|
}
|
||||||
roles.value.push(row.key)
|
httpPost("/api/app/update", { keys: roles.value })
|
||||||
} else {
|
.then(() => {
|
||||||
title.value = "移除应用"
|
ElMessage.success({
|
||||||
const exists = arrayContains(roles.value, row.key)
|
message: title.value + "成功!",
|
||||||
if (!exists) {
|
duration: 1000
|
||||||
return
|
});
|
||||||
}
|
})
|
||||||
roles.value = removeArrayItem(roles.value, row.key)
|
.catch((e) => {
|
||||||
}
|
ElMessage.error(title.value + "失败:" + e.message);
|
||||||
httpPost("/api/app/update", {keys: roles.value}).then(() => {
|
});
|
||||||
ElMessage.success({message: title.value + "成功!", duration: 1000})
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error(title.value + "失败:" + e.message)
|
|
||||||
})
|
})
|
||||||
}).catch(() => {
|
.catch(() => {
|
||||||
store.setShowLoginDialog(true)
|
store.setShowLoginDialog(true);
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const hasRole = (roleKey) => {
|
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) => {
|
const useRole = (role) => {
|
||||||
router.push(`/chat?role_id=${role.id}`)
|
router.push(`/chat?role_id=${role.id}`);
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
@ -880,6 +880,7 @@ const autofillPrompt = (text) => {
|
|||||||
// 发送消息
|
// 发送消息
|
||||||
const sendMessage = function () {
|
const sendMessage = function () {
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
|
console.log("未登录");
|
||||||
store.setShowLoginDialog(true);
|
store.setShowLoginDialog(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,13 @@
|
|||||||
<el-form-item label="图片质量">
|
<el-form-item label="图片质量">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="form-item-inner">
|
<div class="form-item-inner">
|
||||||
<el-select v-model="params.quality" style="width:176px">
|
<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-option
|
||||||
|
v-for="v in qualities"
|
||||||
|
:label="v.name"
|
||||||
|
:value="v.value"
|
||||||
|
:key="v.value"
|
||||||
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -23,8 +28,13 @@
|
|||||||
<el-form-item label="图片尺寸">
|
<el-form-item label="图片尺寸">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="form-item-inner">
|
<div class="form-item-inner">
|
||||||
<el-select v-model="params.size" style="width:176px">
|
<el-select v-model="params.size" style="width: 176px">
|
||||||
<el-option v-for="v in sizes" :label="v" :value="v" :key="v"/>
|
<el-option
|
||||||
|
v-for="v in sizes"
|
||||||
|
:label="v"
|
||||||
|
:value="v"
|
||||||
|
:key="v"
|
||||||
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -35,17 +45,21 @@
|
|||||||
<el-form-item label="图片样式">
|
<el-form-item label="图片样式">
|
||||||
<template #default>
|
<template #default>
|
||||||
<div class="form-item-inner">
|
<div class="form-item-inner">
|
||||||
<el-select v-model="params.style" style="width:176px">
|
<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-option
|
||||||
|
v-for="v in styles"
|
||||||
|
:label="v.name"
|
||||||
|
:value="v.value"
|
||||||
|
:key="v.value"
|
||||||
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
effect="light"
|
content="生动使模型倾向于生成超真实和戏剧性的图像"
|
||||||
content="生动使模型倾向于生成超真实和戏剧性的图像"
|
raw-content
|
||||||
raw-content
|
placement="right"
|
||||||
placement="right"
|
|
||||||
>
|
>
|
||||||
<el-icon class="info-icon">
|
<el-icon class="info-icon">
|
||||||
<InfoFilled/>
|
<InfoFilled />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -55,52 +69,68 @@
|
|||||||
|
|
||||||
<div class="param-line">
|
<div class="param-line">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="params.prompt"
|
v-model="params.prompt"
|
||||||
:autosize="{ minRows: 4, maxRows: 6 }"
|
:autosize="{ minRows: 4, maxRows: 6 }"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
ref="promptRef"
|
ref="promptRef"
|
||||||
placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
|
placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
|
||||||
v-loading="isGenerating"
|
v-loading="isGenerating"
|
||||||
style="--el-mask-color:rgba(100, 100, 100, 0.8)"
|
style="--el-mask-color: rgba(100, 100, 100, 0.8)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-row class="text-info">
|
<el-row class="text-info">
|
||||||
<el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2" :disabled="isGenerating">
|
<el-button
|
||||||
<i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
|
class="generate-btn"
|
||||||
|
size="small"
|
||||||
|
@click="generatePrompt"
|
||||||
|
color="#5865f2"
|
||||||
|
:disabled="isGenerating"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="iconfont icon-chuangzuo"
|
||||||
|
style="margin-right: 5px"
|
||||||
|
></i>
|
||||||
<span>生成专业绘画指令</span>
|
<span>生成专业绘画指令</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<div class="text-info">
|
<div class="text-info">
|
||||||
<el-row :gutter="10">
|
<el-row :gutter="10">
|
||||||
<el-col :span="12">
|
<el-text type="primary"
|
||||||
<el-tag>每次绘图消耗{{ dallPower }}算力</el-tag>
|
>每次绘图消耗
|
||||||
</el-col>
|
<el-text type="warning"
|
||||||
<el-col :span="12">
|
>{{ dallPower }}算力;</el-text
|
||||||
<el-tag type="success">当前可用{{ power }}算力</el-tag>
|
></el-text
|
||||||
</el-col>
|
>
|
||||||
|
|
||||||
|
<el-text type="primary"
|
||||||
|
>当前可用
|
||||||
|
<el-text type="warning"> {{ power }}算力</el-text>
|
||||||
|
</el-text>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="submit-btn">
|
<div class="submit-btn">
|
||||||
<el-button color="#47fff1" :dark="false" round @click="generate">
|
<el-button type="primary" :dark="false" round @click="generate">
|
||||||
立即生成
|
立即生成
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-list-box">
|
<div class="task-list-box">
|
||||||
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
<div
|
||||||
|
class="task-list-inner"
|
||||||
|
:style="{ height: listBoxHeight + 'px' }"
|
||||||
|
>
|
||||||
<div class="job-list-box">
|
<div class="job-list-box">
|
||||||
<h2>任务列表</h2>
|
<h2>任务列表</h2>
|
||||||
<task-list :list="runningJobs" />
|
<task-list :list="runningJobs" />
|
||||||
|
<template v-if="finishedJobs.length > 0">
|
||||||
<h2>创作记录</h2>
|
<h2>创作记录</h2>
|
||||||
<div class="finish-job-list">
|
<div class="finish-job-list">
|
||||||
<div v-if="finishedJobs.length > 0">
|
<div v-if="finishedJobs.length > 0">
|
||||||
<v3-waterfall
|
<v3-waterfall
|
||||||
id="waterfall"
|
id="waterfall"
|
||||||
:list="finishedJobs"
|
:list="finishedJobs"
|
||||||
srcKey="img_thumb"
|
srcKey="img_thumb"
|
||||||
@ -110,331 +140,390 @@
|
|||||||
:distanceToScroll="100"
|
:distanceToScroll="100"
|
||||||
:isLoading="loading"
|
:isLoading="loading"
|
||||||
:isOver="isOver"
|
:isOver="isOver"
|
||||||
@scrollReachBottom="fetchFinishJobs()">
|
@scrollReachBottom="fetchFinishJobs()"
|
||||||
<template #default="slotProp">
|
>
|
||||||
<div class="job-item">
|
<template #default="slotProp">
|
||||||
<el-image
|
<div class="job-item">
|
||||||
|
<el-image
|
||||||
v-if="slotProp.item.img_url !== ''"
|
v-if="slotProp.item.img_url !== ''"
|
||||||
@click="previewImg(slotProp.item)"
|
@click="previewImg(slotProp.item)"
|
||||||
:src="slotProp.item['img_thumb']"
|
:src="slotProp.item['img_thumb']"
|
||||||
fit="cover"
|
fit="cover"
|
||||||
loading="lazy">
|
loading="lazy"
|
||||||
<template #placeholder>
|
>
|
||||||
<div class="image-slot">
|
<template #placeholder>
|
||||||
正在加载图片
|
<div class="image-slot">正在加载图片</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Picture/>
|
<Picture />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-image>
|
</el-image>
|
||||||
|
|
||||||
<el-image v-else-if="slotProp.item.progress === 101">
|
<el-image v-else-if="slotProp.item.progress === 101">
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<div class="err-msg-container">
|
<div class="err-msg-container">
|
||||||
<div class="title">任务失败</div>
|
<div class="title">任务失败</div>
|
||||||
<div class="opt">
|
<div class="opt">
|
||||||
<el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
|
<el-popover
|
||||||
<template #reference>
|
title="错误详情"
|
||||||
<el-button type="info">详情</el-button>
|
trigger="click"
|
||||||
</template>
|
:width="250"
|
||||||
</el-popover>
|
:content="slotProp.item['err_msg']"
|
||||||
<el-button type="danger" @click="removeImage(slotProp.item)">删除</el-button>
|
placement="top"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button type="info">详情</el-button>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
@click="removeImage(slotProp.item)"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</el-image>
|
||||||
</el-image>
|
|
||||||
|
|
||||||
<el-image v-else>
|
<el-image v-else>
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<i class="iconfont icon-loading"></i>
|
<i class="iconfont icon-loading"></i>
|
||||||
<span>正在下载图片</span>
|
<span>正在下载图片</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-image>
|
</el-image>
|
||||||
|
|
||||||
<div class="remove">
|
<div class="remove">
|
||||||
<el-tooltip content="删除" placement="top" effect="light">
|
<el-tooltip content="删除" placement="top">
|
||||||
<el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
|
<el-button
|
||||||
</el-tooltip>
|
type="danger"
|
||||||
<el-tooltip content="取消分享" placement="top" effect="light" v-if="slotProp.item.publish">
|
:icon="Delete"
|
||||||
<el-button type="warning"
|
@click="removeImage(slotProp.item)"
|
||||||
@click="publishImage(slotProp.item, false)"
|
circle
|
||||||
circle>
|
/>
|
||||||
<i class="iconfont icon-cancel-share"></i>
|
</el-tooltip>
|
||||||
</el-button>
|
<el-tooltip
|
||||||
</el-tooltip>
|
content="取消分享"
|
||||||
<el-tooltip content="分享" placement="top" effect="light" v-else>
|
placement="top"
|
||||||
<el-button type="success" @click="publishImage(slotProp.item, true)" circle>
|
v-if="slotProp.item.publish"
|
||||||
<i class="iconfont icon-share-bold"></i>
|
>
|
||||||
</el-button>
|
<el-button
|
||||||
</el-tooltip>
|
type="warning"
|
||||||
|
@click="publishImage(slotProp.item, false)"
|
||||||
|
circle
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-cancel-share"></i>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<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-tooltip content="复制提示词" placement="top">
|
||||||
<el-button type="info" circle class="copy-prompt"
|
<el-button
|
||||||
:data-clipboard-text="slotProp.item.prompt">
|
type="info"
|
||||||
<i class="iconfont icon-file"></i>
|
circle
|
||||||
</el-button>
|
class="copy-prompt"
|
||||||
</el-tooltip>
|
:data-clipboard-text="slotProp.item.prompt"
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-file"></i>
|
||||||
|
</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="no-more-data">
|
|
||||||
<span>没有更多数据了</span>
|
|
||||||
<i class="iconfont icon-face"></i>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</v3-waterfall>
|
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="no-more-data">
|
||||||
|
<span>没有更多数据了</span>
|
||||||
|
<i class="iconfont icon-face"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v3-waterfall>
|
||||||
|
</div>
|
||||||
|
<el-empty
|
||||||
|
:image-size="100"
|
||||||
|
:image="nodata"
|
||||||
|
description="暂无记录"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<el-empty :image-size="100" v-else/>
|
</template>
|
||||||
</div> <!-- end finish job list-->
|
|
||||||
|
<!-- end finish job list-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
<back-top :right="30" :bottom="30" />
|
||||||
</div><!-- end task list box -->
|
</div>
|
||||||
|
<!-- end task list box -->
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
|
import nodata from "@/assets/img/no-data.png";
|
||||||
import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue";
|
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
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 Clipboard from "clipboard";
|
||||||
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
|
import { checkSession, getClientId, getSystemInfo } from "@/store/cache";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import { useSharedStore } from "@/store/sharedata";
|
||||||
import TaskList from "@/components/TaskList.vue";
|
import TaskList from "@/components/TaskList.vue";
|
||||||
import BackTop from "@/components/BackTop.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 paramBoxHeight = ref(0)
|
||||||
const isLogin = ref(false)
|
const isLogin = ref(false);
|
||||||
const loading = ref(true)
|
const loading = ref(true);
|
||||||
const colWidth = ref(220)
|
const colWidth = ref(220);
|
||||||
const isOver = ref(false)
|
const isOver = ref(false);
|
||||||
const previewURL = ref("")
|
const previewURL = ref("");
|
||||||
const store = useSharedStore();
|
const store = useSharedStore();
|
||||||
|
|
||||||
const resizeElement = function () {
|
const resizeElement = function () {
|
||||||
listBoxHeight.value = window.innerHeight - 90
|
listBoxHeight.value = window.innerHeight - 90;
|
||||||
// paramBoxHeight.value = window.innerHeight - 110
|
// paramBoxHeight.value = window.innerHeight - 110
|
||||||
};
|
};
|
||||||
resizeElement()
|
resizeElement();
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
resizeElement()
|
resizeElement();
|
||||||
}
|
};
|
||||||
const qualities = [
|
const qualities = [
|
||||||
{name: "标准", value: "standard"},
|
{ name: "标准", value: "standard" },
|
||||||
{name: "高清", value: "hd"},
|
{ name: "高清", value: "hd" }
|
||||||
]
|
];
|
||||||
const sizes = ["1024x1024", "1792x1024", "1024x1792"]
|
const sizes = ["1024x1024", "1792x1024", "1024x1792"];
|
||||||
const styles = [
|
const styles = [
|
||||||
{name: "生动", value: "vivid"},
|
{ name: "生动", value: "vivid" },
|
||||||
{name: "自然", value: "natural"}
|
{ name: "自然", value: "natural" }
|
||||||
]
|
];
|
||||||
const params = ref({
|
const params = ref({
|
||||||
client_id: getClientId(),
|
client_id: getClientId(),
|
||||||
quality: "standard",
|
quality: "standard",
|
||||||
size: "1024x1024",
|
size: "1024x1024",
|
||||||
style: "vivid",
|
style: "vivid",
|
||||||
prompt: ""
|
prompt: ""
|
||||||
})
|
});
|
||||||
|
|
||||||
const finishedJobs = ref([])
|
const finishedJobs = ref([]);
|
||||||
const runningJobs = ref([])
|
const runningJobs = ref([]);
|
||||||
const power = ref(0)
|
const power = ref(0);
|
||||||
const dallPower = ref(0) // 画一张 SD 图片消耗算力
|
const dallPower = ref(0); // 画一张 SD 图片消耗算力
|
||||||
const clipboard = ref(null)
|
const clipboard = ref(null);
|
||||||
const userId = ref(0)
|
const userId = ref(0);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initData()
|
initData();
|
||||||
clipboard.value = new Clipboard('.copy-prompt');
|
clipboard.value = new Clipboard(".copy-prompt");
|
||||||
clipboard.value.on('success', () => {
|
clipboard.value.on("success", () => {
|
||||||
ElMessage.success("复制成功!");
|
ElMessage.success("复制成功!");
|
||||||
})
|
});
|
||||||
|
|
||||||
clipboard.value.on('error', () => {
|
clipboard.value.on("error", () => {
|
||||||
ElMessage.error('复制失败!');
|
ElMessage.error("复制失败!");
|
||||||
})
|
});
|
||||||
|
|
||||||
getSystemInfo().then(res => {
|
getSystemInfo()
|
||||||
dallPower.value = res.data["dall_power"]
|
.then((res) => {
|
||||||
}).catch(e => {
|
dallPower.value = res.data["dall_power"];
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取系统配置失败:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
store.addMessageHandler("dall",(data) => {
|
store.addMessageHandler("dall", (data) => {
|
||||||
// 丢弃无关消息
|
// 丢弃无关消息
|
||||||
if (data.channel !== "dall" || data.clientId !== getClientId()) {
|
if (data.channel !== "dall" || data.clientId !== getClientId()) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.body === "FINISH" || data.body === "FAIL") {
|
if (data.body === "FINISH" || data.body === "FAIL") {
|
||||||
page.value = 0
|
page.value = 0;
|
||||||
isOver.value = false
|
isOver.value = false;
|
||||||
fetchFinishJobs()
|
fetchFinishJobs();
|
||||||
}
|
}
|
||||||
nextTick(() => fetchRunningJobs())
|
nextTick(() => fetchRunningJobs());
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clipboard.value.destroy()
|
clipboard.value.destroy();
|
||||||
store.removeMessageHandler("dall")
|
store.removeMessageHandler("dall");
|
||||||
})
|
});
|
||||||
|
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
checkSession().then(user => {
|
checkSession()
|
||||||
power.value = user['power']
|
.then((user) => {
|
||||||
userId.value = user.id
|
power.value = user["power"];
|
||||||
isLogin.value = true
|
userId.value = user.id;
|
||||||
|
isLogin.value = true;
|
||||||
|
|
||||||
page.value = 0
|
page.value = 0;
|
||||||
fetchRunningJobs()
|
fetchRunningJobs();
|
||||||
fetchFinishJobs()
|
fetchFinishJobs();
|
||||||
}).catch(() => {
|
})
|
||||||
});
|
.catch(() => {});
|
||||||
}
|
};
|
||||||
|
|
||||||
const fetchRunningJobs = () => {
|
const fetchRunningJobs = () => {
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
// 获取运行中的任务
|
// 获取运行中的任务
|
||||||
httpGet(`/api/dall/jobs?finish=false`).then(res => {
|
httpGet(`/api/dall/jobs?finish=false`)
|
||||||
runningJobs.value = res.data.items
|
.then((res) => {
|
||||||
}).catch(e => {
|
runningJobs.value = res.data.items;
|
||||||
ElMessage.error("获取任务失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("获取任务失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const page = ref(1)
|
const page = ref(1);
|
||||||
const pageSize = ref(15)
|
const pageSize = ref(15);
|
||||||
// 获取已完成的任务
|
// 获取已完成的任务
|
||||||
const fetchFinishJobs = () => {
|
const fetchFinishJobs = () => {
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
page.value = page.value + 1
|
page.value = page.value + 1;
|
||||||
|
|
||||||
httpGet(`/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
httpGet(
|
||||||
if (res.data.items.length < pageSize.value) {
|
`/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`
|
||||||
isOver.value = true
|
)
|
||||||
}
|
.then((res) => {
|
||||||
const imageList = res.data.items
|
if (res.data.items.length < pageSize.value) {
|
||||||
for (let i = 0; i < imageList.length; i++) {
|
isOver.value = true;
|
||||||
imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
|
}
|
||||||
}
|
const imageList = res.data.items;
|
||||||
if (page.value === 1) {
|
for (let i = 0; i < imageList.length; i++) {
|
||||||
finishedJobs.value = imageList
|
imageList[i]["img_thumb"] =
|
||||||
} else {
|
imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75";
|
||||||
finishedJobs.value = finishedJobs.value.concat(imageList)
|
}
|
||||||
}
|
if (page.value === 1) {
|
||||||
|
finishedJobs.value = imageList;
|
||||||
loading.value = false
|
} else {
|
||||||
}).catch(e => {
|
finishedJobs.value = finishedJobs.value.concat(imageList);
|
||||||
ElMessage.error("获取任务失败:" + e.message)
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
loading.value = false;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取任务失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 创建绘图任务
|
// 创建绘图任务
|
||||||
const promptRef = ref(null)
|
const promptRef = ref(null);
|
||||||
const generate = () => {
|
const generate = () => {
|
||||||
if (params.value.prompt === '') {
|
if (params.value.prompt === "") {
|
||||||
promptRef.value.focus()
|
promptRef.value.focus();
|
||||||
return ElMessage.error("请输入绘画提示词!")
|
return ElMessage.error("请输入绘画提示词!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
store.setShowLoginDialog(true)
|
store.setShowLoginDialog(true);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
httpPost("/api/dall/image", params.value).then(() => {
|
httpPost("/api/dall/image", params.value)
|
||||||
ElMessage.success("任务执行成功!")
|
.then(() => {
|
||||||
power.value -= dallPower.value
|
ElMessage.success("任务执行成功!");
|
||||||
fetchRunningJobs()
|
power.value -= dallPower.value;
|
||||||
}).catch(e => {
|
fetchRunningJobs();
|
||||||
ElMessage.error("任务执行失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("任务执行失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const removeImage = (item) => {
|
const removeImage = (item) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm("此操作将会删除任务和图片,继续操作码?", "删除提示", {
|
||||||
'此操作将会删除任务和图片,继续操作码?',
|
confirmButtonText: "确认",
|
||||||
'删除提示',
|
cancelButtonText: "取消",
|
||||||
{
|
type: "warning"
|
||||||
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)
|
|
||||||
})
|
|
||||||
}).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) => {
|
const previewImg = (item) => {
|
||||||
previewURL.value = item.img_url
|
previewURL.value = item.img_url;
|
||||||
}
|
};
|
||||||
|
|
||||||
// 发布图片到作品墙
|
// 发布图片到作品墙
|
||||||
const publishImage = (item, action) => {
|
const publishImage = (item, action) => {
|
||||||
let text = "图片发布"
|
let text = "图片发布";
|
||||||
if (action === false) {
|
if (action === false) {
|
||||||
text = "取消发布"
|
text = "取消发布";
|
||||||
}
|
}
|
||||||
httpGet("/api/dall/publish", {id: item.id, action: action}).then(() => {
|
httpGet("/api/dall/publish", { id: item.id, action: action })
|
||||||
ElMessage.success(text + "成功")
|
.then(() => {
|
||||||
item.publish = action
|
ElMessage.success(text + "成功");
|
||||||
page.value = 0
|
item.publish = action;
|
||||||
isOver.value = false
|
page.value = 0;
|
||||||
}).catch(e => {
|
isOver.value = false;
|
||||||
ElMessage.error(text + "失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error(text + "失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const isGenerating = ref(false)
|
const isGenerating = ref(false);
|
||||||
const generatePrompt = () => {
|
const generatePrompt = () => {
|
||||||
if (params.value.prompt === "") {
|
if (params.value.prompt === "") {
|
||||||
return showMessageError("请输入原始提示词")
|
return showMessageError("请输入原始提示词");
|
||||||
}
|
}
|
||||||
isGenerating.value = true
|
isGenerating.value = true;
|
||||||
httpPost("/api/prompt/image", {prompt: params.value.prompt}).then(res => {
|
httpPost("/api/prompt/image", { prompt: params.value.prompt })
|
||||||
params.value.prompt = res.data
|
.then((res) => {
|
||||||
isGenerating.value = false
|
params.value.prompt = res.data;
|
||||||
}).catch(e => {
|
isGenerating.value = false;
|
||||||
showMessageError("生成提示词失败:"+e.message)
|
})
|
||||||
isGenerating.value = false
|
.catch((e) => {
|
||||||
})
|
showMessageError("生成提示词失败:" + e.message);
|
||||||
}
|
isGenerating.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
@ -180,7 +180,14 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- :style="{ 'padding-left': isCollapse ? '65px' : '170px' }" -->
|
||||||
<div class="right-main">
|
<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">
|
<div class="topheader" v-if="loginUser.id === undefined || !loginUser.id">
|
||||||
<el-button
|
<el-button
|
||||||
@click="router.push('/login')"
|
@click="router.push('/login')"
|
||||||
@ -189,6 +196,7 @@
|
|||||||
>登录</el-button
|
>登录</el-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- <div class="content custom-scroll"> -->
|
||||||
<div class="content custom-scroll">
|
<div class="content custom-scroll">
|
||||||
<router-view :key="routerViewKey" v-slot="{ Component }">
|
<router-view :key="routerViewKey" v-slot="{ Component }">
|
||||||
<transition name="move" mode="out-in">
|
<transition name="move" mode="out-in">
|
||||||
@ -196,6 +204,7 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- </div> -->
|
||||||
</div>
|
</div>
|
||||||
<config-dialog
|
<config-dialog
|
||||||
v-if="loginUser.id"
|
v-if="loginUser.id"
|
||||||
@ -203,12 +212,21 @@
|
|||||||
@hide="showConfigDialog = false"
|
@hide="showConfigDialog = false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { CirclePlus, Setting } from "@element-plus/icons-vue";
|
import { CirclePlus, Setting } from "@element-plus/icons-vue";
|
||||||
import ThemeChange from "@/components/ThemeChange.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 { useRouter } from "vue-router";
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import { httpGet } from "@/utils/http";
|
import { httpGet } from "@/utils/http";
|
||||||
@ -226,10 +244,37 @@ const router = useRouter();
|
|||||||
const logo = ref("");
|
const logo = ref("");
|
||||||
const mainNavs = ref([]);
|
const mainNavs = ref([]);
|
||||||
const moreNavs = 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 title = ref("");
|
||||||
|
const showNoticeLogin = ref(false);
|
||||||
// const mainWinHeight = window.innerHeight - 50;
|
// 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 loginUser = ref({});
|
||||||
const mainWinHeight = loginUser.value.id
|
const mainWinHeight = loginUser.value.id
|
||||||
? window.innerHeight
|
? window.innerHeight
|
||||||
@ -252,10 +297,10 @@ watch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// 监听路由变化
|
// 监听路由变化
|
||||||
router.beforeEach((to, from, next) => {
|
// router.beforeEach((to, from, next) => {
|
||||||
curPath.value = to.path;
|
// curPath.value = to.path;
|
||||||
next();
|
// next();
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (curPath.value === "/external") {
|
if (curPath.value === "/external") {
|
||||||
curPath.value = router.currentRoute.value.query.url;
|
curPath.value = router.currentRoute.value.query.url;
|
||||||
@ -302,7 +347,7 @@ onMounted(() => {
|
|||||||
license.value = { de_copy: false };
|
license.value = { de_copy: false };
|
||||||
showMessageError("获取 License 配置:" + e.message);
|
showMessageError("获取 License 配置:" + e.message);
|
||||||
});
|
});
|
||||||
|
curPath.value = "/" + getFirstPathSegment(window.location.href);
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,37 +11,43 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="waterfall" :style="{ height:listBoxHeight + 'px' }" id="waterfall-box">
|
<div
|
||||||
<v3-waterfall v-if="imgType === 'mj'"
|
class="waterfall"
|
||||||
id="waterfall"
|
:style="{ height: listBoxHeight + 'px' }"
|
||||||
:list="data['mj']"
|
id="waterfall-box"
|
||||||
srcKey="img_thumb"
|
>
|
||||||
:gap="12"
|
<v3-waterfall
|
||||||
:bottomGap="-5"
|
v-if="imgType === 'mj'"
|
||||||
:colWidth="colWidth"
|
id="waterfall"
|
||||||
:distanceToScroll="100"
|
:list="data['mj']"
|
||||||
:isLoading="loading"
|
srcKey="img_thumb"
|
||||||
:isOver="isOver"
|
:gap="12"
|
||||||
@scrollReachBottom="getNext">
|
:bottomGap="-5"
|
||||||
|
:colWidth="colWidth"
|
||||||
|
:distanceToScroll="100"
|
||||||
|
:isLoading="loading"
|
||||||
|
:isOver="isOver"
|
||||||
|
@scrollReachBottom="getNext"
|
||||||
|
>
|
||||||
<template #default="slotProp">
|
<template #default="slotProp">
|
||||||
<div class="list-item">
|
<div class="list-item">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<el-image :src="slotProp.item['img_thumb']"
|
<el-image
|
||||||
:zoom-rate="1.2"
|
:src="slotProp.item['img_thumb']"
|
||||||
:preview-src-list="[slotProp.item['img_url']]"
|
:zoom-rate="1.2"
|
||||||
:preview-teleported="true"
|
:preview-src-list="[slotProp.item['img_url']]"
|
||||||
:initial-index="10"
|
:preview-teleported="true"
|
||||||
loading="lazy">
|
:initial-index="10"
|
||||||
|
loading="lazy"
|
||||||
|
>
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="image-slot">
|
<div class="image-slot">正在加载图片</div>
|
||||||
正在加载图片
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Picture/>
|
<Picture />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -49,59 +55,61 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="opt">
|
<div class="opt">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="light"
|
content="复制提示词"
|
||||||
content="复制提示词"
|
placement="top"
|
||||||
placement="top"
|
|
||||||
>
|
>
|
||||||
<el-icon class="copy-prompt-wall" :data-clipboard-text="slotProp.item.prompt">
|
<el-icon
|
||||||
<DocumentCopy/>
|
class="copy-prompt-wall"
|
||||||
|
:data-clipboard-text="slotProp.item.prompt"
|
||||||
|
>
|
||||||
|
<DocumentCopy />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-tooltip
|
<el-tooltip class="box-item" content="画同款" placement="top">
|
||||||
class="box-item"
|
<i
|
||||||
effect="light"
|
class="iconfont icon-palette-pen"
|
||||||
content="画同款"
|
@click="drawSameMj(slotProp.item)"
|
||||||
placement="top"
|
></i>
|
||||||
>
|
|
||||||
<i class="iconfont icon-palette-pen" @click="drawSameMj(slotProp.item)"></i>
|
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</v3-waterfall>
|
</v3-waterfall>
|
||||||
|
|
||||||
<v3-waterfall v-else-if="imgType === 'dall'"
|
<v3-waterfall
|
||||||
id="waterfall"
|
v-else-if="imgType === 'dall'"
|
||||||
:list="data['dall']"
|
id="waterfall"
|
||||||
srcKey="img_thumb"
|
:list="data['dall']"
|
||||||
:gap="12"
|
srcKey="img_thumb"
|
||||||
:bottomGap="-5"
|
:gap="12"
|
||||||
:colWidth="colWidth"
|
:bottomGap="-5"
|
||||||
:distanceToScroll="100"
|
:colWidth="colWidth"
|
||||||
:isLoading="loading"
|
:distanceToScroll="100"
|
||||||
:isOver="isOver"
|
:isLoading="loading"
|
||||||
@scrollReachBottom="getNext">
|
:isOver="isOver"
|
||||||
|
@scrollReachBottom="getNext"
|
||||||
|
>
|
||||||
<template #default="slotProp">
|
<template #default="slotProp">
|
||||||
<div class="list-item">
|
<div class="list-item">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<el-image :src="slotProp.item['img_thumb']"
|
<el-image
|
||||||
:zoom-rate="1.2"
|
:src="slotProp.item['img_thumb']"
|
||||||
:preview-src-list="[slotProp.item['img_url']]"
|
:zoom-rate="1.2"
|
||||||
:preview-teleported="true"
|
:preview-src-list="[slotProp.item['img_url']]"
|
||||||
:initial-index="10"
|
:preview-teleported="true"
|
||||||
loading="lazy">
|
:initial-index="10"
|
||||||
|
loading="lazy"
|
||||||
|
>
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="image-slot">
|
<div class="image-slot">正在加载图片</div>
|
||||||
正在加载图片
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Picture/>
|
<Picture />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -109,13 +117,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="opt">
|
<div class="opt">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="light"
|
content="复制提示词"
|
||||||
content="复制提示词"
|
placement="top"
|
||||||
placement="top"
|
|
||||||
>
|
>
|
||||||
<el-icon class="copy-prompt-wall" :data-clipboard-text="slotProp.item.prompt">
|
<el-icon
|
||||||
<DocumentCopy/>
|
class="copy-prompt-wall"
|
||||||
|
:data-clipboard-text="slotProp.item.prompt"
|
||||||
|
>
|
||||||
|
<DocumentCopy />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
@ -123,32 +133,35 @@
|
|||||||
</template>
|
</template>
|
||||||
</v3-waterfall>
|
</v3-waterfall>
|
||||||
|
|
||||||
<v3-waterfall v-else
|
<v3-waterfall
|
||||||
id="waterfall"
|
v-else
|
||||||
:list="data['sd']"
|
id="waterfall"
|
||||||
srcKey="img_thumb"
|
:list="data['sd']"
|
||||||
:gap="12"
|
srcKey="img_thumb"
|
||||||
:bottomGap="-5"
|
:gap="12"
|
||||||
:colWidth="colWidth"
|
:bottomGap="-5"
|
||||||
:distanceToScroll="100"
|
:colWidth="colWidth"
|
||||||
:isLoading="loading"
|
:distanceToScroll="100"
|
||||||
:isOver="isOver"
|
:isLoading="loading"
|
||||||
@scrollReachBottom="getNext">
|
:isOver="isOver"
|
||||||
|
@scrollReachBottom="getNext"
|
||||||
|
>
|
||||||
<template #default="slotProp">
|
<template #default="slotProp">
|
||||||
<div class="list-item">
|
<div class="list-item">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<el-image :src="slotProp.item['img_thumb']" loading="lazy"
|
<el-image
|
||||||
@click="showTask(slotProp.item)">
|
:src="slotProp.item['img_thumb']"
|
||||||
|
loading="lazy"
|
||||||
|
@click="showTask(slotProp.item)"
|
||||||
|
>
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="image-slot">
|
<div class="image-slot">正在加载图片</div>
|
||||||
正在加载图片
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Picture/>
|
<Picture />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -159,31 +172,36 @@
|
|||||||
</v3-waterfall>
|
</v3-waterfall>
|
||||||
|
|
||||||
<div class="footer" v-if="isOver">
|
<div class="footer" v-if="isOver">
|
||||||
<span>没有更多数据了</span>
|
<el-empty
|
||||||
<i class="iconfont icon-face"></i>
|
:image-size="100"
|
||||||
|
:image="nodata"
|
||||||
|
description="没有更多数据了"
|
||||||
|
/>
|
||||||
|
<!-- <span>没有更多数据了</span>
|
||||||
|
<i class="iconfont icon-face"></i> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
|
<back-top :right="30" :bottom="30" />
|
||||||
|
</div>
|
||||||
</div><!-- end of waterfall -->
|
<!-- end of waterfall -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 任务详情弹框 -->
|
<!-- 任务详情弹框 -->
|
||||||
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
|
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="16">
|
<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">
|
<el-image :src="item['img_url']" fit="contain">
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="image-slot">
|
<div class="image-slot">正在加载图片</div>
|
||||||
正在加载图片
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #error>
|
<template #error>
|
||||||
<div class="image-slot">
|
<div class="image-slot">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Picture/>
|
<Picture />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -193,26 +211,27 @@
|
|||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<div class="task-info">
|
<div class="task-info">
|
||||||
<div class="info-line">
|
<div class="info-line">
|
||||||
<el-divider>
|
<el-divider> 正向提示词 </el-divider>
|
||||||
正向提示词
|
|
||||||
</el-divider>
|
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<span>{{ item.prompt }}</span>
|
<span>{{ item.prompt }}</span>
|
||||||
<el-icon class="copy-prompt-wall" :data-clipboard-text="item.prompt">
|
<el-icon
|
||||||
<DocumentCopy/>
|
class="copy-prompt-wall"
|
||||||
|
:data-clipboard-text="item.prompt"
|
||||||
|
>
|
||||||
|
<DocumentCopy />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-line">
|
<div class="info-line">
|
||||||
<el-divider>
|
<el-divider> 反向提示词 </el-divider>
|
||||||
反向提示词
|
|
||||||
</el-divider>
|
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<span>{{ item.params.negative_prompt }}</span>
|
<span>{{ item.params.negative_prompt }}</span>
|
||||||
<el-icon class="copy-prompt-wall" :data-clipboard-text="item.params.negative_prompt">
|
<el-icon
|
||||||
<DocumentCopy/>
|
class="copy-prompt-wall"
|
||||||
|
:data-clipboard-text="item.params.negative_prompt"
|
||||||
|
>
|
||||||
|
<DocumentCopy />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -227,7 +246,9 @@
|
|||||||
<div class="info-line">
|
<div class="info-line">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<label>图片尺寸:</label>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -253,9 +274,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="item.params.hd_fix">
|
<div v-if="item.params.hd_fix">
|
||||||
<el-divider>
|
<el-divider> 高清修复 </el-divider>
|
||||||
高清修复
|
|
||||||
</el-divider>
|
|
||||||
<div class="info-line">
|
<div class="info-line">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<label>重绘幅度:</label>
|
<label>重绘幅度:</label>
|
||||||
@ -286,146 +305,151 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="copy-params">
|
<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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
|
import nodata from "@/assets/img/no-data.png";
|
||||||
import {DocumentCopy, Picture} from "@element-plus/icons-vue";
|
import { nextTick, onMounted, onUnmounted, ref } from "vue";
|
||||||
import {httpGet} from "@/utils/http";
|
import { DocumentCopy, Picture } from "@element-plus/icons-vue";
|
||||||
import {ElMessage} from "element-plus";
|
import { httpGet } from "@/utils/http";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import {useRouter} from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import BackTop from "@/components/BackTop.vue";
|
import BackTop from "@/components/BackTop.vue";
|
||||||
|
|
||||||
const data = ref({
|
const data = ref({
|
||||||
"mj": [],
|
mj: [],
|
||||||
"sd": [],
|
sd: [],
|
||||||
"dall": [],
|
dall: []
|
||||||
})
|
});
|
||||||
const loading = ref(true)
|
const loading = ref(true);
|
||||||
const isOver = ref(false)
|
const isOver = ref(false);
|
||||||
const imgType = ref("mj") // 图片类别
|
const imgType = ref("mj"); // 图片类别
|
||||||
const listBoxHeight = window.innerHeight - 124
|
const listBoxHeight = window.innerHeight - 124;
|
||||||
const colWidth = ref(220)
|
const colWidth = ref(220);
|
||||||
const fullImgHeight = ref(window.innerHeight - 60)
|
const fullImgHeight = ref(window.innerHeight - 60);
|
||||||
const showTaskDialog = ref(false)
|
const showTaskDialog = ref(false);
|
||||||
const item = ref({})
|
const item = ref({});
|
||||||
|
|
||||||
// 计算瀑布流列宽度
|
// 计算瀑布流列宽度
|
||||||
const calcColWidth = () => {
|
const calcColWidth = () => {
|
||||||
const listBoxWidth = window.innerWidth - 60 - 80
|
const listBoxWidth = window.innerWidth - 60 - 80;
|
||||||
const rows = Math.floor(listBoxWidth / colWidth.value)
|
const rows = Math.floor(listBoxWidth / colWidth.value);
|
||||||
colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows)
|
colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows);
|
||||||
}
|
};
|
||||||
calcColWidth()
|
calcColWidth();
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
calcColWidth()
|
calcColWidth();
|
||||||
}
|
};
|
||||||
|
|
||||||
const page = ref(0)
|
const page = ref(0);
|
||||||
const pageSize = ref(15)
|
const pageSize = ref(15);
|
||||||
// 获取下一页数据
|
// 获取下一页数据
|
||||||
const getNext = () => {
|
const getNext = () => {
|
||||||
if (isOver.value) {
|
if (isOver.value) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
page.value = page.value + 1
|
page.value = page.value + 1;
|
||||||
let url = ""
|
let url = "";
|
||||||
switch (imgType.value) {
|
switch (imgType.value) {
|
||||||
case "mj":
|
case "mj":
|
||||||
url = "/api/mj/imgWall"
|
url = "/api/mj/imgWall";
|
||||||
break
|
break;
|
||||||
case "sd":
|
case "sd":
|
||||||
url = "/api/sd/imgWall"
|
url = "/api/sd/imgWall";
|
||||||
break
|
break;
|
||||||
case "dall":
|
case "dall":
|
||||||
url = "/api/dall/imgWall"
|
url = "/api/dall/imgWall";
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
httpGet(`${url}?page=${page.value}&page_size=${pageSize.value}`).then(res => {
|
httpGet(`${url}?page=${page.value}&page_size=${pageSize.value}`)
|
||||||
loading.value = false
|
.then((res) => {
|
||||||
if (!res.data.items || res.data.items.length === 0) {
|
loading.value = false;
|
||||||
isOver.value = true
|
if (!res.data.items || res.data.items.length === 0) {
|
||||||
return
|
isOver.value = true;
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 生成缩略图
|
// 生成缩略图
|
||||||
const imageList = res.data.items
|
const imageList = res.data.items;
|
||||||
for (let i = 0; i < imageList.length; i++) {
|
for (let i = 0; i < imageList.length; i++) {
|
||||||
imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
|
imageList[i]["img_thumb"] =
|
||||||
}
|
imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75";
|
||||||
if (data.value[imgType.value].length === 0) {
|
}
|
||||||
data.value[imgType.value] = imageList
|
if (data.value[imgType.value].length === 0) {
|
||||||
return
|
data.value[imgType.value] = imageList;
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (imageList.length < pageSize.value) {
|
if (imageList.length < pageSize.value) {
|
||||||
isOver.value = true
|
isOver.value = true;
|
||||||
}
|
}
|
||||||
data.value[imgType.value] = data.value[imgType.value].concat(imageList)
|
data.value[imgType.value] = data.value[imgType.value].concat(imageList);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取图片失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
}).catch(e => {
|
getNext();
|
||||||
ElMessage.error("获取图片失败:" + e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getNext()
|
const clipboard = ref(null);
|
||||||
|
|
||||||
const clipboard = ref(null)
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
clipboard.value = new Clipboard('.copy-prompt-wall');
|
clipboard.value = new Clipboard(".copy-prompt-wall");
|
||||||
clipboard.value.on('success', () => {
|
clipboard.value.on("success", () => {
|
||||||
ElMessage.success("复制成功!");
|
ElMessage.success("复制成功!");
|
||||||
})
|
});
|
||||||
|
|
||||||
clipboard.value.on('error', () => {
|
clipboard.value.on("error", () => {
|
||||||
ElMessage.error('复制失败!');
|
ElMessage.error("复制失败!");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clipboard.value.destroy()
|
clipboard.value.destroy();
|
||||||
})
|
});
|
||||||
|
|
||||||
const changeImgType = () => {
|
const changeImgType = () => {
|
||||||
console.log(imgType.value)
|
console.log(imgType.value);
|
||||||
document.getElementById('waterfall-box').scrollTo(0, 0)
|
document.getElementById("waterfall-box").scrollTo(0, 0);
|
||||||
page.value = 0
|
page.value = 0;
|
||||||
data.value = {
|
data.value = {
|
||||||
"mj": [],
|
mj: [],
|
||||||
"sd": [],
|
sd: [],
|
||||||
"dall": [],
|
dall: []
|
||||||
}
|
};
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
isOver.value = false
|
isOver.value = false;
|
||||||
nextTick(() => getNext())
|
nextTick(() => getNext());
|
||||||
}
|
};
|
||||||
|
|
||||||
const showTask = (row) => {
|
const showTask = (row) => {
|
||||||
item.value = row
|
item.value = row;
|
||||||
showTaskDialog.value = true
|
showTaskDialog.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
const router = useRouter()
|
|
||||||
const drawSameSd = (row) => {
|
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) => {
|
const drawSameMj = (row) => {
|
||||||
router.push({name: "image-mj", params: {prompt: row.prompt}})
|
router.push({ name: "image-mj", params: { prompt: row.prompt } });
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="!license.de_copy"
|
v-if="!license.de_copy"
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="light"
|
|
||||||
content="部署文档"
|
content="部署文档"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
@ -26,7 +25,6 @@
|
|||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="!license.de_copy"
|
v-if="!license.de_copy"
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="light"
|
|
||||||
content="项目源码"
|
content="项目源码"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
>
|
>
|
||||||
|
@ -5,18 +5,25 @@
|
|||||||
<h2>会员推广计划</h2>
|
<h2>会员推广计划</h2>
|
||||||
<div class="share-box">
|
<div class="share-box">
|
||||||
<div class="info">
|
<div class="info">
|
||||||
我们非常欢迎您把此应用分享给您身边的朋友,分享成功注册后您和被邀请人都将获得 <strong>{{ invitePower }}</strong>
|
我们非常欢迎您把此应用分享给您身边的朋友,分享成功注册后您和被邀请人都将获得
|
||||||
|
<strong>{{ invitePower }}</strong>
|
||||||
算力额度作为奖励。
|
算力额度作为奖励。
|
||||||
你可以保存下面的二维码或者直接复制分享您的专属推广链接发送给微信好友。
|
你可以保存下面的二维码或者直接复制分享您的专属推广链接发送给微信好友。
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="invite-qrcode">
|
<div class="invite-qrcode">
|
||||||
<el-image :src="qrImg"/>
|
<el-image :src="qrImg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="invite-url">
|
<div class="invite-url">
|
||||||
<span>{{ inviteURL }}</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -76,10 +83,10 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>您推荐用户</h2>
|
<h2>您推荐的用户</h2>
|
||||||
|
|
||||||
<div class="invite-logs">
|
<div class="invite-logs">
|
||||||
<invite-list v-if="isLogin"/>
|
<invite-list v-if="isLogin" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -87,69 +94,79 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue"
|
import { onMounted, ref } from "vue";
|
||||||
import QRCode from "qrcode";
|
import QRCode from "qrcode";
|
||||||
import {httpGet} from "@/utils/http";
|
import { httpGet } from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import InviteList from "@/components/InviteList.vue";
|
import InviteList from "@/components/InviteList.vue";
|
||||||
import {checkSession, getSystemInfo} from "@/store/cache";
|
import { checkSession, getSystemInfo } from "@/store/cache";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import { useSharedStore } from "@/store/sharedata";
|
||||||
|
|
||||||
const inviteURL = ref("")
|
const inviteURL = ref("");
|
||||||
const qrImg = ref("/images/wx.png")
|
const qrImg = ref("/images/wx.png");
|
||||||
const invitePower = ref(0)
|
const invitePower = ref(0);
|
||||||
const hits = ref(0)
|
const hits = ref(0);
|
||||||
const regNum = ref(0)
|
const regNum = ref(0);
|
||||||
const rate = ref(0)
|
const rate = ref(0);
|
||||||
const isLogin = ref(false)
|
const isLogin = ref(false);
|
||||||
const store = useSharedStore()
|
const store = useSharedStore();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initData()
|
initData();
|
||||||
|
|
||||||
// 复制链接
|
// 复制链接
|
||||||
const clipboard = new Clipboard('.copy-link');
|
const clipboard = new Clipboard(".copy-link");
|
||||||
clipboard.on('success', () => {
|
clipboard.on("success", () => {
|
||||||
ElMessage.success('复制成功!');
|
ElMessage.success("复制成功!");
|
||||||
})
|
});
|
||||||
|
|
||||||
clipboard.on('error', () => {
|
clipboard.on("error", () => {
|
||||||
ElMessage.error('复制失败!');
|
ElMessage.error("复制失败!");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
checkSession().then(() => {
|
checkSession()
|
||||||
isLogin.value = true
|
.then(() => {
|
||||||
httpGet("/api/invite/code").then(res => {
|
isLogin.value = true;
|
||||||
const text = `${location.protocol}//${location.host}/register?invite_code=${res.data.code}`
|
httpGet("/api/invite/code")
|
||||||
hits.value = res.data["hits"]
|
.then((res) => {
|
||||||
regNum.value = res.data["reg_num"]
|
const text = `${location.protocol}//${location.host}/register?invite_code=${res.data.code}`;
|
||||||
if (hits.value > 0) {
|
hits.value = res.data["hits"];
|
||||||
rate.value = ((regNum.value / hits.value) * 100).toFixed(2)
|
regNum.value = res.data["reg_num"];
|
||||||
}
|
if (hits.value > 0) {
|
||||||
QRCode.toDataURL(text, {width: 400, height: 400, margin: 2}, (error, url) => {
|
rate.value = ((regNum.value / hits.value) * 100).toFixed(2);
|
||||||
if (error) {
|
}
|
||||||
console.error(error)
|
QRCode.toDataURL(
|
||||||
} else {
|
text,
|
||||||
qrImg.value = url;
|
{ width: 400, height: 400, margin: 2 },
|
||||||
}
|
(error, url) => {
|
||||||
});
|
if (error) {
|
||||||
inviteURL.value = text
|
console.error(error);
|
||||||
}).catch(e => {
|
} else {
|
||||||
ElMessage.error("获取邀请码失败:" + e.message)
|
qrImg.value = url;
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
inviteURL.value = text;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取邀请码失败:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
getSystemInfo().then(res => {
|
getSystemInfo()
|
||||||
invitePower.value = res.data["invite_power"]
|
.then((res) => {
|
||||||
}).catch(e => {
|
invitePower.value = res.data["invite_power"];
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取系统配置失败:" + e.message);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}).catch(() => {
|
.catch(() => {
|
||||||
store.setShowLoginDialog(true)
|
store.setShowLoginDialog(true);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@ -157,7 +174,7 @@ const initData = () => {
|
|||||||
.page-invitation {
|
.page-invitation {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background-color: #282c34;
|
// background-color: #282c34;
|
||||||
height 100%
|
height 100%
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
overflow-y visible
|
overflow-y visible
|
||||||
@ -167,17 +184,18 @@ const initData = () => {
|
|||||||
flex-flow column
|
flex-flow column
|
||||||
max-width 1000px
|
max-width 1000px
|
||||||
width 100%
|
width 100%
|
||||||
color #e1e1e1
|
color: var(--text-theme-color);
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color #ffffff;
|
color: var(--theme-textcolor-normal);
|
||||||
text-align center
|
text-align center
|
||||||
}
|
}
|
||||||
|
|
||||||
.share-box {
|
.share-box {
|
||||||
.info {
|
.info {
|
||||||
line-height 1.5
|
line-height 1.5
|
||||||
border 1px solid #444444
|
// border 1px solid #444444
|
||||||
|
background:var(--chat-bg)
|
||||||
border-radius 10px
|
border-radius 10px
|
||||||
padding 10px
|
padding 10px
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div class="images">
|
<div class="images">
|
||||||
<template v-for="(img, index) in images" :key="img">
|
<template v-for="(img, index) in images" :key="img">
|
||||||
<div class="item">
|
<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>
|
<el-icon @click="remove(img)"><CircleCloseFilled /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-swap" v-if="images.length === 2 && index === 0">
|
<div class="btn-swap" v-if="images.length === 2 && index === 0">
|
||||||
@ -16,22 +16,23 @@
|
|||||||
<div class="input-container">
|
<div class="input-container">
|
||||||
<div class="upload-icon" v-if="images.length < 2">
|
<div class="upload-icon" v-if="images.length < 2">
|
||||||
<el-upload
|
<el-upload
|
||||||
class="avatar-uploader"
|
class="avatar-uploader"
|
||||||
:auto-upload="true"
|
:auto-upload="true"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
:http-request="upload"
|
:http-request="upload"
|
||||||
accept=".jpg,.png,.jpeg"
|
accept=".jpg,.png,.jpeg"
|
||||||
>
|
>
|
||||||
<i class="iconfont icon-image"></i>
|
<i class="iconfont icon-image"></i>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
class="prompt-input"
|
class="prompt-input"
|
||||||
:rows="row"
|
:rows="row"
|
||||||
v-model="formData.prompt"
|
v-model="formData.prompt"
|
||||||
placeholder="请输入提示词或者上传图片"
|
placeholder="请输入提示词或者上传图片"
|
||||||
autofocus>
|
autofocus
|
||||||
</textarea>
|
>
|
||||||
|
</textarea>
|
||||||
<div class="send-icon" @click="create">
|
<div class="send-icon" @click="create">
|
||||||
<i class="iconfont icon-send"></i>
|
<i class="iconfont icon-send"></i>
|
||||||
</div>
|
</div>
|
||||||
@ -39,26 +40,34 @@
|
|||||||
|
|
||||||
<div class="params">
|
<div class="params">
|
||||||
<div class="item-group">
|
<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>
|
<i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
|
||||||
<span>生成AI视频提示词</span>
|
<span>生成AI视频提示词</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="item-group">
|
<div class="item-group">
|
||||||
<span class="label">循环参考图</span>
|
<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>
|
||||||
<div class="item-group">
|
<div class="item-group">
|
||||||
<span class="label">提示词优化</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<el-container
|
||||||
|
class="video-container"
|
||||||
<el-container class="video-container" v-loading="loading" element-loading-background="rgba(100,100,100,0.3)">
|
v-loading="loading"
|
||||||
|
element-loading-background="rgba(100,100,100,0.3)"
|
||||||
|
>
|
||||||
<h2 class="h-title">你的作品</h2>
|
<h2 class="h-title">你的作品</h2>
|
||||||
|
|
||||||
<div class="list-box" v-if="!noData">
|
<div class="list-box" v-if="!noData">
|
||||||
@ -67,36 +76,63 @@
|
|||||||
<div class="left">
|
<div class="left">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div v-if="item.progress === 100">
|
<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>
|
</video>
|
||||||
<button class="play" @click="play(item)">
|
<button class="play" @click="play(item)">
|
||||||
<img src="/images/play.svg" alt=""/>
|
<img src="/images/play.svg" alt="" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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 />
|
<generating message="正在生成视频" v-else />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<div class="failed" v-if="item.progress === 101">任务执行失败:{{item.err_msg}},任务提示词:{{item.prompt}}</div>
|
<div class="failed" v-if="item.progress === 101">
|
||||||
<div class="prompt" v-else>{{item.prompt}}</div>
|
任务执行失败:{{ item.err_msg }},任务提示词:{{ item.prompt }}
|
||||||
|
</div>
|
||||||
|
<div class="prompt" v-else>{{ item.prompt }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right" v-if="item.progress === 100">
|
<div class="right" v-if="item.progress === 100">
|
||||||
<div class="tools">
|
<div class="tools">
|
||||||
<button class="btn btn-publish">
|
<button class="btn btn-publish">
|
||||||
<span class="text">发布</span>
|
<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>
|
</button>
|
||||||
|
|
||||||
<el-tooltip effect="light" content="下载视频" placement="top">
|
<el-tooltip content="下载视频" placement="top">
|
||||||
<button class="btn btn-icon" @click="download(item)" :disabled="item.downloading">
|
<button
|
||||||
<i class="iconfont icon-download" v-if="!item.downloading"></i>
|
class="btn btn-icon"
|
||||||
<el-image src="/images/loading.gif" class="downloading" fit="cover" v-else />
|
@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>
|
</button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<el-tooltip effect="light" content="删除" placement="top">
|
<el-tooltip content="删除" placement="top">
|
||||||
<button class="btn btn-icon" @click="removeJob(item)">
|
<button class="btn btn-icon" @click="removeJob(item)">
|
||||||
<i class="iconfont icon-remove"></i>
|
<i class="iconfont icon-remove"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -111,210 +147,243 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-empty :image-size="100" description="没有任何作品,赶紧去创作吧!" v-else />
|
<el-empty
|
||||||
|
:image-size="100"
|
||||||
|
:image="nodata"
|
||||||
|
description="没有任何作品,赶紧去创作吧!"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<el-pagination v-if="total > pageSize" background
|
<el-pagination
|
||||||
style="--el-pagination-button-bg-color:#414141;
|
v-if="total > pageSize"
|
||||||
--el-pagination-button-color:#d1d1d1;
|
background
|
||||||
--el-disabled-bg-color:#414141;
|
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||||
--el-color-primary:#666666;
|
|
||||||
--el-pagination-hover-color:#e1e1e1"
|
|
||||||
layout="total,prev, pager, next"
|
layout="total,prev, pager, next"
|
||||||
:hide-on-single-page="true"
|
:hide-on-single-page="true"
|
||||||
v-model:current-page="page"
|
v-model:current-page="page"
|
||||||
v-model:page-size="pageSize"
|
v-model:page-size="pageSize"
|
||||||
@current-change="fetchData(page)"
|
@current-change="fetchData(page)"
|
||||||
:total="total"/>
|
:total="total"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-container>
|
</el-container>
|
||||||
<black-dialog v-model:show="showDialog" title="预览视频" hide-footer @cancal="showDialog = false" width="auto">
|
<black-dialog
|
||||||
<video style="width: 100%; max-height: 90vh;" :src="currentVideoUrl" preload="auto" :autoplay="true" loop="loop" muted="muted" v-show="showDialog">
|
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>
|
</video>
|
||||||
</black-dialog>
|
</black-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, onUnmounted, reactive, ref} from "vue";
|
import nodata from "@/assets/img/no-data.png";
|
||||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
|
|
||||||
import {httpDownload, httpPost, httpGet} from "@/utils/http";
|
import { onMounted, onUnmounted, reactive, ref } from "vue";
|
||||||
import {checkSession, getClientId} from "@/store/cache";
|
import { CircleCloseFilled } from "@element-plus/icons-vue";
|
||||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
|
import { httpDownload, httpPost, httpGet } from "@/utils/http";
|
||||||
import { replaceImg } from "@/utils/libs"
|
import { checkSession, getClientId } from "@/store/cache";
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
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 BlackSwitch from "@/components/ui/BlackSwitch.vue";
|
||||||
import Generating from "@/components/ui/Generating.vue";
|
import Generating from "@/components/ui/Generating.vue";
|
||||||
import BlackDialog from "@/components/ui/BlackDialog.vue";
|
import BlackDialog from "@/components/ui/BlackDialog.vue";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import { useSharedStore } from "@/store/sharedata";
|
||||||
|
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false);
|
||||||
const currentVideoUrl = ref('')
|
const currentVideoUrl = ref("");
|
||||||
const row = ref(1)
|
const row = ref(1);
|
||||||
const images = ref([])
|
const images = ref([]);
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
client_id: getClientId(),
|
client_id: getClientId(),
|
||||||
prompt: '',
|
prompt: "",
|
||||||
expand_prompt: false,
|
expand_prompt: false,
|
||||||
loop: false,
|
loop: false,
|
||||||
first_frame_img: '',
|
first_frame_img: "",
|
||||||
end_frame_img: ''
|
end_frame_img: ""
|
||||||
})
|
});
|
||||||
|
|
||||||
const store = useSharedStore()
|
const store = useSharedStore();
|
||||||
onMounted(()=>{
|
onMounted(() => {
|
||||||
checkSession().then(() => {
|
checkSession().then(() => {
|
||||||
fetchData(1)
|
fetchData(1);
|
||||||
})
|
});
|
||||||
|
|
||||||
store.addMessageHandler("luma",(data) => {
|
store.addMessageHandler("luma", (data) => {
|
||||||
// 丢弃无关消息
|
// 丢弃无关消息
|
||||||
if (data.channel !== "luma" || data.clientId !== getClientId()) {
|
if (data.channel !== "luma" || data.clientId !== getClientId()) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.body === "FINISH" || data.body === "FAIL") {
|
if (data.body === "FINISH" || data.body === "FAIL") {
|
||||||
fetchData(1)
|
fetchData(1);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
store.removeMessageHandler("luma")
|
store.removeMessageHandler("luma");
|
||||||
})
|
});
|
||||||
|
|
||||||
const download = (item) => {
|
const download = (item) => {
|
||||||
const url = replaceImg(item.video_url)
|
const url = replaceImg(item.video_url);
|
||||||
const downloadURL = `${process.env.VUE_APP_API_HOST}/api/download?url=${url}`
|
const downloadURL = `${process.env.VUE_APP_API_HOST}/api/download?url=${url}`;
|
||||||
// parse filename
|
// parse filename
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
const fileName = urlObj.pathname.split('/').pop();
|
const fileName = urlObj.pathname.split("/").pop();
|
||||||
item.downloading = true
|
item.downloading = true;
|
||||||
httpDownload(downloadURL).then(response => {
|
httpDownload(downloadURL)
|
||||||
const blob = new Blob([response.data]);
|
.then((response) => {
|
||||||
const link = document.createElement('a');
|
const blob = new Blob([response.data]);
|
||||||
link.href = URL.createObjectURL(blob);
|
const link = document.createElement("a");
|
||||||
link.download = fileName;
|
link.href = URL.createObjectURL(blob);
|
||||||
document.body.appendChild(link);
|
link.download = fileName;
|
||||||
link.click();
|
document.body.appendChild(link);
|
||||||
document.body.removeChild(link);
|
link.click();
|
||||||
URL.revokeObjectURL(link.href);
|
document.body.removeChild(link);
|
||||||
item.downloading = false
|
URL.revokeObjectURL(link.href);
|
||||||
}).catch(() => {
|
item.downloading = false;
|
||||||
showMessageError("下载失败")
|
})
|
||||||
item.downloading = false
|
.catch(() => {
|
||||||
})
|
showMessageError("下载失败");
|
||||||
}
|
item.downloading = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const play = (item) => {
|
const play = (item) => {
|
||||||
currentVideoUrl.value = replaceImg(item.video_url)
|
currentVideoUrl.value = replaceImg(item.video_url);
|
||||||
showDialog.value = true
|
showDialog.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
const removeJob = (item) => {
|
const removeJob = (item) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm("此操作将会删除任务相关文件,继续操作码?", "删除提示", {
|
||||||
'此操作将会删除任务相关文件,继续操作码?',
|
confirmButtonText: "确认",
|
||||||
'删除提示',
|
cancelButtonText: "取消",
|
||||||
{
|
type: "warning"
|
||||||
confirmButtonText: '确认',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'warning',
|
|
||||||
}
|
|
||||||
).then(() => {
|
|
||||||
httpGet("/api/video/remove", {id: item.id}).then(() => {
|
|
||||||
ElMessage.success("任务删除成功")
|
|
||||||
fetchData()
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error("任务删除失败:" + e.message)
|
|
||||||
})
|
|
||||||
}).catch(() => {
|
|
||||||
})
|
})
|
||||||
}
|
.then(() => {
|
||||||
|
httpGet("/api/video/remove", { id: item.id })
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success("任务删除成功");
|
||||||
|
fetchData();
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("任务删除失败:" + e.message);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
const publishJob = (item) => {
|
const publishJob = (item) => {
|
||||||
httpGet("/api/video/publish", {id: item.id, publish:item.publish}).then(() => {
|
httpGet("/api/video/publish", { id: item.id, publish: item.publish })
|
||||||
ElMessage.success("操作成功")
|
.then(() => {
|
||||||
}).catch(e => {
|
ElMessage.success("操作成功");
|
||||||
ElMessage.error("操作失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("操作失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const upload = (file) => {
|
const upload = (file) => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', file.file, file.name);
|
formData.append("file", file.file, file.name);
|
||||||
// 执行上传操作
|
// 执行上传操作
|
||||||
httpPost('/api/upload', formData).then((res) => {
|
httpPost("/api/upload", formData)
|
||||||
images.value.push(res.data.url)
|
.then((res) => {
|
||||||
ElMessage.success({message: "上传成功", duration: 500})
|
images.value.push(res.data.url);
|
||||||
}).catch((e) => {
|
ElMessage.success({ message: "上传成功", duration: 500 });
|
||||||
ElMessage.error('图片上传失败:' + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("图片上传失败:" + e.message);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const remove = (img) => {
|
const remove = (img) => {
|
||||||
images.value = images.value.filter(item => item !== img)
|
images.value = images.value.filter((item) => item !== img);
|
||||||
}
|
};
|
||||||
|
|
||||||
const switchReverse = () => {
|
const switchReverse = () => {
|
||||||
images.value = images.value.reverse()
|
images.value = images.value.reverse();
|
||||||
}
|
};
|
||||||
const loading = ref(false)
|
const loading = ref(false);
|
||||||
const list = ref([])
|
const list = ref([]);
|
||||||
const noData = ref(true)
|
const noData = ref(true);
|
||||||
const page = ref(1)
|
const page = ref(1);
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10);
|
||||||
const total = ref(0)
|
const total = ref(0);
|
||||||
const fetchData = (_page) => {
|
const fetchData = (_page) => {
|
||||||
if (_page) {
|
if (_page) {
|
||||||
page.value = _page
|
page.value = _page;
|
||||||
}
|
}
|
||||||
httpGet("/api/video/list",{page:page.value, page_size:pageSize.value, type: 'luma'}).then(res => {
|
httpGet("/api/video/list", {
|
||||||
total.value = res.data.total
|
page: page.value,
|
||||||
loading.value = false
|
page_size: pageSize.value,
|
||||||
list.value = res.data.items
|
type: "luma"
|
||||||
noData.value = list.value.length === 0
|
|
||||||
}).catch(() => {
|
|
||||||
loading.value = false
|
|
||||||
noData.value = true
|
|
||||||
})
|
})
|
||||||
}
|
.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 create = () => {
|
||||||
|
const len = images.value.length;
|
||||||
const len = images.value.length;
|
if (len) {
|
||||||
if(len){
|
formData.first_frame_img = images.value[0];
|
||||||
formData.first_frame_img = images.value[0]
|
if (len === 2) {
|
||||||
if(len === 2){
|
formData.end_frame_img = images.value[1];
|
||||||
formData.end_frame_img = images.value[1]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
httpPost("/api/video/luma/create", formData).then(() => {
|
httpPost("/api/video/luma/create", formData)
|
||||||
fetchData(1)
|
.then(() => {
|
||||||
showMessageOK("创建任务成功")
|
fetchData(1);
|
||||||
}).catch(e => {
|
showMessageOK("创建任务成功");
|
||||||
showMessageError("创建任务失败:"+e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
showMessageError("创建任务失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const isGenerating = ref(false)
|
const isGenerating = ref(false);
|
||||||
const generatePrompt = () => {
|
const generatePrompt = () => {
|
||||||
if (formData.prompt === "") {
|
if (formData.prompt === "") {
|
||||||
return showMessageError("请输入原始提示词")
|
return showMessageError("请输入原始提示词");
|
||||||
}
|
}
|
||||||
isGenerating.value = true
|
isGenerating.value = true;
|
||||||
httpPost("/api/prompt/image", {prompt: formData.prompt}).then(res => {
|
httpPost("/api/prompt/image", { prompt: formData.prompt })
|
||||||
formData.prompt = res.data
|
.then((res) => {
|
||||||
isGenerating.value = false
|
formData.prompt = res.data;
|
||||||
}).catch(e => {
|
isGenerating.value = false;
|
||||||
showMessageError("生成提示词失败:"+e.message)
|
})
|
||||||
isGenerating.value = false
|
.catch((e) => {
|
||||||
})
|
showMessageError("生成提示词失败:" + e.message);
|
||||||
}
|
isGenerating.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
@ -7,64 +7,81 @@
|
|||||||
|
|
||||||
<div class="mark-map-params">
|
<div class="mark-map-params">
|
||||||
<el-form label-width="80px" label-position="left">
|
<el-form label-width="80px" label-position="left">
|
||||||
<div class="param-line">
|
<div class="param-line">你的需求?</div>
|
||||||
你的需求?
|
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
<div class="param-line">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="prompt"
|
v-model="prompt"
|
||||||
:autosize="{ minRows: 4, maxRows: 6 }"
|
:autosize="{ minRows: 4, maxRows: 6 }"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
placeholder="请给AI输入提示词,让AI帮你完善"
|
placeholder="请给AI输入提示词,让AI帮你完善"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="param-line">请选择生成思维导图的AI模型</div>
|
||||||
<div class="param-line">
|
<div class="param-line">
|
||||||
请选择生成思维导图的AI模型
|
<el-select
|
||||||
</div>
|
v-model="modelID"
|
||||||
<div class="param-line">
|
placeholder="请选择模型"
|
||||||
<el-select v-model="modelID" placeholder="请选择模型" style="width:100%">
|
style="width: 100%"
|
||||||
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in models"
|
v-for="item in models"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
>
|
>
|
||||||
<span>{{ item.name }}</span>
|
<span>{{ item.name }}</span>
|
||||||
<el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
|
<el-tag
|
||||||
item.power
|
style="margin-left: 5px; position: relative; top: -2px"
|
||||||
}}算力
|
type="info"
|
||||||
|
size="small"
|
||||||
|
>{{ item.power }}算力
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-option>
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-info">
|
<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>
|
||||||
|
|
||||||
<div class="param-line">
|
<div class="submit-btn flex-center">
|
||||||
<el-button color="#47fff1" :dark="false" round @click="generateAI" :loading="loading">
|
<el-button
|
||||||
|
style="width: 200px"
|
||||||
|
type="primary"
|
||||||
|
:dark="false"
|
||||||
|
round
|
||||||
|
@click="generateAI"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
生成思维导图
|
生成思维导图
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-line">
|
<div class="param-line">使用已有内容生成?</div>
|
||||||
使用已有内容生成?
|
|
||||||
</div>
|
|
||||||
<div class="param-line">
|
<div class="param-line">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="content"
|
v-model="content"
|
||||||
:autosize="{ minRows: 4, maxRows: 6 }"
|
:autosize="{ minRows: 4, maxRows: 6 }"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
placeholder="请用markdown语法输入您想要生成思维导图的内容!"
|
placeholder="请用markdown语法输入您想要生成思维导图的内容!"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="param-line">
|
<div class="param-line flex-center">
|
||||||
<el-button color="#C5F9AE" :dark="false" round @click="generate">直接生成(免费)</el-button>
|
<el-button
|
||||||
|
color="rgb(78, 51, 254)"
|
||||||
|
style="width: 200px"
|
||||||
|
:dark="false"
|
||||||
|
round
|
||||||
|
@click="generate"
|
||||||
|
>直接生成(免费)</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -73,188 +90,196 @@
|
|||||||
<div class="top-bar">
|
<div class="top-bar">
|
||||||
<el-button @click="downloadImage" type="primary">
|
<el-button @click="downloadImage" type="primary">
|
||||||
<el-icon>
|
<el-icon>
|
||||||
<Download/>
|
<Download />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<span>下载图片</span>
|
<span>下载图片</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="body" id="markmap">
|
<div class="body" id="markmap">
|
||||||
<svg ref="svgRef" :style="{ height: rightBoxHeight + 'px' }"/>
|
<svg ref="svgRef" :style="{ height: rightBoxHeight + 'px' }" />
|
||||||
<div id="toolbar"></div>
|
<div id="toolbar"></div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end task list box -->
|
</div>
|
||||||
|
<!-- end task list box -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {nextTick, ref} from 'vue';
|
import { nextTick, ref } from "vue";
|
||||||
import {Markmap} from 'markmap-view';
|
import { Markmap } from "markmap-view";
|
||||||
import {Transformer} from 'markmap-lib';
|
import { Transformer } from "markmap-lib";
|
||||||
import {checkSession, getSystemInfo} from "@/store/cache";
|
import { checkSession, getSystemInfo } from "@/store/cache";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import { httpGet, httpPost } from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import {Download} from "@element-plus/icons-vue";
|
import { Download } from "@element-plus/icons-vue";
|
||||||
import {Toolbar} from 'markmap-toolbar';
|
import { Toolbar } from "markmap-toolbar";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import { useSharedStore } from "@/store/sharedata";
|
||||||
|
|
||||||
const leftBoxHeight = ref(window.innerHeight - 105)
|
const leftBoxHeight = ref(window.innerHeight - 105);
|
||||||
const rightBoxHeight = ref(window.innerHeight - 115)
|
const rightBoxHeight = ref(window.innerHeight - 115);
|
||||||
|
|
||||||
const prompt = ref("")
|
const prompt = ref("");
|
||||||
const text = ref("")
|
const text = ref("");
|
||||||
const content = ref(text.value)
|
const content = ref(text.value);
|
||||||
const html = ref("")
|
const html = ref("");
|
||||||
|
|
||||||
const isLogin = ref(false)
|
const isLogin = ref(false);
|
||||||
const loginUser = ref({power: 0})
|
const loginUser = ref({ power: 0 });
|
||||||
const transformer = new Transformer();
|
const transformer = new Transformer();
|
||||||
const store = useSharedStore();
|
const store = useSharedStore();
|
||||||
const loading = ref(false)
|
const loading = ref(false);
|
||||||
|
|
||||||
const svgRef = ref(null)
|
const svgRef = ref(null);
|
||||||
const markMap = ref(null)
|
const markMap = ref(null);
|
||||||
const models = ref([])
|
const models = ref([]);
|
||||||
const modelID = ref(0)
|
const modelID = ref(0);
|
||||||
|
|
||||||
getSystemInfo().then(res => {
|
getSystemInfo()
|
||||||
text.value = res.data['mark_map_text']
|
.then((res) => {
|
||||||
content.value = text.value
|
text.value = res.data["mark_map_text"];
|
||||||
initData()
|
content.value = text.value;
|
||||||
nextTick(() => {
|
initData();
|
||||||
try {
|
nextTick(() => {
|
||||||
markMap.value = Markmap.create(svgRef.value)
|
try {
|
||||||
const {el} = Toolbar.create(markMap.value);
|
markMap.value = Markmap.create(svgRef.value);
|
||||||
document.getElementById('toolbar').append(el);
|
const { el } = Toolbar.create(markMap.value);
|
||||||
update()
|
document.getElementById("toolbar").append(el);
|
||||||
} catch (e) {
|
update();
|
||||||
console.error(e)
|
} catch (e) {
|
||||||
}
|
console.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}).catch(e => {
|
.catch((e) => {
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
ElMessage.error("获取系统配置失败:" + e.message);
|
||||||
})
|
});
|
||||||
|
|
||||||
const initData = () => {
|
const initData = () => {
|
||||||
httpGet("/api/model/list").then(res => {
|
httpGet("/api/model/list")
|
||||||
for (let v of res.data) {
|
.then((res) => {
|
||||||
models.value.push(v)
|
for (let v of res.data) {
|
||||||
}
|
models.value.push(v);
|
||||||
modelID.value = models.value[0].id
|
}
|
||||||
}).catch(e => {
|
modelID.value = models.value[0].id;
|
||||||
ElMessage.error("获取模型失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取模型失败:" + e.message);
|
||||||
checkSession().then(user => {
|
});
|
||||||
loginUser.value = user
|
|
||||||
isLogin.value = true
|
checkSession()
|
||||||
}).catch(() => {
|
.then((user) => {
|
||||||
});
|
loginUser.value = user;
|
||||||
}
|
isLogin.value = true;
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
};
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
try {
|
try {
|
||||||
const {root} = transformer.transform(processContent(text.value))
|
const { root } = transformer.transform(processContent(text.value));
|
||||||
markMap.value.setData(root)
|
markMap.value.setData(root);
|
||||||
markMap.value.fit()
|
markMap.value.fit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const processContent = (text) => {
|
const processContent = (text) => {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return text
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
const arr = []
|
const arr = [];
|
||||||
const lines = text.split("\n")
|
const lines = text.split("\n");
|
||||||
for (let line of lines) {
|
for (let line of lines) {
|
||||||
if (line.indexOf("```") !== -1) {
|
if (line.indexOf("```") !== -1) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
line = line.replace(/([*_~`>])|(\d+\.)\s/g, '')
|
line = line.replace(/([*_~`>])|(\d+\.)\s/g, "");
|
||||||
arr.push(line)
|
arr.push(line);
|
||||||
}
|
}
|
||||||
return arr.join("\n")
|
return arr.join("\n");
|
||||||
}
|
};
|
||||||
|
|
||||||
window.onresize = () => {
|
window.onresize = () => {
|
||||||
leftBoxHeight.value = window.innerHeight - 145
|
leftBoxHeight.value = window.innerHeight - 145;
|
||||||
rightBoxHeight.value = window.innerHeight - 85
|
rightBoxHeight.value = window.innerHeight - 85;
|
||||||
}
|
};
|
||||||
|
|
||||||
const generate = () => {
|
const generate = () => {
|
||||||
text.value = content.value
|
text.value = content.value;
|
||||||
update()
|
update();
|
||||||
}
|
};
|
||||||
|
|
||||||
// 使用 AI 智能生成
|
// 使用 AI 智能生成
|
||||||
const generateAI = () => {
|
const generateAI = () => {
|
||||||
html.value = ''
|
html.value = "";
|
||||||
text.value = ''
|
text.value = "";
|
||||||
if (prompt.value === '') {
|
if (prompt.value === "") {
|
||||||
return ElMessage.error("请输入你的需求")
|
return ElMessage.error("请输入你的需求");
|
||||||
}
|
}
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
store.setShowLoginDialog(true)
|
store.setShowLoginDialog(true);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
httpPost("/api/markMap/gen", {
|
httpPost("/api/markMap/gen", {
|
||||||
prompt:prompt.value,
|
prompt: prompt.value,
|
||||||
model_id: modelID.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) => {
|
const getModelById = (modelId) => {
|
||||||
for (let m of models.value) {
|
for (let m of models.value) {
|
||||||
if (m.id === modelId) {
|
if (m.id === modelId) {
|
||||||
return m
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// download SVG to png file
|
// download SVG to png file
|
||||||
const downloadImage = () => {
|
const downloadImage = () => {
|
||||||
const svgElement = document.getElementById("markmap");
|
const svgElement = document.getElementById("markmap");
|
||||||
// 将 SVG 渲染到图片对象
|
// 将 SVG 渲染到图片对象
|
||||||
const serializer = new XMLSerializer()
|
const serializer = new XMLSerializer();
|
||||||
const source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(svgRef.value)
|
const source =
|
||||||
const image = new Image()
|
'<?xml version="1.0" standalone="no"?>\r\n' +
|
||||||
image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source)
|
serializer.serializeToString(svgRef.value);
|
||||||
|
const image = new Image();
|
||||||
|
image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
|
||||||
|
|
||||||
// 将图片对象渲染
|
// 将图片对象渲染
|
||||||
const canvas = document.createElement('canvas')
|
const canvas = document.createElement("canvas");
|
||||||
canvas.width = svgElement.offsetWidth
|
canvas.width = svgElement.offsetWidth;
|
||||||
canvas.height = svgElement.offsetHeight
|
canvas.height = svgElement.offsetHeight;
|
||||||
let context = canvas.getContext('2d')
|
let context = canvas.getContext("2d");
|
||||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
context.fillStyle = 'white';
|
context.fillStyle = "white";
|
||||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
image.onload = function () {
|
image.onload = function () {
|
||||||
context.drawImage(image, 0, 0)
|
context.drawImage(image, 0, 0);
|
||||||
const a = document.createElement('a')
|
const a = document.createElement("a");
|
||||||
a.download = "geek-ai-xmind.png"
|
a.download = "geek-ai-xmind.png";
|
||||||
a.href = canvas.toDataURL(`image/png`)
|
a.href = canvas.toDataURL(`image/png`);
|
||||||
a.click()
|
a.click();
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
@ -1,25 +1,39 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<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="inner">
|
||||||
<div class="user-profile">
|
<div class="user-profile">
|
||||||
<user-profile :key="profileKey"/>
|
<user-profile :key="profileKey" />
|
||||||
|
|
||||||
<el-row class="user-opt" :gutter="20">
|
<el-row class="user-opt" :gutter="20">
|
||||||
<el-col :span="12">
|
<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>
|
||||||
<el-col :span="12">
|
<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>
|
||||||
<el-col :span="12">
|
<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>
|
||||||
<el-col :span="12">
|
<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>
|
||||||
<el-col :span="24">
|
<el-col :span="24">
|
||||||
<el-button type="primary" @click="showRedeemVerifyDialog = true">卡密兑换
|
<el-button type="primary" @click="showRedeemVerifyDialog = true"
|
||||||
|
>卡密兑换
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -36,7 +50,7 @@
|
|||||||
<el-col v-for="item in list" :key="item" :span="6">
|
<el-col v-for="item in list" :key="item" :span="6">
|
||||||
<div class="product-item">
|
<div class="product-item">
|
||||||
<div class="image-container">
|
<div class="image-container">
|
||||||
<el-image :src="vipImg" fit="cover"/>
|
<el-image :src="vipImg" fit="cover" />
|
||||||
</div>
|
</div>
|
||||||
<div class="product-title">
|
<div class="product-title">
|
||||||
<span class="name">{{ item.name }}</span>
|
<span class="name">{{ item.name }}</span>
|
||||||
@ -44,7 +58,9 @@
|
|||||||
<div class="product-info">
|
<div class="product-info">
|
||||||
<div class="info-line">
|
<div class="info-line">
|
||||||
<span class="label">商品原价:</span>
|
<span class="label">商品原价:</span>
|
||||||
<span class="price"><del>¥{{ item.price }}</del></span>
|
<span class="price"
|
||||||
|
><del>¥{{ item.price }}</del></span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-line">
|
<div class="info-line">
|
||||||
<span class="label">优惠价:</span>
|
<span class="label">优惠价:</span>
|
||||||
@ -52,7 +68,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="info-line">
|
<div class="info-line">
|
||||||
<span class="label">有效期:</span>
|
<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>
|
<span class="expire" v-else>长期有效</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -62,22 +80,42 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="pay-way">
|
<div class="pay-way">
|
||||||
|
<span
|
||||||
<span type="primary" v-for="payWay in payWays" @click="pay(item,payWay)" :key="payWay">
|
type="primary"
|
||||||
<el-button v-if="payWay.pay_type==='alipay'" color="#15A6E8" circle>
|
v-for="payWay in payWays"
|
||||||
<i class="iconfont icon-alipay" ></i>
|
@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>
|
||||||
<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>
|
<i class="iconfont icon-qq"></i>
|
||||||
</el-button>
|
</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>
|
<i class="iconfont icon-paypal"></i>
|
||||||
</el-button>
|
</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>
|
<i class="iconfont icon-jd-pay"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else-if="payWay.pay_type==='douyin'" class="douyin" circle>
|
<el-button
|
||||||
<i class="iconfont icon-douyin"></i>
|
v-else-if="payWay.pay_type === 'douyin'"
|
||||||
|
class="douyin"
|
||||||
|
circle
|
||||||
|
>
|
||||||
|
<i class="iconfont icon-douyin"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-else circle class="wechat" color="#67C23A">
|
<el-button v-else circle class="wechat" color="#67C23A">
|
||||||
<i class="iconfont icon-wechat-pay"></i>
|
<i class="iconfont icon-wechat-pay"></i>
|
||||||
@ -93,116 +131,155 @@
|
|||||||
<h2 class="headline">消费账单</h2>
|
<h2 class="headline">消费账单</h2>
|
||||||
|
|
||||||
<div class="user-order">
|
<div class="user-order">
|
||||||
<user-order v-if="isLogin" :key="userOrderKey"/>
|
<user-order v-if="isLogin" :key="userOrderKey" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false"/>
|
<password-dialog
|
||||||
<bind-mobile v-if="isLogin" :show="showBindMobileDialog" @hide="showBindMobileDialog = false"/>
|
v-if="isLogin"
|
||||||
<bind-email v-if="isLogin" :show="showBindEmailDialog" @hide="showBindEmailDialog = false"/>
|
:show="showPasswordDialog"
|
||||||
<third-login v-if="isLogin" :show="showThirdLoginDialog" @hide="showThirdLoginDialog = false"/>
|
@hide="showPasswordDialog = false"
|
||||||
<redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/>
|
/>
|
||||||
|
<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>
|
</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 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" />
|
<el-image :src="qrImg" fit="cover" />
|
||||||
</div>
|
</div>
|
||||||
<div style="padding-bottom: 10px; text-align: center">
|
<div style="padding-bottom: 10px; text-align: center">
|
||||||
<el-button type="success" @click="payCallback(true)">支付成功</el-button>
|
<el-button type="success" @click="payCallback(true)"
|
||||||
<el-button type="danger" @click="payCallback(false)">支付失败</el-button>
|
>支付成功</el-button
|
||||||
|
>
|
||||||
|
<el-button type="danger" @click="payCallback(false)"
|
||||||
|
>支付失败</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue"
|
import { onMounted, ref } from "vue";
|
||||||
import {ElMessage} from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import { httpGet, httpPost } from "@/utils/http";
|
||||||
import {checkSession, getSystemInfo} from "@/store/cache";
|
import { checkSession, getSystemInfo } from "@/store/cache";
|
||||||
import UserProfile from "@/components/UserProfile.vue";
|
import UserProfile from "@/components/UserProfile.vue";
|
||||||
import PasswordDialog from "@/components/PasswordDialog.vue";
|
import PasswordDialog from "@/components/PasswordDialog.vue";
|
||||||
import BindMobile from "@/components/BindMobile.vue";
|
import BindMobile from "@/components/BindMobile.vue";
|
||||||
import RedeemVerify from "@/components/RedeemVerify.vue";
|
import RedeemVerify from "@/components/RedeemVerify.vue";
|
||||||
import UserOrder from "@/components/UserOrder.vue";
|
import UserOrder from "@/components/UserOrder.vue";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import { useSharedStore } from "@/store/sharedata";
|
||||||
import BindEmail from "@/components/BindEmail.vue";
|
import BindEmail from "@/components/BindEmail.vue";
|
||||||
import ThirdLogin from "@/components/ThirdLogin.vue";
|
import ThirdLogin from "@/components/ThirdLogin.vue";
|
||||||
import QRCode from "qrcode";
|
import QRCode from "qrcode";
|
||||||
|
|
||||||
const list = ref([])
|
const list = ref([]);
|
||||||
const vipImg = ref("/images/vip.png")
|
const vipImg = ref("/images/menu/member.png");
|
||||||
const enableReward = ref(false) // 是否启用众筹功能
|
const enableReward = ref(false); // 是否启用众筹功能
|
||||||
const rewardImg = ref('/images/reward.png')
|
const rewardImg = ref("/images/reward.png");
|
||||||
const showPasswordDialog = ref(false)
|
const showPasswordDialog = ref(false);
|
||||||
const showBindMobileDialog = ref(false)
|
const showBindMobileDialog = ref(false);
|
||||||
const showBindEmailDialog = ref(false)
|
const showBindEmailDialog = ref(false);
|
||||||
const showRedeemVerifyDialog = ref(false)
|
const showRedeemVerifyDialog = ref(false);
|
||||||
const showThirdLoginDialog = ref(false)
|
const showThirdLoginDialog = ref(false);
|
||||||
const user = ref(null)
|
const user = ref(null);
|
||||||
const isLogin = ref(false)
|
const isLogin = ref(false);
|
||||||
const orderTimeout = ref(1800)
|
const orderTimeout = ref(1800);
|
||||||
const loading = ref(true)
|
const loading = ref(true);
|
||||||
const loadingText = ref("加载中...")
|
const loadingText = ref("加载中...");
|
||||||
const orderPayInfoText = 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 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(() => {
|
onMounted(() => {
|
||||||
checkSession().then(_user => {
|
checkSession()
|
||||||
user.value = _user
|
.then((_user) => {
|
||||||
isLogin.value = true
|
user.value = _user;
|
||||||
}).catch(() => {
|
isLogin.value = true;
|
||||||
store.setShowLoginDialog(true)
|
})
|
||||||
})
|
.catch(() => {
|
||||||
|
store.setShowLoginDialog(true);
|
||||||
|
});
|
||||||
|
|
||||||
httpGet("/api/product/list").then((res) => {
|
httpGet("/api/product/list")
|
||||||
list.value = res.data
|
.then((res) => {
|
||||||
loading.value = false
|
list.value = res.data;
|
||||||
}).catch(e => {
|
loading.value = false;
|
||||||
ElMessage.error("获取产品套餐失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取产品套餐失败:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
getSystemInfo().then(res => {
|
getSystemInfo()
|
||||||
rewardImg.value = res.data['reward_img']
|
.then((res) => {
|
||||||
enableReward.value = res.data['enabled_reward']
|
rewardImg.value = res.data["reward_img"];
|
||||||
orderPayInfoText.value = res.data['order_pay_info_text']
|
enableReward.value = res.data["enabled_reward"];
|
||||||
if (res.data['order_pay_timeout'] > 0) {
|
orderPayInfoText.value = res.data["order_pay_info_text"];
|
||||||
orderTimeout.value = res.data['order_pay_timeout']
|
if (res.data["order_pay_timeout"] > 0) {
|
||||||
}
|
orderTimeout.value = res.data["order_pay_timeout"];
|
||||||
vipInfoText.value = res.data['vip_info_text']
|
}
|
||||||
}).catch(e => {
|
vipInfoText.value = res.data["vip_info_text"];
|
||||||
ElMessage.error("获取系统配置失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("获取系统配置失败:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
httpGet("/api/payment/payWays").then(res => {
|
httpGet("/api/payment/payWays")
|
||||||
payWays.value = res.data
|
.then((res) => {
|
||||||
}).catch(e => {
|
payWays.value = res.data;
|
||||||
ElMessage.error("获取支付方式失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
})
|
ElMessage.error("获取支付方式失败:" + e.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const pay = (product, payWay) => {
|
const pay = (product, payWay) => {
|
||||||
if (!isLogin.value) {
|
if (!isLogin.value) {
|
||||||
store.setShowLoginDialog(true)
|
store.setShowLoginDialog(true);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
loadingText.value = "正在生成支付订单..."
|
loadingText.value = "正在生成支付订单...";
|
||||||
let host = process.env.VUE_APP_API_HOST
|
let host = process.env.VUE_APP_API_HOST;
|
||||||
if (host === '') {
|
if (host === "") {
|
||||||
host = `${location.protocol}//${location.host}`;
|
host = `${location.protocol}//${location.host}`;
|
||||||
}
|
}
|
||||||
httpPost(`${process.env.VUE_APP_API_HOST}/api/payment/doPay`, {
|
httpPost(`${process.env.VUE_APP_API_HOST}/api/payment/doPay`, {
|
||||||
@ -212,45 +289,49 @@ const pay = (product, payWay) => {
|
|||||||
user_id: user.value.id,
|
user_id: user.value.id,
|
||||||
host: host,
|
host: host,
|
||||||
device: "jump"
|
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) => {
|
|
||||||
if (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)
|
|
||||||
})
|
})
|
||||||
}
|
.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);
|
||||||
|
} else {
|
||||||
|
qrImg.value = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
window.open(res.data, "_blank");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
ElMessage.error("生成支付订单失败:" + e.message);
|
||||||
|
loading.value = false;
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const redeemCallback = (success) => {
|
const redeemCallback = (success) => {
|
||||||
showRedeemVerifyDialog.value = false
|
showRedeemVerifyDialog.value = false;
|
||||||
if (success) {
|
if (success) {
|
||||||
profileKey.value += 1
|
profileKey.value += 1;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const payCallback = (success) => {
|
const payCallback = (success) => {
|
||||||
showDialog.value = false
|
showDialog.value = false;
|
||||||
if (success) {
|
if (success) {
|
||||||
profileKey.value += 1
|
profileKey.value += 1;
|
||||||
userOrderKey.value += 1
|
userOrderKey.value += 1;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus">
|
<style lang="stylus">
|
||||||
|
@ -1,127 +1,160 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="power-log custom-scroll" v-loading="loading">
|
<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="list-box">
|
||||||
<div class="handle-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
|
<el-date-picker
|
||||||
v-model="query.date"
|
v-model="query.date"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-placeholder="结束日期"
|
end-placeholder="结束日期"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-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>
|
</div>
|
||||||
|
|
||||||
<el-row v-if="items.length > 0">
|
<el-row v-if="items.length > 0">
|
||||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto" border
|
<el-table
|
||||||
style="--el-table-border-color:#373C47;
|
:data="items"
|
||||||
--el-table-tr-bg-color:#2D323B;
|
:row-key="(row) => row.id"
|
||||||
--el-table-row-hover-bg-color:#373C47;
|
table-layout="auto"
|
||||||
--el-table-header-bg-color:#474E5C;
|
border
|
||||||
--el-table-text-color:#d1d1d1">
|
>
|
||||||
<el-table-column prop="username" label="用户" width="130px"/>
|
<el-table-column prop="username" label="用户" width="130px" />
|
||||||
<el-table-column prop="model" label="模型" width="130px"/>
|
<el-table-column prop="model" label="模型" width="130px" />
|
||||||
<el-table-column prop="type" label="类型">
|
<el-table-column prop="type" label="类型">
|
||||||
<template #default="scope">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="数额">
|
<el-table-column label="数额">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div>
|
<div>
|
||||||
<el-text type="success" v-if="scope.row.mark === 1">+{{ scope.row.amount }}</el-text>
|
<el-text type="success" v-if="scope.row.mark === 1"
|
||||||
<el-text type="danger" v-if="scope.row.mark === 0">-{{ scope.row.amount }}</el-text>
|
>+{{ scope.row.amount }}</el-text
|
||||||
|
>
|
||||||
|
<el-text type="danger" v-if="scope.row.mark === 0"
|
||||||
|
>-{{ scope.row.amount }}</el-text
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="balance" label="余额"/>
|
<el-table-column prop="balance" label="余额" />
|
||||||
<el-table-column label="发生时间" width="160px">
|
<el-table-column label="发生时间" width="160px">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="remark" label="备注"/>
|
<el-table-column prop="remark" label="备注" />
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<el-pagination v-if="total > 0" background
|
<el-pagination
|
||||||
layout="total,prev, pager, next"
|
v-if="total > 0"
|
||||||
:hide-on-single-page="true"
|
background
|
||||||
v-model:current-page="page"
|
layout="total,prev, pager, next"
|
||||||
v-model:page-size="pageSize"
|
style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
|
||||||
@current-change="fetchData()"
|
:hide-on-single-page="true"
|
||||||
:total="total"/>
|
v-model:current-page="page"
|
||||||
|
v-model:page-size="pageSize"
|
||||||
|
@current-change="fetchData()"
|
||||||
|
:total="total"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-empty :image-size="100" v-else/>
|
<el-empty
|
||||||
|
:image-size="100"
|
||||||
|
v-else
|
||||||
|
:image="nodata"
|
||||||
|
description="暂无数据"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue"
|
import nodata from "@/assets/img/no-data.png";
|
||||||
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([])
|
import { onMounted, ref } from "vue";
|
||||||
const total = ref(0)
|
import { dateFormat } from "@/utils/libs";
|
||||||
const page = ref(1)
|
import { Search } from "@element-plus/icons-vue";
|
||||||
const pageSize = ref(20)
|
import Clipboard from "clipboard";
|
||||||
const loading = ref(false)
|
import { ElMessage } from "element-plus";
|
||||||
const listBoxHeight = window.innerHeight - 87
|
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({
|
const query = ref({
|
||||||
model: "",
|
model: "",
|
||||||
date: []
|
date: []
|
||||||
})
|
});
|
||||||
const tagColors = ref(["primary", "success", "primary", "danger", "info", "warning"])
|
const tagColors = ref([
|
||||||
|
"primary",
|
||||||
|
"success",
|
||||||
|
"primary",
|
||||||
|
"danger",
|
||||||
|
"info",
|
||||||
|
"warning"
|
||||||
|
]);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkSession().then(() => {
|
checkSession()
|
||||||
fetchData()
|
.then(() => {
|
||||||
}).catch(() => {
|
fetchData();
|
||||||
})
|
})
|
||||||
const clipboard = new Clipboard('.copy-order-no');
|
.catch(() => {});
|
||||||
clipboard.on('success', () => {
|
const clipboard = new Clipboard(".copy-order-no");
|
||||||
|
clipboard.on("success", () => {
|
||||||
ElMessage.success("复制成功!");
|
ElMessage.success("复制成功!");
|
||||||
})
|
});
|
||||||
|
|
||||||
clipboard.on('error', () => {
|
clipboard.on("error", () => {
|
||||||
ElMessage.error('复制失败!');
|
ElMessage.error("复制失败!");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const fetchData = () => {
|
const fetchData = () => {
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
httpPost('/api/powerLog/list', {
|
httpPost("/api/powerLog/list", {
|
||||||
model: query.value.model,
|
model: query.value.model,
|
||||||
date: query.value.date,
|
date: query.value.date,
|
||||||
page: page.value,
|
page: page.value,
|
||||||
page_size: pageSize.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>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@ -135,7 +168,10 @@ const fetchData = () => {
|
|||||||
.list-box {
|
.list-box {
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
//overflow-y auto
|
//overflow-y auto
|
||||||
|
background: var(--chat-bg);
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
.handle-box {
|
.handle-box {
|
||||||
padding 20px 0
|
padding 20px 0
|
||||||
|
|
||||||
|
@ -1,93 +1,104 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-song" :style="{ height: winHeight + 'px' }">
|
<div class="page-song" :style="{ height: winHeight + 'px' }">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<h2 class="title">{{song.title}}</h2>
|
<h2 class="title">{{ song.title }}</h2>
|
||||||
<div class="row tags" v-if="song.tags">
|
<div class="row tags" v-if="song.tags">
|
||||||
<span>{{song.tags}}</span>
|
<span>{{ song.tags }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row author">
|
<div class="row author">
|
||||||
<span>
|
<span>
|
||||||
<el-avatar :size="32" :src="song.user?.avatar" />
|
<el-avatar :size="32" :src="song.user?.avatar" />
|
||||||
</span>
|
</span>
|
||||||
<span class="nickname">{{song.user?.nickname}}</span>
|
<span class="nickname">{{ song.user?.nickname }}</span>
|
||||||
<button class="btn btn-icon" @click="play">
|
<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>
|
</button>
|
||||||
|
|
||||||
<el-tooltip effect="light" content="复制歌曲链接" placement="top">
|
<el-tooltip content="复制歌曲链接" placement="top">
|
||||||
<button class="btn btn-icon copy-link" :data-clipboard-text="getShareURL(song)" >
|
<button
|
||||||
|
class="btn btn-icon copy-link"
|
||||||
|
:data-clipboard-text="getShareURL(song)"
|
||||||
|
>
|
||||||
<i class="iconfont icon-share1"></i>
|
<i class="iconfont icon-share1"></i>
|
||||||
</button>
|
</button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row date">
|
<div class="row date">
|
||||||
<span>{{dateFormat(song.created_at)}}</span>
|
<span>{{ dateFormat(song.created_at) }}</span>
|
||||||
<span class="version">{{song.raw_data?.major_model_version}}</span>
|
<span class="version">{{ song.raw_data?.major_model_version }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="music-player" v-if="playList.length > 0">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, onUnmounted, ref} from "vue"
|
import { onMounted, onUnmounted, ref } from "vue";
|
||||||
import {useRouter} from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import {httpGet} from "@/utils/http";
|
import { httpGet } from "@/utils/http";
|
||||||
import {showMessageError} from "@/utils/dialog";
|
import { showMessageError } from "@/utils/dialog";
|
||||||
import {dateFormat} from "@/utils/libs";
|
import { dateFormat } from "@/utils/libs";
|
||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import {ElMessage} from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import MusicPlayer from "@/components/MusicPlayer.vue";
|
import MusicPlayer from "@/components/MusicPlayer.vue";
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const id = router.currentRoute.value.params.id
|
const id = router.currentRoute.value.params.id;
|
||||||
const song = ref({title:""})
|
const song = ref({ title: "" });
|
||||||
const playList = ref([])
|
const playList = ref([]);
|
||||||
const playerRef = ref(null)
|
const playerRef = ref(null);
|
||||||
|
|
||||||
httpGet("/api/suno/detail",{song_id:id}).then(res => {
|
httpGet("/api/suno/detail", { song_id: id })
|
||||||
song.value = res.data
|
.then((res) => {
|
||||||
playList.value = [song.value]
|
song.value = res.data;
|
||||||
document.title = song.value?.title+ " | By "+song.value?.user.nickname+" | Suno音乐"
|
playList.value = [song.value];
|
||||||
}).catch(e => {
|
document.title =
|
||||||
showMessageError("获取歌曲详情失败:"+e.message)
|
song.value?.title + " | By " + song.value?.user.nickname + " | Suno音乐";
|
||||||
})
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
showMessageError("获取歌曲详情失败:" + e.message);
|
||||||
|
});
|
||||||
|
|
||||||
const clipboard = ref(null)
|
const clipboard = ref(null);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
clipboard.value = new Clipboard('.copy-link');
|
clipboard.value = new Clipboard(".copy-link");
|
||||||
clipboard.value.on('success', () => {
|
clipboard.value.on("success", () => {
|
||||||
ElMessage.success("复制歌曲链接成功!");
|
ElMessage.success("复制歌曲链接成功!");
|
||||||
})
|
});
|
||||||
|
|
||||||
clipboard.value.on('error', () => {
|
clipboard.value.on("error", () => {
|
||||||
ElMessage.error('复制失败!');
|
ElMessage.error("复制失败!");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
clipboard.value.destroy()
|
clipboard.value.destroy();
|
||||||
})
|
});
|
||||||
|
|
||||||
// 播放歌曲
|
// 播放歌曲
|
||||||
const play = () => {
|
const play = () => {
|
||||||
playerRef.value.play()
|
playerRef.value.play();
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const winHeight = ref(window.innerHeight - 50);
|
||||||
const winHeight = ref(window.innerHeight-50)
|
|
||||||
const getShareURL = (item) => {
|
const getShareURL = (item) => {
|
||||||
return `${location.protocol}//${location.host}/song/${item.id}`
|
return `${location.protocol}//${location.host}/song/${item.id}`;
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
<template #default="props">
|
<template #default="props">
|
||||||
<div>
|
<div>
|
||||||
<el-table :data="props.row.context" :border="childBorder">
|
<el-table :data="props.row.context" :border="childBorder">
|
||||||
<el-table-column label="对话应用" prop="role" width="120"/>
|
<el-table-column label="对话应用" prop="role" width="120" />
|
||||||
<el-table-column label="对话内容" prop="content"/>
|
<el-table-column label="对话内容" prop="content" />
|
||||||
</el-table>
|
</el-table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -23,24 +23,39 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="应用类型" prop="type_name"/>
|
<el-table-column label="应用类型" prop="type_name" />
|
||||||
<el-table-column label="应用标识" prop="key"/>
|
<el-table-column label="应用标识" prop="key" />
|
||||||
<el-table-column label="绑定模型" prop="model_name"/>
|
<el-table-column label="绑定模型" prop="model_name" />
|
||||||
<el-table-column label="启用状态">
|
<el-table-column label="启用状态">
|
||||||
<template #default="scope">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="应用图标" prop="icon">
|
<el-table-column label="应用图标" prop="icon">
|
||||||
<template #default="scope">
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="打招呼信息" prop="hello_msg"/>
|
<el-table-column label="打招呼信息" prop="hello_msg" />
|
||||||
<el-table-column label="操作" width="150">
|
<el-table-column label="操作" width="150">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="small" type="primary" @click="rowEdit(scope.$index, scope.row)">编辑</el-button>
|
<el-button
|
||||||
<el-popconfirm title="确定要删除当前应用吗?" @confirm="removeRole(scope.row)" :width="200">
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="rowEdit(scope.$index, scope.row)"
|
||||||
|
>编辑</el-button
|
||||||
|
>
|
||||||
|
<el-popconfirm
|
||||||
|
title="确定要删除当前应用吗?"
|
||||||
|
@confirm="removeRole(scope.row)"
|
||||||
|
:width="200"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button size="small" type="danger">删除</el-button>
|
<el-button size="small" type="danger">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
@ -51,48 +66,48 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showDialog"
|
v-model="showDialog"
|
||||||
:title="optTitle"
|
:title="optTitle"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
width="50%"
|
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-form-item label="应用名称:" prop="name">
|
||||||
<el-input
|
<el-input v-model="role.name" autocomplete="off" />
|
||||||
v-model="role.name"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="应用分类:" prop="tid">
|
<el-form-item label="应用分类:" prop="tid">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="role.tid"
|
v-model="role.tid"
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择分类"
|
placeholder="请选择分类"
|
||||||
clearable
|
clearable
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in appTypes"
|
v-for="item in appTypes"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="应用标志:" prop="key">
|
<el-form-item label="应用标志:" prop="key">
|
||||||
<el-input
|
<el-input v-model="role.key" autocomplete="off" />
|
||||||
v-model="role.key"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="应用图标:" prop="icon">
|
<el-form-item label="应用图标:" prop="icon">
|
||||||
<el-input v-model="role.icon">
|
<el-input v-model="role.icon">
|
||||||
<template #append>
|
<template #append>
|
||||||
<el-upload
|
<el-upload
|
||||||
:auto-upload="true"
|
:auto-upload="true"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
:http-request="uploadImg"
|
:http-request="uploadImg"
|
||||||
>
|
>
|
||||||
上传
|
上传
|
||||||
</el-upload>
|
</el-upload>
|
||||||
@ -102,25 +117,22 @@
|
|||||||
|
|
||||||
<el-form-item label="绑定模型:" prop="model_id">
|
<el-form-item label="绑定模型:" prop="model_id">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="role.model_id"
|
v-model="role.model_id"
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择模型"
|
placeholder="请选择模型"
|
||||||
clearable
|
clearable
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in models"
|
v-for="item in models"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="打招呼信息:" prop="hello_msg">
|
<el-form-item label="打招呼信息:" prop="hello_msg">
|
||||||
<el-input
|
<el-input v-model="role.hello_msg" autocomplete="off" />
|
||||||
v-model="role.hello_msg"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="上下文信息:" prop="context">
|
<el-form-item label="上下文信息:" prop="context">
|
||||||
@ -130,10 +142,10 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-select v-model="scope.row.role" placeholder="Role">
|
<el-select v-model="scope.row.role" placeholder="Role">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="value in messageRoles"
|
v-for="value in messageRoles"
|
||||||
:key="value"
|
:key="value"
|
||||||
:label="value"
|
:label="value"
|
||||||
:value="value"
|
:value="value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</template>
|
</template>
|
||||||
@ -143,12 +155,16 @@
|
|||||||
<div class="context-msg-key">
|
<div class="context-msg-key">
|
||||||
<span>对话内容</span>
|
<span>对话内容</span>
|
||||||
<span class="fr">
|
<span class="fr">
|
||||||
<el-button type="primary" @click="addContext" size="small">
|
<el-button
|
||||||
<el-icon>
|
type="primary"
|
||||||
<Plus/>
|
@click="addContext"
|
||||||
</el-icon>
|
size="small"
|
||||||
增加一行
|
>
|
||||||
</el-button>
|
<el-icon>
|
||||||
|
<Plus />
|
||||||
|
</el-icon>
|
||||||
|
增加一行
|
||||||
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -156,35 +172,58 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="context-msg-content">
|
<div class="context-msg-content">
|
||||||
<el-input
|
<el-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="3"
|
:rows="3"
|
||||||
v-model="scope.row.content"
|
v-model="scope.row.content"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
v-loading="isGenerating"
|
v-loading="isGenerating"
|
||||||
/>
|
/>
|
||||||
<span class="remove-item">
|
<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-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-button>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-popover placement="right" :width="400" trigger="click">
|
<el-popover
|
||||||
|
placement="right"
|
||||||
|
:width="400"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
<template #reference>
|
<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>
|
<i class="iconfont icon-linggan"></i>
|
||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-input
|
<el-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:rows="3"
|
:rows="3"
|
||||||
v-model="metaPrompt"
|
v-model="metaPrompt"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
placeholder="请您输入要 AI实现的目标,任务或者需要AI扮演的角色?"
|
placeholder="请您输入要 AI实现的目标,任务或者需要AI扮演的角色?"
|
||||||
/>
|
/>
|
||||||
<el-row class="text-line">
|
<el-row class="text-line">
|
||||||
<el-text class="mx-1" type="info" size="small">使用 AI 生成 System 预设指令</el-text>
|
<el-text type="info" size="small"
|
||||||
<el-button class="generate-btn" size="small" @click="generatePrompt(scope.row)" color="#5865f2" :disabled="isGenerating">
|
>使用 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>
|
<i class="iconfont icon-chuangzuo"></i>
|
||||||
<span>立即生成</span>
|
<span>立即生成</span>
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -199,175 +238,195 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="启用状态">
|
<el-form-item label="启用状态">
|
||||||
<el-switch v-model="role.enable"/>
|
<el-switch v-model="role.enable" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="showDialog = false">取消</el-button>
|
<el-button @click="showDialog = false">取消</el-button>
|
||||||
<el-button type="primary" @click="save">保存</el-button>
|
<el-button type="primary" @click="save">保存</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { Delete, Plus } from "@element-plus/icons-vue";
|
||||||
import {Delete, Plus} from "@element-plus/icons-vue";
|
import { onMounted, reactive, ref } from "vue";
|
||||||
import {onMounted, reactive, ref} from "vue";
|
import { httpGet, httpPost } from "@/utils/http";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import { ElMessage } from "element-plus";
|
||||||
import {ElMessage} from "element-plus";
|
import { copyObj, removeArrayItem } from "@/utils/libs";
|
||||||
import {copyObj, removeArrayItem} from "@/utils/libs";
|
import { Sortable } from "sortablejs";
|
||||||
import {Sortable} from "sortablejs"
|
|
||||||
import Compressor from "compressorjs";
|
import Compressor from "compressorjs";
|
||||||
import {showMessageError} from "@/utils/dialog";
|
import { showMessageError } from "@/utils/dialog";
|
||||||
|
|
||||||
const showDialog = ref(false)
|
const showDialog = ref(false);
|
||||||
const parentBorder = ref(true)
|
const parentBorder = ref(true);
|
||||||
const childBorder = ref(true)
|
const childBorder = ref(true);
|
||||||
const tableData = ref([])
|
const tableData = ref([]);
|
||||||
const sortedTableData = ref([])
|
const sortedTableData = ref([]);
|
||||||
const role = ref({context: []})
|
const role = ref({ context: [] });
|
||||||
const formRef = ref(null)
|
const formRef = ref(null);
|
||||||
const optTitle = ref("")
|
const optTitle = ref("");
|
||||||
const loading = ref(true)
|
const loading = ref(true);
|
||||||
|
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
name: [{required: true, message: '请输入用户名', trigger: 'blur',}],
|
name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
|
||||||
key: [{required: true, message: '请输入应用标识', trigger: 'blur',}],
|
key: [{ required: true, message: "请输入应用标识", trigger: "blur" }],
|
||||||
icon: [{required: true, message: '请输入应用图标', trigger: 'blur',}],
|
icon: [{ required: true, message: "请输入应用图标", trigger: "blur" }],
|
||||||
sort: [
|
sort: [
|
||||||
{required: true, message: '请输入排序数字', trigger: 'blur'},
|
{ required: true, message: "请输入排序数字", trigger: "blur" },
|
||||||
{type: 'number', message: '请输入有效数字'},
|
{ type: "number", message: "请输入有效数字" }
|
||||||
],
|
],
|
||||||
hello_msg: [{required: true, message: '请输入打招呼信息', trigger: 'change',}]
|
hello_msg: [
|
||||||
})
|
{ required: true, message: "请输入打招呼信息", trigger: "change" }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
const appTypes = ref([])
|
const appTypes = ref([]);
|
||||||
const models = ref([])
|
const models = ref([]);
|
||||||
const messageRoles = ref(["system", "user", "assistant"])
|
const messageRoles = ref(["system", "user", "assistant"]);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchData()
|
fetchData();
|
||||||
|
|
||||||
// get chat models
|
// get chat models
|
||||||
httpGet('/api/admin/model/list?enable=1').then((res) => {
|
httpGet("/api/admin/model/list?enable=1")
|
||||||
models.value = res.data
|
.then((res) => {
|
||||||
}).catch(() => {
|
models.value = res.data;
|
||||||
ElMessage.error("获取AI模型数据失败");
|
})
|
||||||
})
|
.catch(() => {
|
||||||
|
ElMessage.error("获取AI模型数据失败");
|
||||||
|
});
|
||||||
|
|
||||||
// get app type
|
// get app type
|
||||||
httpGet('/api/admin/app/type/list?enable=1').then((res) => {
|
httpGet("/api/admin/app/type/list?enable=1")
|
||||||
appTypes.value = res.data
|
.then((res) => {
|
||||||
}).catch(() => {
|
appTypes.value = res.data;
|
||||||
ElMessage.error("获取应用分类数据失败");
|
})
|
||||||
})
|
.catch(() => {
|
||||||
|
ElMessage.error("获取应用分类数据失败");
|
||||||
})
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const fetchData = () => {
|
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++) {
|
// const arr = res.data;
|
||||||
// if(arr[i].model_id == 0){
|
// for (let i = 0; i < arr.length; i++) {
|
||||||
// arr[i].model_id = ''
|
// if(arr[i].model_id == 0){
|
||||||
// }
|
// arr[i].model_id = ''
|
||||||
// }
|
// }
|
||||||
tableData.value = res.data
|
// }
|
||||||
sortedTableData.value = copyObj(tableData.value)
|
tableData.value = res.data;
|
||||||
loading.value = false
|
sortedTableData.value = copyObj(tableData.value);
|
||||||
}).catch(() => {
|
loading.value = false;
|
||||||
ElMessage.error("获取聊天应用失败");
|
})
|
||||||
})
|
.catch(() => {
|
||||||
|
ElMessage.error("获取聊天应用失败");
|
||||||
|
});
|
||||||
|
|
||||||
const drawBodyWrapper = document.querySelector('.el-table__body tbody')
|
const drawBodyWrapper = document.querySelector(".el-table__body tbody");
|
||||||
// 初始化拖动排序插件
|
// 初始化拖动排序插件
|
||||||
Sortable.create(drawBodyWrapper, {
|
Sortable.create(drawBodyWrapper, {
|
||||||
sort: true,
|
sort: true,
|
||||||
animation: 500,
|
animation: 500,
|
||||||
onEnd({newIndex, oldIndex, from}) {
|
onEnd({ newIndex, oldIndex, from }) {
|
||||||
if (oldIndex === newIndex) {
|
if (oldIndex === newIndex) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedData = Array.from(from.children).map(row => row.querySelector('.sort').getAttribute('data-id'));
|
const sortedData = Array.from(from.children).map((row) =>
|
||||||
const ids = []
|
row.querySelector(".sort").getAttribute("data-id")
|
||||||
const sorts = []
|
);
|
||||||
|
const ids = [];
|
||||||
|
const sorts = [];
|
||||||
sortedData.forEach((id, index) => {
|
sortedData.forEach((id, index) => {
|
||||||
ids.push(parseInt(id))
|
ids.push(parseInt(id));
|
||||||
sorts.push(index+1)
|
sorts.push(index + 1);
|
||||||
tableData.value[index].sort_num = index + 1
|
tableData.value[index].sort_num = index + 1;
|
||||||
})
|
});
|
||||||
|
|
||||||
httpPost("/api/admin/role/sort", {ids: ids, sorts: sorts}).catch(e => {
|
httpPost("/api/admin/role/sort", { ids: ids, sorts: sorts }).catch(
|
||||||
ElMessage.error("排序失败:" + e.message)
|
(e) => {
|
||||||
})
|
ElMessage.error("排序失败:" + e.message);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const roleSet = (filed, row) => {
|
const roleSet = (filed, row) => {
|
||||||
httpPost('/api/admin/role/set', {id: row.id, filed: filed, value: row[filed]}).then(() => {
|
httpPost("/api/admin/role/set", {
|
||||||
ElMessage.success("操作成功!")
|
id: row.id,
|
||||||
}).catch(e => {
|
filed: filed,
|
||||||
ElMessage.error("操作失败:" + e.message)
|
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) {
|
const rowEdit = function (index, row) {
|
||||||
optTitle.value = "修改应用"
|
optTitle.value = "修改应用";
|
||||||
curIndex.value = index
|
curIndex.value = index;
|
||||||
role.value = copyObj(row)
|
role.value = copyObj(row);
|
||||||
showDialog.value = true
|
showDialog.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
const addRole = function () {
|
const addRole = function () {
|
||||||
optTitle.value = "添加新应用"
|
optTitle.value = "添加新应用";
|
||||||
role.value = {context: []}
|
role.value = { context: [] };
|
||||||
showDialog.value = true
|
showDialog.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
const save = function () {
|
const save = function () {
|
||||||
formRef.value.validate((valid) => {
|
formRef.value.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
showDialog.value = false
|
showDialog.value = false;
|
||||||
httpPost('/api/admin/role/save', role.value).then(() => {
|
httpPost("/api/admin/role/save", role.value)
|
||||||
ElMessage.success('操作成功')
|
.then(() => {
|
||||||
fetchData()
|
ElMessage.success("操作成功");
|
||||||
}).catch((e) => {
|
fetchData();
|
||||||
ElMessage.error('操作失败,' + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("操作失败," + e.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const removeRole = function (row) {
|
const removeRole = function (row) {
|
||||||
httpGet('/api/admin/role/remove?id=' + row.id).then(() => {
|
httpGet("/api/admin/role/remove?id=" + row.id)
|
||||||
ElMessage.success("删除成功!")
|
.then(() => {
|
||||||
tableData.value = removeArrayItem(tableData.value, row, (v1, v2) => {
|
ElMessage.success("删除成功!");
|
||||||
return v1.id === v2.id
|
tableData.value = removeArrayItem(tableData.value, row, (v1, v2) => {
|
||||||
|
return v1.id === v2.id;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}).catch(() => {
|
.catch(() => {
|
||||||
ElMessage.error("删除失败!")
|
ElMessage.error("删除失败!");
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const addContext = function () {
|
const addContext = function () {
|
||||||
if (!role.value.context) {
|
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) {
|
const removeContext = function (index) {
|
||||||
role.value.context.splice(index, 1);
|
role.value.context.splice(index, 1);
|
||||||
}
|
};
|
||||||
|
|
||||||
// 图片上传
|
// 图片上传
|
||||||
const uploadImg = (file) => {
|
const uploadImg = (file) => {
|
||||||
@ -376,36 +435,40 @@ const uploadImg = (file) => {
|
|||||||
quality: 0.6,
|
quality: 0.6,
|
||||||
success(result) {
|
success(result) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', result, result.name);
|
formData.append("file", result, result.name);
|
||||||
// 执行上传操作
|
// 执行上传操作
|
||||||
httpPost('/api/admin/upload', formData).then((res) => {
|
httpPost("/api/admin/upload", formData)
|
||||||
role.value.icon = res.data.url
|
.then((res) => {
|
||||||
ElMessage.success('上传成功')
|
role.value.icon = res.data.url;
|
||||||
}).catch((e) => {
|
ElMessage.success("上传成功");
|
||||||
ElMessage.error('上传失败:' + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("上传失败:" + e.message);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
error(e) {
|
error(e) {
|
||||||
ElMessage.error('上传失败:' + e.message)
|
ElMessage.error("上传失败:" + e.message);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const isGenerating = ref(false)
|
const isGenerating = ref(false);
|
||||||
const metaPrompt = ref("")
|
const metaPrompt = ref("");
|
||||||
const generatePrompt = (row) => {
|
const generatePrompt = (row) => {
|
||||||
if (metaPrompt.value === "") {
|
if (metaPrompt.value === "") {
|
||||||
return showMessageError("请输入元提示词")
|
return showMessageError("请输入元提示词");
|
||||||
}
|
}
|
||||||
isGenerating.value = true
|
isGenerating.value = true;
|
||||||
httpPost("/api/prompt/meta", {prompt: metaPrompt.value}).then(res => {
|
httpPost("/api/prompt/meta", { prompt: metaPrompt.value })
|
||||||
row.content = res.data
|
.then((res) => {
|
||||||
isGenerating.value = false
|
row.content = res.data;
|
||||||
}).catch(e => {
|
isGenerating.value = false;
|
||||||
showMessageError("生成失败:"+e.message)
|
})
|
||||||
isGenerating.value = false
|
.catch((e) => {
|
||||||
})
|
showMessageError("生成失败:" + e.message);
|
||||||
}
|
isGenerating.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@ -488,4 +551,4 @@ const generatePrompt = (row) => {
|
|||||||
font-size 14px
|
font-size 14px
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -3,29 +3,48 @@
|
|||||||
<el-tabs v-model="activeName" @tab-change="handleChange">
|
<el-tabs v-model="activeName" @tab-change="handleChange">
|
||||||
<el-tab-pane label="对话列表" name="chat" v-loading="data.chat.loading">
|
<el-tab-pane label="对话列表" name="chat" v-loading="data.chat.loading">
|
||||||
<div class="handle-box">
|
<div class="handle-box">
|
||||||
<el-input v-model.number="data.chat.query.user_id" placeholder="账户ID" class="handle-input mr10"
|
<el-input
|
||||||
@keyup="searchChat($event)"></el-input>
|
v-model.number="data.chat.query.user_id"
|
||||||
<el-input v-model="data.chat.query.title" placeholder="对话标题" class="handle-input mr10"
|
placeholder="账户ID"
|
||||||
@keyup="searchChat($event)"></el-input>
|
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
|
<el-date-picker
|
||||||
v-model="data.chat.query.created_at"
|
v-model="data.chat.query.created_at"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-placeholder="结束日期"
|
end-placeholder="结束日期"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-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>
|
</div>
|
||||||
|
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-table :data="data.chat.items" :row-key="row => row.id" table-layout="auto">
|
<el-table
|
||||||
<el-table-column prop="user_id" label="账户ID"/>
|
:data="data.chat.items"
|
||||||
<el-table-column prop="username" label="账户"/>
|
: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="图标">
|
<el-table-column label="图标">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-avatar :size="30" :src="scope.row.role.icon"/>
|
<el-avatar :size="30" :src="scope.row.role.icon" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="角色">
|
<el-table-column label="角色">
|
||||||
@ -33,21 +52,29 @@
|
|||||||
<span>{{ scope.row.role.name }}</span>
|
<span>{{ scope.row.role.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="model" label="模型"/>
|
<el-table-column prop="model" label="模型" />
|
||||||
<el-table-column prop="title" label="标题"/>
|
<el-table-column prop="title" label="标题" />
|
||||||
<el-table-column prop="msg_num" label="消息数量"/>
|
<el-table-column prop="msg_num" label="消息数量" />
|
||||||
<el-table-column prop="token" label="消耗算力"/>
|
<el-table-column prop="token" label="消耗算力" />
|
||||||
|
|
||||||
<el-table-column label="创建时间">
|
<el-table-column label="创建时间">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column label="操作" width="180">
|
<el-table-column label="操作" width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="small" type="primary" @click="showMessages(scope.row)">查看</el-button>
|
<el-button
|
||||||
<el-popconfirm title="确定要删除当前记录吗?" @confirm="removeChat(scope.row)">
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="showMessages(scope.row)"
|
||||||
|
>查看</el-button
|
||||||
|
>
|
||||||
|
<el-popconfirm
|
||||||
|
title="确定要删除当前记录吗?"
|
||||||
|
@confirm="removeChat(scope.row)"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button size="small" type="danger">删除</el-button>
|
<el-button size="small" type="danger">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
@ -58,67 +85,104 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<el-pagination v-if="data.chat.total > 0" background
|
<el-pagination
|
||||||
layout="total,prev, pager, next"
|
v-if="data.chat.total > 0"
|
||||||
:hide-on-single-page="true"
|
background
|
||||||
v-model:current-page="data.chat.page"
|
layout="total,prev, pager, next"
|
||||||
v-model:page-size="data.chat.pageSize"
|
:hide-on-single-page="true"
|
||||||
@current-change="fetchChatData()"
|
v-model:current-page="data.chat.page"
|
||||||
:total="data.chat.total"/>
|
v-model:page-size="data.chat.pageSize"
|
||||||
|
@current-change="fetchChatData()"
|
||||||
|
:total="data.chat.total"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="消息记录" name="message">
|
<el-tab-pane label="消息记录" name="message">
|
||||||
<div class="handle-box">
|
<div class="handle-box">
|
||||||
<el-input v-model.number="data.message.query.user_id" placeholder="账户ID" class="handle-input mr10"
|
<el-input
|
||||||
@keyup="searchMessage($event)"></el-input>
|
v-model.number="data.message.query.user_id"
|
||||||
<el-input v-model="data.message.query.content" placeholder="消息内容" class="handle-input mr10"
|
placeholder="账户ID"
|
||||||
@keyup="searchMessage($event)"></el-input>
|
class="handle-input mr10"
|
||||||
<el-input v-model="data.message.query.model" placeholder="模型" class="handle-input mr10"
|
@keyup="searchMessage($event)"
|
||||||
@keyup="searchMessage($event)"></el-input>
|
></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
|
<el-date-picker
|
||||||
v-model="data.message.query.created_at"
|
v-model="data.message.query.created_at"
|
||||||
type="daterange"
|
type="daterange"
|
||||||
start-placeholder="开始日期"
|
start-placeholder="开始日期"
|
||||||
end-placeholder="结束日期"
|
end-placeholder="结束日期"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD"
|
||||||
value-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>
|
</div>
|
||||||
|
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-table :data="data.message.items" :row-key="row => row.id" table-layout="auto">
|
<el-table
|
||||||
<el-table-column prop="user_id" label="账户ID"/>
|
:data="data.message.items"
|
||||||
<el-table-column prop="username" label="账户"/>
|
: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="角色">
|
<el-table-column label="角色">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-avatar :size="30" :src="scope.row.icon"/>
|
<el-avatar :size="30" :src="scope.row.icon" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="model" label="模型"/>
|
<el-table-column prop="model" label="模型" />
|
||||||
|
|
||||||
<el-table-column label="消息内容">
|
<el-table-column label="消息内容">
|
||||||
<template #default="scope">
|
<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 }}
|
{{ scope.row.content }}
|
||||||
</el-text>
|
</el-text>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column prop="token" label="算力"/>
|
<el-table-column prop="token" label="算力" />
|
||||||
|
|
||||||
<el-table-column label="创建时间">
|
<el-table-column label="创建时间">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column label="操作" width="180">
|
<el-table-column label="操作" width="180">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button size="small" type="primary" @click="showContent(scope.row.content)">查看</el-button>
|
<el-button
|
||||||
<el-popconfirm title="确定要删除当前记录吗?" @confirm="removeMessage(scope.row)">
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="showContent(scope.row.content)"
|
||||||
|
>查看</el-button
|
||||||
|
>
|
||||||
|
<el-popconfirm
|
||||||
|
title="确定要删除当前记录吗?"
|
||||||
|
@confirm="removeMessage(scope.row)"
|
||||||
|
>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button size="small" type="danger">删除</el-button>
|
<el-button size="small" type="danger">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
@ -129,24 +193,25 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<el-pagination v-if="data.message.total > 0" background
|
<el-pagination
|
||||||
layout="total,prev, pager, next"
|
v-if="data.message.total > 0"
|
||||||
:hide-on-single-page="true"
|
background
|
||||||
v-model:current-page="data.message.page"
|
layout="total,prev, pager, next"
|
||||||
v-model:page-size="data.message.pageSize"
|
:hide-on-single-page="true"
|
||||||
@current-change="fetchMessageData()"
|
v-model:current-page="data.message.page"
|
||||||
:total="data.message.total"/>
|
v-model:page-size="data.message.pageSize"
|
||||||
|
@current-change="fetchMessageData()"
|
||||||
|
:total="data.message.total"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showContentDialog"
|
v-model="showContentDialog"
|
||||||
title="消息详情"
|
title="消息详情"
|
||||||
class="chat-dialog"
|
class="chat-dialog"
|
||||||
style="--el-dialog-width:60%"
|
style="--el-dialog-width: 60%"
|
||||||
>
|
>
|
||||||
<div class="chat-detail">
|
<div class="chat-detail">
|
||||||
<div class="chat-line" v-html="dialogContent"></div>
|
<div class="chat-line" v-html="dialogContent"></div>
|
||||||
@ -154,138 +219,147 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showChatItemDialog"
|
v-model="showChatItemDialog"
|
||||||
title="对话详情"
|
title="对话详情"
|
||||||
class="chat-dialog"
|
class="chat-dialog"
|
||||||
style="--el-dialog-width:60%"
|
style="--el-dialog-width: 60%"
|
||||||
>
|
>
|
||||||
<div class="chat-box chat-page">
|
<div class="chat-box chat-page">
|
||||||
<div v-for="item in messages" :key="item.id">
|
<div v-for="item in messages" :key="item.id">
|
||||||
<chat-prompt
|
<chat-prompt v-if="item.type === 'prompt'" :data="item" />
|
||||||
v-if="item.type==='prompt'"
|
<chat-reply
|
||||||
:data="item"/>
|
v-else-if="item.type === 'reply'"
|
||||||
<chat-reply v-else-if="item.type==='reply'"
|
:read-only="true"
|
||||||
:read-only="true"
|
:data="item"
|
||||||
:data="item"/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- end chat box -->
|
</div>
|
||||||
|
<!-- end chat box -->
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, ref} from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import { httpGet, httpPost } from "@/utils/http";
|
||||||
import {ElMessage} from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import {dateFormat, processContent} from "@/utils/libs";
|
import { dateFormat, processContent } from "@/utils/libs";
|
||||||
import {Search} from "@element-plus/icons-vue";
|
import { Search } from "@element-plus/icons-vue";
|
||||||
import 'highlight.js/styles/a11y-dark.css'
|
import "highlight.js/styles/a11y-dark.css";
|
||||||
import hl from "highlight.js";
|
import hl from "highlight.js";
|
||||||
import ChatPrompt from "@/components/ChatPrompt.vue";
|
import ChatPrompt from "@/components/ChatPrompt.vue";
|
||||||
import ChatReply from "@/components/ChatReply.vue";
|
import ChatReply from "@/components/ChatReply.vue";
|
||||||
|
|
||||||
// 变量定义
|
// 变量定义
|
||||||
const data = ref({
|
const data = ref({
|
||||||
"chat": {
|
chat: {
|
||||||
items: [],
|
items: [],
|
||||||
query: {title: "", created_at: [], page: 1, page_size: 15},
|
query: { title: "", created_at: [], page: 1, page_size: 15 },
|
||||||
total: 0,
|
total: 0,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 15,
|
pageSize: 15,
|
||||||
loading: true
|
loading: true
|
||||||
},
|
},
|
||||||
"message": {
|
message: {
|
||||||
items: [],
|
items: [],
|
||||||
query: {title: "", created_at: [], page: 1, page_size: 15},
|
query: { title: "", created_at: [], page: 1, page_size: 15 },
|
||||||
total: 0,
|
total: 0,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 15,
|
pageSize: 15,
|
||||||
loading: true
|
loading: true
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
const activeName = ref("chat")
|
const activeName = ref("chat");
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchChatData()
|
fetchChatData();
|
||||||
})
|
});
|
||||||
|
|
||||||
const handleChange = (tab) => {
|
const handleChange = (tab) => {
|
||||||
if (tab === "chat") {
|
if (tab === "chat") {
|
||||||
fetchChatData()
|
fetchChatData();
|
||||||
} else if (tab === "message") {
|
} else if (tab === "message") {
|
||||||
fetchMessageData()
|
fetchMessageData();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 搜索对话
|
// 搜索对话
|
||||||
const searchChat = (evt) => {
|
const searchChat = (evt) => {
|
||||||
if (evt.keyCode === 13) {
|
if (evt.keyCode === 13) {
|
||||||
fetchChatData()
|
fetchChatData();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 搜索消息
|
// 搜索消息
|
||||||
const searchMessage = (evt) => {
|
const searchMessage = (evt) => {
|
||||||
if (evt.keyCode === 13) {
|
if (evt.keyCode === 13) {
|
||||||
fetchMessageData()
|
fetchMessageData();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const fetchChatData = () => {
|
const fetchChatData = () => {
|
||||||
const d = data.value.chat
|
const d = data.value.chat;
|
||||||
d.query.page = d.page
|
d.query.page = d.page;
|
||||||
d.query.page_size = d.pageSize
|
d.query.page_size = d.pageSize;
|
||||||
httpPost('/api/admin/chat/list', d.query).then((res) => {
|
httpPost("/api/admin/chat/list", d.query)
|
||||||
if (res.data) {
|
.then((res) => {
|
||||||
d.items = res.data.items
|
if (res.data) {
|
||||||
d.total = res.data.total
|
d.items = res.data.items;
|
||||||
d.page = res.data.page
|
d.total = res.data.total;
|
||||||
d.pageSize = res.data.page_size
|
d.page = res.data.page;
|
||||||
}
|
d.pageSize = res.data.page_size;
|
||||||
d.loading = false
|
}
|
||||||
}).catch(e => {
|
d.loading = false;
|
||||||
ElMessage.error("获取数据失败:" + e.message);
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("获取数据失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const fetchMessageData = () => {
|
const fetchMessageData = () => {
|
||||||
const d = data.value.message
|
const d = data.value.message;
|
||||||
d.query.page = d.page
|
d.query.page = d.page;
|
||||||
d.query.page_size = d.pageSize
|
d.query.page_size = d.pageSize;
|
||||||
httpPost('/api/admin/chat/message', d.query).then((res) => {
|
httpPost("/api/admin/chat/message", d.query)
|
||||||
if (res.data) {
|
.then((res) => {
|
||||||
d.items = res.data.items
|
if (res.data) {
|
||||||
d.total = res.data.total
|
d.items = res.data.items;
|
||||||
d.page = res.data.page
|
d.total = res.data.total;
|
||||||
d.pageSize = res.data.page_size
|
d.page = res.data.page;
|
||||||
}
|
d.pageSize = res.data.page_size;
|
||||||
d.loading = false
|
}
|
||||||
}).catch(e => {
|
d.loading = false;
|
||||||
ElMessage.error("获取数据失败:" + e.message);
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("获取数据失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const removeChat = function (row) {
|
const removeChat = function (row) {
|
||||||
httpGet('/api/admin/chat/remove?chat_id=' + row.chat_id).then(() => {
|
httpGet("/api/admin/chat/remove?chat_id=" + row.chat_id)
|
||||||
ElMessage.success("删除成功!")
|
.then(() => {
|
||||||
fetchChatData()
|
ElMessage.success("删除成功!");
|
||||||
}).catch((e) => {
|
fetchChatData();
|
||||||
ElMessage.error("删除失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("删除失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const removeMessage = function (row) {
|
const removeMessage = function (row) {
|
||||||
httpGet('/api/admin/chat/message/remove?id=' + row.id).then(() => {
|
httpGet("/api/admin/chat/message/remove?id=" + row.id)
|
||||||
ElMessage.success("删除成功!")
|
.then(() => {
|
||||||
fetchMessageData()
|
ElMessage.success("删除成功!");
|
||||||
}).catch((e) => {
|
fetchMessageData();
|
||||||
ElMessage.error("删除失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("删除失败:" + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const mathjaxPlugin = require('markdown-it-mathjax3')
|
const mathjaxPlugin = require("markdown-it-mathjax3");
|
||||||
const md = require('markdown-it')({
|
const md = require("markdown-it")({
|
||||||
breaks: true,
|
breaks: true,
|
||||||
html: true,
|
html: true,
|
||||||
linkify: true,
|
linkify: true,
|
||||||
@ -293,41 +367,43 @@ const md = require('markdown-it')({
|
|||||||
highlight: function (str, lang) {
|
highlight: function (str, lang) {
|
||||||
if (lang && hl.getLanguage(lang)) {
|
if (lang && hl.getLanguage(lang)) {
|
||||||
// 处理代码高亮
|
// 处理代码高亮
|
||||||
const preCode = hl.highlight(lang, str, true).value
|
const preCode = hl.highlight(lang, str, true).value;
|
||||||
// 将代码包裹在 pre 中
|
// 将代码包裹在 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 中
|
// 将代码包裹在 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 showContentDialog = ref(false);
|
||||||
const dialogContent = ref("")
|
const dialogContent = ref("");
|
||||||
const showContent = (content) => {
|
const showContent = (content) => {
|
||||||
showContentDialog.value = true
|
showContentDialog.value = true;
|
||||||
dialogContent.value = md.render(processContent(content))
|
dialogContent.value = md.render(processContent(content));
|
||||||
}
|
};
|
||||||
|
|
||||||
const showChatItemDialog = ref(false)
|
const showChatItemDialog = ref(false);
|
||||||
const messages = ref([])
|
const messages = ref([]);
|
||||||
const showMessages = (row) => {
|
const showMessages = (row) => {
|
||||||
showChatItemDialog.value = true
|
showChatItemDialog.value = true;
|
||||||
messages.value = []
|
messages.value = [];
|
||||||
httpGet('/api/admin/chat/history?chat_id=' + row.chat_id).then(res => {
|
httpGet("/api/admin/chat/history?chat_id=" + row.chat_id)
|
||||||
const data = res.data
|
.then((res) => {
|
||||||
for (let i = 0; i < data.length; i++) {
|
const data = res.data;
|
||||||
messages.value.push(data[i]);
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
@ -93,6 +93,7 @@
|
|||||||
<el-pagination v-if="data.mj.total > 0" background
|
<el-pagination v-if="data.mj.total > 0" background
|
||||||
layout="total,prev, pager, next"
|
layout="total,prev, pager, next"
|
||||||
:hide-on-single-page="true"
|
:hide-on-single-page="true"
|
||||||
|
|
||||||
v-model:current-page="data.mj.page"
|
v-model:current-page="data.mj.page"
|
||||||
v-model:page-size="data.mj.pageSize"
|
v-model:page-size="data.mj.pageSize"
|
||||||
@current-change="fetchMjData()"
|
@current-change="fetchMjData()"
|
||||||
|
@ -1,27 +1,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container user-list" v-loading="loading">
|
<div class="container user-list" v-loading="loading">
|
||||||
<div class="handle-box">
|
<div class="handle-box">
|
||||||
<el-input v-model="query.username" placeholder="账号" class="handle-input mr10"></el-input>
|
<el-input
|
||||||
<el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
|
v-model="query.username"
|
||||||
<el-button type="success" :icon="Plus" @click="addUser">新增用户</el-button>
|
placeholder="账号"
|
||||||
<el-button type="danger" :icon="Delete" @click="multipleDelete">删除</el-button>
|
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>
|
</div>
|
||||||
|
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-table :data="users.items" border class="table" :row-key="row => row.id"
|
<el-table
|
||||||
@selection-change="handleSelectionChange" table-layout="auto">
|
: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 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="账号">
|
<el-table-column label="账号">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ scope.row.username }}</span>
|
<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>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="mobile" label="手机"/>
|
<el-table-column prop="mobile" label="手机" />
|
||||||
<el-table-column prop="email" label="邮箱"/>
|
<el-table-column prop="email" label="邮箱" />
|
||||||
<el-table-column prop="nickname" label="昵称"/>
|
<el-table-column prop="nickname" label="昵称" />
|
||||||
<el-table-column prop="power" label="剩余算力"/>
|
<el-table-column prop="power" label="剩余算力" />
|
||||||
<el-table-column label="状态" width="80">
|
<el-table-column label="状态" width="80">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.status" type="success">正常</el-tag>
|
<el-tag v-if="scope.row.status" type="success">正常</el-tag>
|
||||||
@ -30,337 +50,384 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="过期时间">
|
<el-table-column label="过期时间">
|
||||||
<template #default="scope">
|
<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>
|
<el-tag v-else>长期有效</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column label="注册时间">
|
<el-table-column label="注册时间">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span>{{ dateFormat(scope.row['created_at']) }}</span>
|
<span>{{ dateFormat(scope.row["created_at"]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column fixed="right" label="操作" width="200">
|
<el-table-column fixed="right" label="操作" width="200">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button-group class="ml-4">
|
<el-button-group class="ml-4">
|
||||||
<el-button size="small" type="primary" @click="userEdit(scope.row)">编辑</el-button>
|
<el-button
|
||||||
<el-button size="small" type="danger" @click="removeUser(scope.row)">删除</el-button>
|
size="small"
|
||||||
<el-button size="small" type="success" @click="resetPass(scope.row)">重置密码</el-button>
|
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>
|
</el-button-group>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<el-pagination v-if="users.total > 0"
|
<el-pagination
|
||||||
background
|
v-if="users.total > 0"
|
||||||
layout="total, prev, pager, next"
|
background
|
||||||
v-model:current-page="users.page"
|
layout="total, prev, pager, next"
|
||||||
v-model:page-size="users.page_size"
|
v-model:current-page="users.page"
|
||||||
:total="users.total"
|
v-model:page-size="users.page_size"
|
||||||
@current-change="fetchUserList(users.page, users.page_size)"
|
:total="users.total"
|
||||||
|
@current-change="fetchUserList(users.page, users.page_size)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="showUserEditDialog"
|
v-model="showUserEditDialog"
|
||||||
:title="title"
|
:title="title"
|
||||||
:close-on-click-modal="false"
|
:close-on-click-modal="false"
|
||||||
width="50%"
|
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-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>
|
||||||
<el-form-item label="手机:" prop="mobile">
|
<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>
|
||||||
<el-form-item label="邮箱:" prop="email">
|
<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>
|
||||||
<el-form-item v-if="add" label="密码:" prop="password">
|
<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>
|
||||||
<el-form-item label="剩余算力:" prop="power">
|
<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>
|
||||||
|
|
||||||
<el-form-item label="有效期:" prop="expired_time">
|
<el-form-item label="有效期:" prop="expired_time">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="user.expired_time"
|
v-model="user.expired_time"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
placeholder="选择日期"
|
placeholder="选择日期"
|
||||||
format="YYYY-MM-DD HH:mm:ss"
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
:disabled-date="disabledDate"
|
:disabled-date="disabledDate"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="聊天角色" prop="chat_roles">
|
<el-form-item label="聊天角色" prop="chat_roles">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="user.chat_roles"
|
v-model="user.chat_roles"
|
||||||
multiple
|
multiple
|
||||||
:filterable="true"
|
:filterable="true"
|
||||||
placeholder="选择聊天角色,多选"
|
placeholder="选择聊天角色,多选"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in roles"
|
v-for="item in roles"
|
||||||
:key="item.key"
|
:key="item.key"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:value="item.key"
|
:value="item.key"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="模型权限" prop="chat_models">
|
<el-form-item label="模型权限" prop="chat_models">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="user.chat_models"
|
v-model="user.chat_models"
|
||||||
multiple
|
multiple
|
||||||
:filterable="true"
|
:filterable="true"
|
||||||
placeholder="选择AI模型,多选"
|
placeholder="选择AI模型,多选"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in models"
|
v-for="item in models"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="item.name"
|
:label="item.name"
|
||||||
:value="item.id"
|
:value="item.id"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
|
||||||
<el-form-item label="启用状态">
|
<el-form-item label="启用状态">
|
||||||
<el-switch v-model="user.status"/>
|
<el-switch v-model="user.status" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="开通VIP">
|
<el-form-item label="开通VIP">
|
||||||
<el-switch v-model="user.vip"/>
|
<el-switch v-model="user.vip" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="showUserEditDialog = false">取消</el-button>
|
<el-button @click="showUserEditDialog = false">取消</el-button>
|
||||||
<el-button type="primary" @click="saveUser">提交</el-button>
|
<el-button type="primary" @click="saveUser">提交</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog v-model="showResetPassDialog" title="重置密码" width="50%">
|
||||||
v-model="showResetPassDialog"
|
|
||||||
title="重置密码"
|
|
||||||
width="50%"
|
|
||||||
>
|
|
||||||
<el-form label-width="100px" ref="userEditFormRef">
|
<el-form label-width="100px" ref="userEditFormRef">
|
||||||
<el-form-item label="账户:">
|
<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>
|
||||||
|
|
||||||
<el-form-item label="新密码:">
|
<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-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button type="primary" @click="doResetPass">提交</el-button>
|
<el-button type="primary" @click="doResetPass">提交</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted, reactive, ref} from "vue";
|
import { onMounted, reactive, ref } from "vue";
|
||||||
import {httpGet, httpPost} from "@/utils/http";
|
import { httpGet, httpPost } from "@/utils/http";
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import {dateFormat, disabledDate} from "@/utils/libs";
|
import { dateFormat, disabledDate } from "@/utils/libs";
|
||||||
import {Delete, Plus, Search} from "@element-plus/icons-vue";
|
import { Delete, Plus, Search } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
// 变量定义
|
// 变量定义
|
||||||
const users = ref({page: 1, page_size: 15, items: []})
|
const users = ref({ page: 1, page_size: 15, items: [] });
|
||||||
const query = ref({username: '', page: 1, page_size: 15})
|
const query = ref({ username: "", page: 1, page_size: 15 });
|
||||||
|
|
||||||
const title = ref('添加用户')
|
const title = ref("添加用户");
|
||||||
const vipImg = ref("/images/vip.png")
|
const vipImg = ref("/images/menu/member.png");
|
||||||
const add = ref(true)
|
const add = ref(true);
|
||||||
const user = ref({chat_roles: [], chat_models: []})
|
const user = ref({ chat_roles: [], chat_models: [] });
|
||||||
const pass = ref({username: '', password: '', id: 0})
|
const pass = ref({ username: "", password: "", id: 0 });
|
||||||
const roles = ref([])
|
const roles = ref([]);
|
||||||
const models = ref([])
|
const models = ref([]);
|
||||||
const showUserEditDialog = ref(false)
|
const showUserEditDialog = ref(false);
|
||||||
const showResetPassDialog = ref(false)
|
const showResetPassDialog = ref(false);
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
username: [{required: true, message: '请输入账号', trigger: 'blur',}],
|
username: [{ required: true, message: "请输入账号", trigger: "blur" }],
|
||||||
password: [
|
password: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
validator: (rule, value) => {
|
validator: (rule, value) => {
|
||||||
return !(value.length > 16 || value.length < 8);
|
return !(value.length > 16 || value.length < 8);
|
||||||
|
},
|
||||||
}, message: '密码必须为8-16',
|
message: "密码必须为8-16",
|
||||||
trigger: 'blur'
|
trigger: "blur"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
calls: [
|
calls: [
|
||||||
{required: true, message: '请输入提问次数'},
|
{ required: true, message: "请输入提问次数" },
|
||||||
{type: 'number', message: '请输入有效数字'},
|
{ type: "number", message: "请输入有效数字" }
|
||||||
],
|
],
|
||||||
chat_roles: [{required: true, message: '请选择聊天角色', trigger: 'change'}],
|
chat_roles: [
|
||||||
chat_models: [{required: true, message: '请选择AI模型', trigger: 'change'}],
|
{ required: true, message: "请选择聊天角色", trigger: "change" }
|
||||||
})
|
],
|
||||||
const loading = ref(true)
|
chat_models: [{ required: true, message: "请选择AI模型", trigger: "change" }]
|
||||||
|
});
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
const userEditFormRef = ref(null)
|
const userEditFormRef = ref(null);
|
||||||
|
|
||||||
onMounted(() => {
|
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")
|
||||||
roles.value = res.data;
|
.then((res) => {
|
||||||
}).catch(() => {
|
roles.value = res.data;
|
||||||
ElMessage.error("获取聊天角色失败");
|
})
|
||||||
})
|
.catch(() => {
|
||||||
|
ElMessage.error("获取聊天角色失败");
|
||||||
|
});
|
||||||
|
|
||||||
httpGet('/api/admin/model/list').then(res => {
|
httpGet("/api/admin/model/list")
|
||||||
models.value = res.data
|
.then((res) => {
|
||||||
}).catch(e => {
|
models.value = res.data;
|
||||||
ElMessage.error("获取模型失败:" + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
})
|
ElMessage.error("获取模型失败:" + e.message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const fetchUserList = function (page, pageSize) {
|
const fetchUserList = function (page, pageSize) {
|
||||||
query.value.page = page
|
query.value.page = page;
|
||||||
query.value.page_size = pageSize
|
query.value.page_size = pageSize;
|
||||||
httpGet('/api/admin/user/list', query.value).then((res) => {
|
httpGet("/api/admin/user/list", query.value)
|
||||||
if (res.data) {
|
.then((res) => {
|
||||||
// 初始化数据
|
if (res.data) {
|
||||||
const arr = res.data.items;
|
// 初始化数据
|
||||||
for (let i = 0; i < arr.length; i++) {
|
const arr = res.data.items;
|
||||||
arr[i].expired_time = dateFormat(arr[i].expired_time)
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
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
|
loading.value = false;
|
||||||
users.value.total = res.data.total
|
})
|
||||||
users.value.page = res.data.page
|
.catch(() => {
|
||||||
user.value.page_size = res.data.page_size
|
ElMessage.error("加载用户列表失败");
|
||||||
}
|
});
|
||||||
loading.value = false
|
};
|
||||||
}).catch(() => {
|
|
||||||
ElMessage.error('加载用户列表失败')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
fetchUserList(users.value.page, users.value.page_size)
|
fetchUserList(users.value.page, users.value.page_size);
|
||||||
}
|
};
|
||||||
|
|
||||||
// 删除用户
|
// 删除用户
|
||||||
const removeUser = function (user) {
|
const removeUser = function (user) {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
'此操作将会永久删除用户信息和聊天记录,确认操作吗?',
|
"此操作将会永久删除用户信息和聊天记录,确认操作吗?",
|
||||||
'警告',
|
"警告",
|
||||||
{
|
{
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: "取消",
|
||||||
type: 'warning',
|
type: "warning"
|
||||||
}
|
}
|
||||||
).then(() => {
|
)
|
||||||
httpGet('/api/admin/user/remove', {id: user.id}).then(() => {
|
.then(() => {
|
||||||
ElMessage.success('操作成功!')
|
httpGet("/api/admin/user/remove", { id: user.id })
|
||||||
fetchUserList(users.value.page, users.value.page_size)
|
.then(() => {
|
||||||
}).catch((e) => {
|
ElMessage.success("操作成功!");
|
||||||
ElMessage.error('操作失败,' + e.message)
|
fetchUserList(users.value.page, users.value.page_size);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("操作失败," + e.message);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}).catch(() => {
|
.catch(() => {
|
||||||
ElMessage.info('操作被取消')
|
ElMessage.info("操作被取消");
|
||||||
})
|
});
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const userEdit = function (row) {
|
const userEdit = function (row) {
|
||||||
user.value = row
|
user.value = row;
|
||||||
title.value = '编辑用户'
|
title.value = "编辑用户";
|
||||||
showUserEditDialog.value = true
|
showUserEditDialog.value = true;
|
||||||
add.value = false
|
add.value = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
const addUser = () => {
|
const addUser = () => {
|
||||||
user.value = {chat_id: 0, chat_roles: [], chat_models: []}
|
user.value = { chat_id: 0, chat_roles: [], chat_models: [] };
|
||||||
title.value = '添加用户'
|
title.value = "添加用户";
|
||||||
showUserEditDialog.value = true
|
showUserEditDialog.value = true;
|
||||||
add.value = true
|
add.value = true;
|
||||||
}
|
};
|
||||||
|
|
||||||
const saveUser = function () {
|
const saveUser = function () {
|
||||||
userEditFormRef.value.validate((valid) => {
|
userEditFormRef.value.validate((valid) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
showUserEditDialog.value = false
|
showUserEditDialog.value = false;
|
||||||
console.log(user.value)
|
console.log(user.value);
|
||||||
httpPost('/api/admin/user/save', user.value).then((res) => {
|
httpPost("/api/admin/user/save", user.value)
|
||||||
ElMessage.success('操作成功!')
|
.then((res) => {
|
||||||
if (add.value) {
|
ElMessage.success("操作成功!");
|
||||||
users.value.items.push(res.data)
|
if (add.value) {
|
||||||
}
|
users.value.items.push(res.data);
|
||||||
}).catch((e) => {
|
}
|
||||||
ElMessage.error('操作失败,' + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
|
ElMessage.error("操作失败," + e.message);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const userIds = ref([])
|
const userIds = ref([]);
|
||||||
const handleSelectionChange = function (rows) {
|
const handleSelectionChange = function (rows) {
|
||||||
userIds.value = []
|
userIds.value = [];
|
||||||
rows.forEach((row) => {
|
rows.forEach((row) => {
|
||||||
userIds.value.push(row.id)
|
userIds.value.push(row.id);
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const multipleDelete = function () {
|
const multipleDelete = function () {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
'此操作将会永久删除用户信息和聊天记录,确认操作吗?',
|
"此操作将会永久删除用户信息和聊天记录,确认操作吗?",
|
||||||
'警告',
|
"警告",
|
||||||
{
|
{
|
||||||
confirmButtonText: '确定',
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: '取消',
|
cancelButtonText: "取消",
|
||||||
type: 'warning',
|
type: "warning"
|
||||||
}
|
}
|
||||||
).then(() => {
|
)
|
||||||
loading.value = true
|
.then(() => {
|
||||||
httpGet('/api/admin/user/remove', {ids: userIds.value}).then(() => {
|
loading.value = true;
|
||||||
ElMessage.success('操作成功!')
|
httpGet("/api/admin/user/remove", { ids: userIds.value })
|
||||||
fetchUserList(users.value.page, users.value.page_size)
|
.then(() => {
|
||||||
loading.value = false
|
ElMessage.success("操作成功!");
|
||||||
}).catch((e) => {
|
fetchUserList(users.value.page, users.value.page_size);
|
||||||
ElMessage.error('操作失败,' + e.message)
|
loading.value = false;
|
||||||
loading.value = false
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
ElMessage.error("操作失败," + e.message);
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
})
|
})
|
||||||
}).catch(() => {
|
.catch(() => {
|
||||||
ElMessage.info('操作被取消')
|
ElMessage.info("操作被取消");
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
const resetPass = (row) => {
|
const resetPass = (row) => {
|
||||||
showResetPassDialog.value = true
|
showResetPassDialog.value = true;
|
||||||
pass.value.id = row.id
|
pass.value.id = row.id;
|
||||||
pass.value.username = row.username
|
pass.value.username = row.username;
|
||||||
}
|
};
|
||||||
|
|
||||||
const doResetPass = () => {
|
const doResetPass = () => {
|
||||||
httpPost('/api/admin/user/resetPass', pass.value).then(() => {
|
httpPost("/api/admin/user/resetPass", pass.value)
|
||||||
ElMessage.success('操作成功!')
|
.then(() => {
|
||||||
showResetPassDialog.value = false
|
ElMessage.success("操作成功!");
|
||||||
}).catch((e) => {
|
showResetPassDialog.value = false;
|
||||||
ElMessage.error('操作失败,' + e.message)
|
})
|
||||||
})
|
.catch((e) => {
|
||||||
}
|
ElMessage.error("操作失败," + e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
@ -398,4 +465,4 @@ const doResetPass = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|