style:样式切换
| 
		 Before Width: | Height: | Size: 1.5 KiB  | 
| 
		 Before Width: | Height: | Size: 1.8 KiB  | 
| 
		 Before Width: | Height: | Size: 802 B  | 
| 
		 Before Width: | Height: | Size: 939 B  | 
| 
		 Before Width: | Height: | Size: 1.7 KiB  | 
| 
		 Before Width: | Height: | Size: 1.2 KiB  | 
| 
		 Before Width: | Height: | Size: 1.6 KiB  | 
@@ -51,6 +51,6 @@
 | 
			
		||||
  color: #999;
 | 
			
		||||
}
 | 
			
		||||
.page-apps .inner .list-box .app-item:hover {
 | 
			
		||||
  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
			
		||||
  box-shadow: 0 0 10px var(--shadow-color); /* 添加阴影效果 */
 | 
			
		||||
  transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,30 +1,38 @@
 | 
			
		||||
.page-apps {
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  // background-color: #282c34;
 | 
			
		||||
  height 100%
 | 
			
		||||
 | 
			
		||||
  .apps-type-nav{
 | 
			
		||||
    height 43px
 | 
			
		||||
    height 50px
 | 
			
		||||
    padding 8px 0;
 | 
			
		||||
    margin-bottom 3px
 | 
			
		||||
    margin 10px auto
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .scrollbar-type-nav{
 | 
			
		||||
    display flex
 | 
			
		||||
    align-items center
 | 
			
		||||
    height 43px
 | 
			
		||||
    padding 0 5px
 | 
			
		||||
   
 | 
			
		||||
    padding 2px
 | 
			
		||||
    background-color #f4f1f7
 | 
			
		||||
    width fit-content
 | 
			
		||||
    border 1px solid rgba(79,89,102,.078)
 | 
			
		||||
    border-radius: 20px 
 | 
			
		||||
    margin: 0 auto
 | 
			
		||||
    // background: var(--chat-bg);
 | 
			
		||||
    // width 100%
 | 
			
		||||
    li{
 | 
			
		||||
      flex-shrink 0
 | 
			
		||||
      display flex
 | 
			
		||||
      align-items center
 | 
			
		||||
      justify-content center
 | 
			
		||||
      margin 0 10px
 | 
			
		||||
      margin 5px 8px
 | 
			
		||||
      height 26px
 | 
			
		||||
      border-radius 4px
 | 
			
		||||
      border 1px solid rgb(80,80,80)
 | 
			
		||||
      // border 1px solid rgb(80,80,80)
 | 
			
		||||
      padding 2px 12px
 | 
			
		||||
      background rgba(60,60,60 0.9)
 | 
			
		||||
      color #fff
 | 
			
		||||
      // background rgba(60,60,60 0.9)
 | 
			
		||||
      color var(--theme-text-tertiary)
 | 
			
		||||
      font-weight: bold
 | 
			
		||||
      font-size 14px
 | 
			
		||||
      cursor pointer
 | 
			
		||||
 | 
			
		||||
@@ -34,9 +42,12 @@
 | 
			
		||||
        overflow hidden
 | 
			
		||||
        margin-right 5px
 | 
			
		||||
        border-radius 50%
 | 
			
		||||
    
 | 
			
		||||
      }
 | 
			
		||||
      &.active{
 | 
			
		||||
        background #21aa93;
 | 
			
		||||
        background #fff;
 | 
			
		||||
        color: var(--el-color-primary);
 | 
			
		||||
        border-radius 20px
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -53,16 +64,24 @@
 | 
			
		||||
      .item {
 | 
			
		||||
        display flex
 | 
			
		||||
        flex-flow row
 | 
			
		||||
        border 1px solid rgb(80,80,80)
 | 
			
		||||
        // border 1px solid rgb(80,80,80)
 | 
			
		||||
        padding 10px
 | 
			
		||||
        background rgba(60,60,60 0.5)
 | 
			
		||||
        background: var(--chat-bg);
 | 
			
		||||
        border-radius 8px
 | 
			
		||||
 | 
			
		||||
        .image {
 | 
			
		||||
          width 80px
 | 
			
		||||
          height 80px
 | 
			
		||||
          min-width 80px
 | 
			
		||||
          border-radius 5px
 | 
			
		||||
          border-radius 50%
 | 
			
		||||
          overflow hidden
 | 
			
		||||
          object-fit: contain
 | 
			
		||||
          display: flex
 | 
			
		||||
          align-items center
 | 
			
		||||
          justify-content center
 | 
			
		||||
          flex-shrink 0
 | 
			
		||||
          border: 2px solid #f5f7fd
 | 
			
		||||
          background: #fff
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .inner {
 | 
			
		||||
@@ -75,7 +94,7 @@
 | 
			
		||||
            text-align left
 | 
			
		||||
 | 
			
		||||
            .info-title {
 | 
			
		||||
              color var(--el-text-color)
 | 
			
		||||
              color: var(--text-theme-color)
 | 
			
		||||
              font-size 1.25rem
 | 
			
		||||
              line-height 1.75rem
 | 
			
		||||
              letter-spacing: .025em;
 | 
			
		||||
@@ -96,7 +115,8 @@
 | 
			
		||||
              word-break: break-all;
 | 
			
		||||
              height 34px
 | 
			
		||||
              font-size: .875rem;
 | 
			
		||||
              color #999999
 | 
			
		||||
              color var(--el-text-color-primary)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
$sideBgColor = #252526;
 | 
			
		||||
$borderColor = #4676d0;
 | 
			
		||||
 | 
			
		||||
#app {
 | 
			
		||||
 | 
			
		||||
  height: 100%;
 | 
			
		||||
@@ -33,7 +32,7 @@ $borderColor = #4676d0;
 | 
			
		||||
          // .search-input {
 | 
			
		||||
          //   --el-input-bg-color: #363535
 | 
			
		||||
          //   --el-input-border-color: #464545
 | 
			
		||||
          //   --el-input-focus-border-color: #47fff1
 | 
			
		||||
          //   --el-input-focus-border-color:#b0a0f8
 | 
			
		||||
          //   --el-input-hover-border-color: #2DA39A
 | 
			
		||||
          //   box-shadow: none
 | 
			
		||||
          // }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
  --sm-txt:rgba(163, 174, 208, 1);
 | 
			
		||||
  --text-secondary: #8a939d;
 | 
			
		||||
  --el-color-primary: rgb(107, 80, 225);
 | 
			
		||||
  --theme-textcolor-normal:#b0a0f8;
 | 
			
		||||
  --el-border-radius-base: 8px;
 | 
			
		||||
  --el-color-primary-light-5:rgb(107, 85, 255);
 | 
			
		||||
  --el-color-primary-light-3:rgb(78, 51, 254);
 | 
			
		||||
@@ -19,9 +20,18 @@
 | 
			
		||||
  --theme-border-primary: rgba(86, 86, 95, .322); //主题边框颜色
 | 
			
		||||
  --theme-border-hover: rgb(107, 85, 255);//主题边框hover颜色
 | 
			
		||||
  --text--hover:rgba(215, 211, 240, 0.581) //主题色hover色系
 | 
			
		||||
 --el-input-focus-border-color: #b0a0f8;
 | 
			
		||||
 --little-btn-bg:#e9d3f6;
 | 
			
		||||
 --gray-btn-bg:#ededf591;
 | 
			
		||||
  --a-link-color: #3561ff
 | 
			
		||||
  --shadow-color:rgba(223,71,255,0.6)
 | 
			
		||||
  --sm-btn-bg:#6052ed;
 | 
			
		||||
--theme-text-tertiary: #595959;
 | 
			
		||||
--theme-btn-fill-tertiary: #f0ebff;
 | 
			
		||||
--theme-text-btn-tertiary: #6841ea;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// #e7e7e8
 | 
			
		||||
}
 | 
			
		||||
.el-dialog{
 | 
			
		||||
    --el-border-radius-base: 20px;
 | 
			
		||||
@@ -90,13 +100,13 @@
 | 
			
		||||
/* 滚动条的轨道背景颜色 */
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar-track {
 | 
			
		||||
  background: #f1f1f1; /* 滚动条轨道颜色 */
 | 
			
		||||
  background: #f1f1f1 !important; /* 滚动条轨道颜色 */
 | 
			
		||||
  border-radius: 6px; /* 可选:轨道圆角 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 滚动条滑块的颜色 */
 | 
			
		||||
::-webkit-scrollbar-thumb {
 | 
			
		||||
  background: #888; /* 滑块颜色 */
 | 
			
		||||
  background: #888 !important; /* 滑块颜色 */
 | 
			
		||||
  border-radius: 6px; /* 可选:滑块圆角 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -116,3 +126,12 @@
 | 
			
		||||
    font-size: 16px
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.sm-btn-theme{
 | 
			
		||||
      background-color: var(--theme-btn-fill-tertiary) !important;
 | 
			
		||||
    color: var(--theme-text-btn-tertiary) !important;
 | 
			
		||||
    border: none;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .el-tag, .el-tag.el-tag--primary{
 | 
			
		||||
  --el-tag-bg-color:#f0ebff
 | 
			
		||||
  }
 | 
			
		||||
@@ -1,13 +1,18 @@
 | 
			
		||||
.custom-scroll ::-webkit-scrollbar {
 | 
			
		||||
  width: 8px; /* 滚动条宽度 */
 | 
			
		||||
  display:none;
 | 
			
		||||
}
 | 
			
		||||
.custom-scroll ::-webkit-scrollbar-track {
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  display:none;
 | 
			
		||||
}
 | 
			
		||||
.custom-scroll ::-webkit-scrollbar-thumb {
 | 
			
		||||
  background-color: #444;
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
  display:none;
 | 
			
		||||
}
 | 
			
		||||
.custom-scroll ::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
  background-color: #666;
 | 
			
		||||
  display:none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,9 @@
 | 
			
		||||
  .tab-box{
 | 
			
		||||
    align-items: center
 | 
			
		||||
    background-color: var(--card-bg)
 | 
			
		||||
    height: 100%
 | 
			
		||||
    // height: 100%
 | 
			
		||||
    // position: fixed;
 | 
			
		||||
    height: 100vh;
 | 
			
		||||
    .title{
 | 
			
		||||
      font-size: 28px
 | 
			
		||||
      font-weight: 700
 | 
			
		||||
@@ -173,6 +175,13 @@
 | 
			
		||||
  background: var(--theme-bg-all);
 | 
			
		||||
  // background-image: linear-gradient(180deg, rgba(247, 232, 255, .54), rgba(191, 223, 255, .35));
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  .loginMask{
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    z-index: 999;
 | 
			
		||||
    }
 | 
			
		||||
  .topheader{
 | 
			
		||||
    display: flex;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,25 @@
 | 
			
		||||
.page-dall {
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  // background-color: #282c34;
 | 
			
		||||
 | 
			
		||||
  .inner {
 | 
			
		||||
    display: flex;
 | 
			
		||||
 | 
			
		||||
    .sd-box {
 | 
			
		||||
      margin 10px
 | 
			
		||||
      background-color #262626
 | 
			
		||||
      border 1px solid #454545
 | 
			
		||||
      // background-color #262626
 | 
			
		||||
      // border 1px solid #454545
 | 
			
		||||
      min-width 300px
 | 
			
		||||
      max-width 300px
 | 
			
		||||
      padding 10px
 | 
			
		||||
      border-radius 10px
 | 
			
		||||
      color #ffffff;
 | 
			
		||||
      color var(--text-theme-color);
 | 
			
		||||
      font-size 14px
 | 
			
		||||
 | 
			
		||||
      h2 {
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        font-size 20px
 | 
			
		||||
        text-align center
 | 
			
		||||
        color #47fff1
 | 
			
		||||
        color#b0a0f8
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 隐藏滚动条
 | 
			
		||||
@@ -66,25 +66,24 @@
 | 
			
		||||
        text-align center
 | 
			
		||||
 | 
			
		||||
        .el-button {
 | 
			
		||||
          width 100%
 | 
			
		||||
          width 200px
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
          span {
 | 
			
		||||
            color #2D3A4B
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-form {
 | 
			
		||||
      .el-form-item__label {
 | 
			
		||||
        color #ffffff
 | 
			
		||||
        color var(--text-theme-color)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .task-list-box {
 | 
			
		||||
      background: var(--chat-bg);
 | 
			
		||||
      width 100%
 | 
			
		||||
      padding 10px
 | 
			
		||||
      color #ffffff
 | 
			
		||||
      color var(--text-theme-color)
 | 
			
		||||
      overflow-x hidden
 | 
			
		||||
 | 
			
		||||
      .task-list-inner {
 | 
			
		||||
@@ -93,26 +92,26 @@
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-tabs__item {
 | 
			
		||||
          color: #fff;
 | 
			
		||||
          color: var(--text-theme-color);
 | 
			
		||||
          font-size: 18px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .title-tabs .el-tabs__item.is-active {
 | 
			
		||||
          color: #47FFF1;
 | 
			
		||||
          color:#b0a0f8;
 | 
			
		||||
          font-size: 18px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .title-tabs .el-tabs__active-bar {
 | 
			
		||||
          background-color: #47FFF1;
 | 
			
		||||
          background-color:#b0a0f8;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-textarea {
 | 
			
		||||
          --el-input-focus-border-color: #47FFF1;
 | 
			
		||||
          // --el-input-focus-border-color:#b0a0f8;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-textarea__inner {
 | 
			
		||||
          background: transparent;
 | 
			
		||||
          color: #fff;
 | 
			
		||||
          color: var(--text-theme-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-input__wrapper {
 | 
			
		||||
@@ -141,7 +140,7 @@
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-form-item__label {
 | 
			
		||||
          color #ffffff
 | 
			
		||||
          color var(--text-theme-color)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 图片上传样式
 | 
			
		||||
 
 | 
			
		||||
@@ -365,7 +365,7 @@
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover {
 | 
			
		||||
  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
			
		||||
  box-shadow: 0 0 10px rgba(223,71,255,0.6); /* 添加阴影效果 */
 | 
			
		||||
  transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
}
 | 
			
		||||
.page-mj .inner .task-list-box .task-list-inner .job-list-box .el-image {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,30 @@
 | 
			
		||||
.page-mj {
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  // background-color: #282c34;
 | 
			
		||||
  height 100%
 | 
			
		||||
 | 
			
		||||
  .inner {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    // height: 100%
 | 
			
		||||
 | 
			
		||||
    .mj-box {
 | 
			
		||||
      margin 10px
 | 
			
		||||
      background-color #262626
 | 
			
		||||
      border 1px solid #454545
 | 
			
		||||
      // background-color #262626
 | 
			
		||||
      // border 1px solid #454545
 | 
			
		||||
      // height: calc(100vh - 50px)
 | 
			
		||||
      // overflow: scroll
 | 
			
		||||
      min-width 300px
 | 
			
		||||
      max-width 300px
 | 
			
		||||
      padding 10px
 | 
			
		||||
      border-radius 10px
 | 
			
		||||
      color #ffffff;
 | 
			
		||||
      color var(--text-theme-color);
 | 
			
		||||
      font-size 14px
 | 
			
		||||
      overflow auto
 | 
			
		||||
 | 
			
		||||
      h2 {
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        font-size 20px
 | 
			
		||||
        text-align center
 | 
			
		||||
        color #47fff1
 | 
			
		||||
        color var( --theme-textcolor-normal)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 隐藏滚动条
 | 
			
		||||
@@ -44,16 +48,20 @@
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .grid-content {
 | 
			
		||||
            background-color #383838
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
            // background-color #383838
 | 
			
		||||
            background: var(--card-bg);
 | 
			
		||||
            border-radius: 8px;
 | 
			
		||||
            padding 8px 14px
 | 
			
		||||
            display flex
 | 
			
		||||
            cursor pointer
 | 
			
		||||
            margin-bottom: 10px;
 | 
			
		||||
            border 1px solid #383838
 | 
			
		||||
            // border 1px solid #383838
 | 
			
		||||
            border 1px solid var(--chat-bg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            &:hover {
 | 
			
		||||
              background-color #585858
 | 
			
		||||
              border 1px solid var(--theme-border-hover)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .icon {
 | 
			
		||||
@@ -70,28 +78,30 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
          .grid-content.active {
 | 
			
		||||
            color #47fff1
 | 
			
		||||
            background-color #585858
 | 
			
		||||
            border 1px solid #47fff1
 | 
			
		||||
            // color #47fff1
 | 
			
		||||
            // background-color #585858
 | 
			
		||||
            border 1px solid var(--theme-border-hover)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .model {
 | 
			
		||||
            background-color #383838
 | 
			
		||||
            border 1px solid #454545
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
            background: var(--card-bg);
 | 
			
		||||
            // border 1px solid #454545
 | 
			
		||||
            border-radius 8px
 | 
			
		||||
            padding 5px
 | 
			
		||||
            margin-bottom 10px
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow column
 | 
			
		||||
            align-items center
 | 
			
		||||
            cursor pointer
 | 
			
		||||
            border 1px solid var(--chat-bg)
 | 
			
		||||
 | 
			
		||||
            &:hover {
 | 
			
		||||
              background-color #585858
 | 
			
		||||
              border 1px solid var(--theme-border-hover)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .el-image {
 | 
			
		||||
              height 30px
 | 
			
		||||
              height 40px
 | 
			
		||||
              width 100%
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -103,9 +113,10 @@
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .model.active {
 | 
			
		||||
            color #47fff1
 | 
			
		||||
            background-color #585858
 | 
			
		||||
            border 1px solid #47fff1
 | 
			
		||||
            // color #47fff1
 | 
			
		||||
            // background-color #585858
 | 
			
		||||
            border 1px solid var(--theme-border-hover)
 | 
			
		||||
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .form-item-inner {
 | 
			
		||||
@@ -113,16 +124,16 @@
 | 
			
		||||
            align-items: center
 | 
			
		||||
 | 
			
		||||
            .el-select {
 | 
			
		||||
              --el-select-input-focus-border-color: #47FFF1;
 | 
			
		||||
              --el-input-focus-border-color: #47FFF1;
 | 
			
		||||
              --el-select-input-focus-border-color: var(--el-color-primary)
 | 
			
		||||
              --el-input-focus-border-color: var(--el-color-primary)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .el-input__wrapper {
 | 
			
		||||
              background: #383838;
 | 
			
		||||
              background: var(--chat-bg)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .el-input__inner {
 | 
			
		||||
              color: #fff
 | 
			
		||||
              color: var(--text-theme-color)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .el-icon {
 | 
			
		||||
@@ -167,7 +178,7 @@
 | 
			
		||||
 | 
			
		||||
    .el-form {
 | 
			
		||||
      .el-form-item__label {
 | 
			
		||||
        color #ffffff
 | 
			
		||||
        color var(--text-theme-color)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .el-input, .el-slider {
 | 
			
		||||
@@ -182,9 +193,10 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .task-list-box {
 | 
			
		||||
      background: var(--chat-bg);
 | 
			
		||||
      width 100%
 | 
			
		||||
      padding 0 10px 10px 10px
 | 
			
		||||
      color #ffffff
 | 
			
		||||
      color var(--text-theme-color)
 | 
			
		||||
      overflow-x hidden
 | 
			
		||||
 | 
			
		||||
      .task-list-inner {
 | 
			
		||||
@@ -193,26 +205,26 @@
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-tabs__item {
 | 
			
		||||
          color: #fff;
 | 
			
		||||
          color: var(--text-theme-color);
 | 
			
		||||
          font-size: 18px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .title-tabs .el-tabs__item.is-active {
 | 
			
		||||
          color: #47FFF1;
 | 
			
		||||
          color: var( --theme-textcolor-normal);
 | 
			
		||||
          font-size: 18px;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .title-tabs .el-tabs__active-bar {
 | 
			
		||||
          background-color: #47FFF1;
 | 
			
		||||
          background-color: var( --theme-textcolor-normal);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-textarea {
 | 
			
		||||
          --el-input-focus-border-color: #47FFF1;
 | 
			
		||||
          --el-input-focus-border-color: var(--el-color-primary)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-textarea__inner {
 | 
			
		||||
          background: transparent;
 | 
			
		||||
          color: #fff;
 | 
			
		||||
          color: var(--text-theme-color);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-input__wrapper {
 | 
			
		||||
@@ -241,7 +253,7 @@
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-form-item__label {
 | 
			
		||||
          color #ffffff
 | 
			
		||||
          color var(--text-theme-color)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 图片上传样式
 | 
			
		||||
@@ -367,7 +379,7 @@
 | 
			
		||||
                            display block
 | 
			
		||||
                            cursor pointer
 | 
			
		||||
                            background-color #4E5058
 | 
			
		||||
                            color #ffffff
 | 
			
		||||
                            color var(--text-theme-color)
 | 
			
		||||
 | 
			
		||||
                            &:hover {
 | 
			
		||||
                              background-color #6D6F78
 | 
			
		||||
@@ -422,7 +434,7 @@
 | 
			
		||||
              justify-content center
 | 
			
		||||
              align-items center
 | 
			
		||||
              min-height 220px
 | 
			
		||||
              color #ffffff
 | 
			
		||||
              color var(--text-theme-color)
 | 
			
		||||
              overflow hidden
 | 
			
		||||
 | 
			
		||||
              .err-msg-container {
 | 
			
		||||
 
 | 
			
		||||
@@ -250,7 +250,7 @@
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
.page-sd .inner .task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover {
 | 
			
		||||
  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
			
		||||
  box-shadow: 0 0 10px rgba(223,71,255,0.6); /* 添加阴影效果 */
 | 
			
		||||
  transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
}
 | 
			
		||||
.page-sd .inner .task-list-box .task-list-inner .job-list-box .el-image {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
.page-sd {
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  // background-color: #282c34;
 | 
			
		||||
 | 
			
		||||
  .inner {
 | 
			
		||||
    display: flex;
 | 
			
		||||
 | 
			
		||||
    .sd-box {
 | 
			
		||||
      margin 10px
 | 
			
		||||
      background-color #262626
 | 
			
		||||
      border 1px solid #454545
 | 
			
		||||
      // background-color #262626
 | 
			
		||||
      // border 1px solid #454545
 | 
			
		||||
      min-width 300px
 | 
			
		||||
      max-width 300px
 | 
			
		||||
      padding 10px 10px 20px 10px
 | 
			
		||||
      border-radius 10px
 | 
			
		||||
      color #ffffff;
 | 
			
		||||
      color var(--text-theme-color);
 | 
			
		||||
      font-size 14px
 | 
			
		||||
      overflow auto
 | 
			
		||||
 | 
			
		||||
@@ -20,7 +20,8 @@
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        font-size 20px
 | 
			
		||||
        text-align center
 | 
			
		||||
        color #47fff1
 | 
			
		||||
        color var( --theme-textcolor-normal)
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 隐藏滚动条
 | 
			
		||||
@@ -67,25 +68,24 @@
 | 
			
		||||
        text-align center
 | 
			
		||||
 | 
			
		||||
        .el-button {
 | 
			
		||||
          width 100%
 | 
			
		||||
          width 200px
 | 
			
		||||
 | 
			
		||||
         
 | 
			
		||||
          span {
 | 
			
		||||
            color #2D3A4B
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-form {
 | 
			
		||||
      .el-form-item__label {
 | 
			
		||||
        color #ffffff
 | 
			
		||||
        color: var(--text-theme-color)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .task-list-box {
 | 
			
		||||
      background: var(--chat-bg);
 | 
			
		||||
      width 100%
 | 
			
		||||
      padding 0 10px 10px 10px
 | 
			
		||||
      color #ffffff
 | 
			
		||||
      color: var(--text-theme-color)
 | 
			
		||||
      overflow-x hidden
 | 
			
		||||
 | 
			
		||||
      .task-list-inner {
 | 
			
		||||
@@ -108,7 +108,7 @@
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-textarea {
 | 
			
		||||
          --el-input-focus-border-color: #47FFF1;
 | 
			
		||||
          // --el-input-focus-border-color: #47FFF1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-textarea__inner {
 | 
			
		||||
@@ -142,7 +142,7 @@
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-form-item__label {
 | 
			
		||||
          color #ffffff
 | 
			
		||||
          color: var(--text-theme-color)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 图片上传样式
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@
 | 
			
		||||
 | 
			
		||||
.page-images-wall {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  // background-color: #282c34;
 | 
			
		||||
 | 
			
		||||
  .inner {
 | 
			
		||||
    width 100%
 | 
			
		||||
    color #ffffff
 | 
			
		||||
    color var(--text-theme-color);
 | 
			
		||||
    overflow hidden
 | 
			
		||||
 | 
			
		||||
    .header {
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
          font-size 16px
 | 
			
		||||
 | 
			
		||||
          .el-radio {
 | 
			
		||||
            color #ffffff
 | 
			
		||||
            color var(--text-theme-color);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
@@ -68,7 +68,7 @@
 | 
			
		||||
          width 100%
 | 
			
		||||
          bottom 0
 | 
			
		||||
          left 0
 | 
			
		||||
          color #ffffff
 | 
			
		||||
          color var(--text-theme-color);
 | 
			
		||||
          padding 8px 10px
 | 
			
		||||
          line-height 1.2
 | 
			
		||||
          border-top-right-radius 10px
 | 
			
		||||
@@ -77,11 +77,15 @@
 | 
			
		||||
          span {
 | 
			
		||||
            word-break break-all
 | 
			
		||||
          }
 | 
			
		||||
          .iconfont{
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
          }
 | 
			
		||||
          .el-icon, .iconfont {
 | 
			
		||||
            top 2px
 | 
			
		||||
            cursor pointer
 | 
			
		||||
            border 1px solid #ffffff
 | 
			
		||||
           color: #fff !important;
 | 
			
		||||
            border 1px solid #fff !important;
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
            padding 2px
 | 
			
		||||
            font-size 16px;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
.page-luma {
 | 
			
		||||
  display flex
 | 
			
		||||
  height 100%
 | 
			
		||||
  background-color #0E0808
 | 
			
		||||
  // background-color #0E0808
 | 
			
		||||
  // background: var(--chat-bg);
 | 
			
		||||
 | 
			
		||||
  overflow auto
 | 
			
		||||
  //justify-content center
 | 
			
		||||
  flex-flow column
 | 
			
		||||
  align-items center
 | 
			
		||||
  background: linear-gradient(180deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3));
 | 
			
		||||
  // background: linear-gradient(180deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .prompt-box {
 | 
			
		||||
@@ -50,7 +52,7 @@
 | 
			
		||||
      .btn-swap {
 | 
			
		||||
        margin-right 10px
 | 
			
		||||
        .icon-exchange{
 | 
			
		||||
          color #ffffff
 | 
			
		||||
          color var(--text-theme-color)
 | 
			
		||||
          cursor pointer
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -60,18 +62,20 @@
 | 
			
		||||
    .prompt-container {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      .input-container {
 | 
			
		||||
        background: linear-gradient(90deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3));
 | 
			
		||||
        background: var(--chat-bg);
 | 
			
		||||
        // background: linear-gradient(90deg, rgba(75, 62, 53, 0.8), rgba(144, 50, 181, 0.3));
 | 
			
		||||
        border-radius: 28px;
 | 
			
		||||
        padding: 10px 20px;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
 | 
			
		||||
        margin-bottom: 16px;
 | 
			
		||||
        // box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
 | 
			
		||||
 | 
			
		||||
        .prompt-input {
 | 
			
		||||
          background: transparent;
 | 
			
		||||
          border: none;
 | 
			
		||||
          outline: none;
 | 
			
		||||
          color: white;
 | 
			
		||||
          color var(--text-theme-color);
 | 
			
		||||
          font-size: 14px;
 | 
			
		||||
          width: 100%;
 | 
			
		||||
          padding: 10px;
 | 
			
		||||
@@ -82,16 +86,14 @@
 | 
			
		||||
          overflow-wrap: break-word;
 | 
			
		||||
 | 
			
		||||
          scrollbar-width: none; /* 隐藏滚动条 */
 | 
			
		||||
          &::placeholder {
 | 
			
		||||
            color: rgba(255, 255, 255, 0.6);
 | 
			
		||||
          }
 | 
			
		||||
         
 | 
			
		||||
          &::-webkit-scrollbar {
 | 
			
		||||
            display: none;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .upload-icon, .send-icon {
 | 
			
		||||
          color #e1e1e1
 | 
			
		||||
          color var( --el-color-primary)
 | 
			
		||||
          .iconfont {
 | 
			
		||||
            font-size 20px
 | 
			
		||||
            cursor pointer
 | 
			
		||||
@@ -104,7 +106,7 @@
 | 
			
		||||
      .params {
 | 
			
		||||
        display flex
 | 
			
		||||
        justify-content right
 | 
			
		||||
        color #e1e1e1
 | 
			
		||||
        color var(--text-theme-color);
 | 
			
		||||
        font-size 14px
 | 
			
		||||
        padding 10px 30px
 | 
			
		||||
 | 
			
		||||
@@ -129,9 +131,9 @@
 | 
			
		||||
    padding 0 40px
 | 
			
		||||
 | 
			
		||||
    .h-title {
 | 
			
		||||
      color #ffffff
 | 
			
		||||
      color var(--text-theme-color)
 | 
			
		||||
      width 100%
 | 
			
		||||
      font-size 36px
 | 
			
		||||
      // font-size 36px
 | 
			
		||||
      text-align left
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -147,9 +149,7 @@
 | 
			
		||||
        cursor pointer
 | 
			
		||||
        margin-bottom 10px
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background-color #2A2525
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
 | 
			
		||||
        .left {
 | 
			
		||||
          .container {
 | 
			
		||||
@@ -189,7 +189,8 @@
 | 
			
		||||
              border-radius 5px
 | 
			
		||||
              background rgba(100, 100, 100, 0.3)
 | 
			
		||||
              cursor pointer
 | 
			
		||||
              color #ffffff
 | 
			
		||||
              color var(--text-theme-color)
 | 
			
		||||
              
 | 
			
		||||
              opacity 0
 | 
			
		||||
              transform: translate(-50%, 0px);
 | 
			
		||||
              transition opacity 0.3s ease 0s
 | 
			
		||||
@@ -222,7 +223,8 @@
 | 
			
		||||
            text-overflow ellipsis
 | 
			
		||||
          }
 | 
			
		||||
          .prompt {
 | 
			
		||||
            color rgb(250 247 245)
 | 
			
		||||
            color var( --el-text-color-primary)
 | 
			
		||||
            cursor: text
 | 
			
		||||
          }
 | 
			
		||||
          .failed {
 | 
			
		||||
            color #E4696B
 | 
			
		||||
@@ -248,7 +250,7 @@
 | 
			
		||||
 | 
			
		||||
              .text {
 | 
			
		||||
                margin-right 10px
 | 
			
		||||
                color #e1e1e1
 | 
			
		||||
                color #000
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -259,8 +261,9 @@
 | 
			
		||||
              color #726E6C
 | 
			
		||||
 | 
			
		||||
              &:hover {
 | 
			
		||||
                background #5f5958
 | 
			
		||||
                color #e1e1e1
 | 
			
		||||
                // background #5f5958
 | 
			
		||||
                // color #e1e1e1
 | 
			
		||||
                color:var(--el-color-primary)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .downloading {
 | 
			
		||||
@@ -317,7 +320,7 @@
 | 
			
		||||
    //        border-radius 20px
 | 
			
		||||
    //        padding 3px 15px
 | 
			
		||||
    //        cursor pointer
 | 
			
		||||
    //        color #ffffff
 | 
			
		||||
    //        color var(--text-theme-color)
 | 
			
		||||
    //        font-size 14px
 | 
			
		||||
    //
 | 
			
		||||
    //        .iconfont {
 | 
			
		||||
@@ -344,14 +347,14 @@
 | 
			
		||||
 | 
			
		||||
  .btn {
 | 
			
		||||
    margin-right 10px
 | 
			
		||||
    background-color #363030
 | 
			
		||||
    border none
 | 
			
		||||
    border-radius 5px
 | 
			
		||||
    padding 5px 10px
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    color #000
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color #5F5958
 | 
			
		||||
      opacity: 0.7
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
.page-mark-map {
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  // background-color: #282c34;
 | 
			
		||||
  height 100%
 | 
			
		||||
 | 
			
		||||
  .inner {
 | 
			
		||||
@@ -7,20 +7,20 @@
 | 
			
		||||
 | 
			
		||||
    .mark-map-box {
 | 
			
		||||
      margin 10px
 | 
			
		||||
      background-color #262626
 | 
			
		||||
      border 1px solid #454545
 | 
			
		||||
      // background-color #262626
 | 
			
		||||
      // border 1px solid #454545
 | 
			
		||||
      min-width 300px
 | 
			
		||||
      max-width 300px
 | 
			
		||||
      padding 10px
 | 
			
		||||
      border-radius 10px
 | 
			
		||||
      color #ffffff;
 | 
			
		||||
      color var(--text-theme-color);
 | 
			
		||||
      font-size 14px
 | 
			
		||||
 | 
			
		||||
      h2 {
 | 
			
		||||
        font-weight: bold;
 | 
			
		||||
        font-size 20px
 | 
			
		||||
        text-align center
 | 
			
		||||
        color #47fff1
 | 
			
		||||
        color var( --theme-textcolor-normal)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 隐藏滚动条
 | 
			
		||||
@@ -43,7 +43,7 @@
 | 
			
		||||
            width 100%
 | 
			
		||||
 | 
			
		||||
            span {
 | 
			
		||||
              color #2D3A4B
 | 
			
		||||
              color #fff
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@@ -61,13 +61,15 @@
 | 
			
		||||
 | 
			
		||||
    .el-form {
 | 
			
		||||
      .el-form-item__label {
 | 
			
		||||
        color #ffffff
 | 
			
		||||
        color var(--text-theme-color)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .chat-box {
 | 
			
		||||
      width 100%
 | 
			
		||||
      background: var(--chat-bg);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
      .top-bar {
 | 
			
		||||
        display flex
 | 
			
		||||
@@ -77,13 +79,13 @@
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .markdown {
 | 
			
		||||
        color #ffffff
 | 
			
		||||
        color var(--text-theme-color)
 | 
			
		||||
        display flex
 | 
			
		||||
        justify-content center
 | 
			
		||||
        align-items center
 | 
			
		||||
 | 
			
		||||
        h1 {
 | 
			
		||||
          color: #47fff1;
 | 
			
		||||
          color: var( --theme-textcolor-normal);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        h2 {
 | 
			
		||||
@@ -116,7 +118,7 @@
 | 
			
		||||
 | 
			
		||||
        .markmap {
 | 
			
		||||
          width 100%
 | 
			
		||||
          color #ffffff
 | 
			
		||||
          color var(--text-theme-color)
 | 
			
		||||
          font-size 12px
 | 
			
		||||
 | 
			
		||||
          .markmap-foreign {
 | 
			
		||||
@@ -139,7 +141,7 @@
 | 
			
		||||
 | 
			
		||||
            .mm-toolbar-item {
 | 
			
		||||
              cursor pointer
 | 
			
		||||
              color var(--el-color-white)
 | 
			
		||||
              color var( --el-text-color-primary)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,11 @@
 | 
			
		||||
        list-style: normal;
 | 
			
		||||
    }
 | 
			
		||||
    a {
 | 
			
		||||
        color: #42b983;
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
        
 | 
			
		||||
        color :var(--a-link-color);
 | 
			
		||||
        text-decoration: underline;
 | 
			
		||||
       
 | 
			
		||||
        padding: 0 2px;
 | 
			
		||||
        text-decoration: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    h1,
 | 
			
		||||
 
 | 
			
		||||
@@ -141,7 +141,7 @@
 | 
			
		||||
  margin-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
.member .inner .product-box .list-box .product-item:hover {
 | 
			
		||||
  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
			
		||||
  box-shadow: 0 0 10px var(--shadow-color); /* 添加阴影效果 */
 | 
			
		||||
  transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
}
 | 
			
		||||
.member .inner .product-box .headline {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
.member {
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  // background-color: #282c34;
 | 
			
		||||
  height 100%
 | 
			
		||||
 | 
			
		||||
  .title {
 | 
			
		||||
    text-align center
 | 
			
		||||
    background-color #25272d
 | 
			
		||||
    font-size 24px
 | 
			
		||||
    color #ffffff
 | 
			
		||||
    color var(--text-theme-color)
 | 
			
		||||
    padding 10px
 | 
			
		||||
    border-bottom 1px solid #3c3c3c
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .inner {
 | 
			
		||||
    color #ffffff
 | 
			
		||||
    color var(--text-theme-color)
 | 
			
		||||
    padding 15px 0 15px 15px;
 | 
			
		||||
    overflow-x hidden
 | 
			
		||||
    overflow-y visible
 | 
			
		||||
@@ -22,13 +22,13 @@
 | 
			
		||||
    .user-profile {
 | 
			
		||||
      padding 10px 20px 20px 20px
 | 
			
		||||
      width 300px
 | 
			
		||||
      background-color #393F4A
 | 
			
		||||
      color #ffffff
 | 
			
		||||
      background-color var(--chat-bg)
 | 
			
		||||
      color var(--text-theme-color)
 | 
			
		||||
      border-radius 10px
 | 
			
		||||
      //height 100vh
 | 
			
		||||
 | 
			
		||||
      .el-form-item__label {
 | 
			
		||||
        color #ffffff
 | 
			
		||||
        color var(--text-theme-color)
 | 
			
		||||
        justify-content start
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -58,7 +58,8 @@
 | 
			
		||||
 | 
			
		||||
      .list-box {
 | 
			
		||||
        .product-item {
 | 
			
		||||
          border 1px solid #666666
 | 
			
		||||
          // border 1px solid #666666
 | 
			
		||||
          background-color var(--chat-bg)
 | 
			
		||||
          border-radius 6px
 | 
			
		||||
          overflow hidden
 | 
			
		||||
          cursor pointer
 | 
			
		||||
@@ -87,7 +88,7 @@
 | 
			
		||||
              text-align center
 | 
			
		||||
              font-size 16px
 | 
			
		||||
              font-weight bold
 | 
			
		||||
              color #47fff1
 | 
			
		||||
              color var( --el-color-primary)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@@ -138,14 +139,14 @@
 | 
			
		||||
                padding 0
 | 
			
		||||
 | 
			
		||||
                .icon-alipay,.icon-wechat-pay {
 | 
			
		||||
                  color #ffffff
 | 
			
		||||
                  color var(--text-theme-color)
 | 
			
		||||
                }
 | 
			
		||||
                .icon-qq {
 | 
			
		||||
                  color #15A6E8
 | 
			
		||||
                  font-size 24px
 | 
			
		||||
                }
 | 
			
		||||
                .icon-jd-pay {
 | 
			
		||||
                  color #ffffff
 | 
			
		||||
                  color var(--text-theme-color)
 | 
			
		||||
                  font-size 24px
 | 
			
		||||
                }
 | 
			
		||||
                .icon-douyin {
 | 
			
		||||
@@ -161,7 +162,7 @@
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          &:hover {
 | 
			
		||||
            box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
 | 
			
		||||
            // box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
 | 
			
		||||
            transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
    height 200px
 | 
			
		||||
    overflow hidden
 | 
			
		||||
    padding 2px
 | 
			
		||||
    background-color #555555
 | 
			
		||||
    background-color var( --gray-btn-bg)
 | 
			
		||||
 | 
			
		||||
    .job-item-inner {
 | 
			
		||||
      position relative
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
.page-suno {
 | 
			
		||||
  display flex
 | 
			
		||||
  height 100%
 | 
			
		||||
  background-color #0E0808
 | 
			
		||||
  // background-color #0E0808
 | 
			
		||||
  overflow auto
 | 
			
		||||
 | 
			
		||||
  .left-bar {
 | 
			
		||||
@@ -24,7 +24,7 @@
 | 
			
		||||
 | 
			
		||||
    .params {
 | 
			
		||||
      padding 20px 0
 | 
			
		||||
      color rgb(250 247 245)
 | 
			
		||||
      color: var(--text-theme-color);
 | 
			
		||||
      position relative
 | 
			
		||||
 | 
			
		||||
      .pure-music {
 | 
			
		||||
@@ -78,6 +78,7 @@
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        .song {
 | 
			
		||||
          display flex
 | 
			
		||||
          padding 10px
 | 
			
		||||
@@ -137,6 +138,8 @@
 | 
			
		||||
          bottom 10px
 | 
			
		||||
          font-size 12px
 | 
			
		||||
          padding 2px 5px
 | 
			
		||||
          background-color var(--sm-btn-bg)
 | 
			
		||||
    color: #fff
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -154,12 +157,16 @@
 | 
			
		||||
          .tag {
 | 
			
		||||
            margin-right 10px
 | 
			
		||||
            word-break keep-all
 | 
			
		||||
            background-color #312C2C
 | 
			
		||||
            color #e1e1e1
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
            background: var(--card-bg);
 | 
			
		||||
            color:var(--theme-text-color-primary);
 | 
			
		||||
            opacity 0.7
 | 
			
		||||
            border-radius 8px
 | 
			
		||||
            padding 3px 6px
 | 
			
		||||
            cursor pointer
 | 
			
		||||
            font-size 13px
 | 
			
		||||
            &:hover{
 | 
			
		||||
              color:var( --el-color-primary)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -169,6 +176,8 @@
 | 
			
		||||
    width 100%
 | 
			
		||||
    color rgb(250 247 245)
 | 
			
		||||
    overflow auto
 | 
			
		||||
    background: var(--chat-bg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .list-box {
 | 
			
		||||
      padding 0 0 0 20px
 | 
			
		||||
@@ -180,7 +189,7 @@
 | 
			
		||||
        margin-bottom 10px
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background-color #2A2525
 | 
			
		||||
         background: rgba(188,149,236,0.08)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .left {
 | 
			
		||||
@@ -247,18 +256,18 @@
 | 
			
		||||
            font-weight 700
 | 
			
		||||
 | 
			
		||||
            a {
 | 
			
		||||
              color rgb(250 247 245)
 | 
			
		||||
              color vae( --a-link-color)
 | 
			
		||||
              &:hover {
 | 
			
		||||
                text-decoration underline
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .model {
 | 
			
		||||
              color #E2E8F0
 | 
			
		||||
              background-color #1C1616
 | 
			
		||||
              border 1px solid #8f8f8f
 | 
			
		||||
              color #8f8f8f
 | 
			
		||||
              // background-color #1C1616
 | 
			
		||||
              // border 1px solid #8f8f8f
 | 
			
		||||
              font-weight normal
 | 
			
		||||
              font-size 14px
 | 
			
		||||
              font-size 12px
 | 
			
		||||
              padding 1px 3px
 | 
			
		||||
              border-radius 5px
 | 
			
		||||
              margin-left 10px
 | 
			
		||||
@@ -271,7 +280,7 @@
 | 
			
		||||
 | 
			
		||||
          .tags {
 | 
			
		||||
            font-size 14px
 | 
			
		||||
            color #d1d1d1
 | 
			
		||||
            color var(--el-text-color-primary)
 | 
			
		||||
            padding 3px 0
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
@@ -303,8 +312,9 @@
 | 
			
		||||
              color #726E6C
 | 
			
		||||
 | 
			
		||||
              &:hover {
 | 
			
		||||
                background #5f5958
 | 
			
		||||
                color #e1e1e1
 | 
			
		||||
                // background #5f5958
 | 
			
		||||
                // color #e1e1e1
 | 
			
		||||
                color:var(--el-color-primary)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .downloading {
 | 
			
		||||
@@ -372,14 +382,25 @@
 | 
			
		||||
 | 
			
		||||
  .btn {
 | 
			
		||||
    margin-right 10px
 | 
			
		||||
    background-color #363030
 | 
			
		||||
    color: #000
 | 
			
		||||
    border none
 | 
			
		||||
    border-radius 5px
 | 
			
		||||
    padding 5px 10px
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color #5F5958
 | 
			
		||||
      opacity :0.8
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.submit-btn {
 | 
			
		||||
  display flex
 | 
			
		||||
  align-items: center
 | 
			
		||||
  margin: 20px 0
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .el-button {
 | 
			
		||||
    width 200px
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -20,7 +20,9 @@
 | 
			
		||||
  }
 | 
			
		||||
  --btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
 | 
			
		||||
  --border-active:rgba(255, 255, 255, 0.1);
 | 
			
		||||
  --card-bg: rgba(17, 28, 68, 1);
 | 
			
		||||
  // --card-bg: rgba(17, 28, 68, 1);
 | 
			
		||||
  --card-bg: #1f243f;
 | 
			
		||||
   --card-bg-table: rgba(17, 28, 68, 1);
 | 
			
		||||
  --theme-bg:rgb(13, 20, 53);
 | 
			
		||||
  --theme-bg-all:rgb(13, 20, 53);
 | 
			
		||||
   --sign-bg: rgba(27, 37, 75, 1);
 | 
			
		||||
@@ -28,7 +30,7 @@
 | 
			
		||||
  --text-color-primary: #d1c7ff;
 | 
			
		||||
  --el-text-color-regular: rgba(163, 174, 208, 1)
 | 
			
		||||
  --el-border-color:rgb(79, 80, 85)
 | 
			
		||||
  --el-text-color-primary: #fff;
 | 
			
		||||
  --el-text-color-primary: #fff;//黑白切换
 | 
			
		||||
   --el-bg-color-overlay: rgba(17, 28, 68, 1);
 | 
			
		||||
   --el-border-color-light: rgba(255, 255, 255, 0.2);
 | 
			
		||||
  --line-box:rgba(255, 255, 255, 0.1);
 | 
			
		||||
@@ -39,6 +41,7 @@
 | 
			
		||||
--el-color-primary-light-9:rgba(86, 86, 95, .2);
 | 
			
		||||
--chat-wel-bg:#2d2f388a;
 | 
			
		||||
  --theme-text-color-secondary: #a3aed0;
 | 
			
		||||
  // --el-pagination-button-bg-color: rgba(86,86,95,0.2); 
 | 
			
		||||
 | 
			
		||||
   //layout 
 | 
			
		||||
  .more-menus li.moreTitle,
 | 
			
		||||
@@ -52,9 +55,14 @@
 | 
			
		||||
  .more-menus span.title{
 | 
			
		||||
    color:#000;
 | 
			
		||||
  }
 | 
			
		||||
  .menu-list .menu-list-item.active .el-icon{
 | 
			
		||||
    background: #0080006e;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  --theme-text-color-primary: #fff;
 | 
			
		||||
  --theme-text-primary: #f3f3f3;
 | 
			
		||||
  --chat-content-bg:rgba(86, 86, 95, .2);
 | 
			
		||||
  --chat-content-bg-list:rgba(86, 86, 95, .2);
 | 
			
		||||
  
 | 
			
		||||
  // --theme-text-tertiary: #e1e1e1;
 | 
			
		||||
}
 | 
			
		||||
@@ -45,7 +45,7 @@
 | 
			
		||||
  --chat-list-bg: #0302020a;
 | 
			
		||||
--chat-content-bg-list:#fff;
 | 
			
		||||
--chat-wel-bg:rgba(247, 247, 248, 1);
 | 
			
		||||
 | 
			
		||||
--el-pagination-button-bg-color: rgba(86,86,95,0.2);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@
 | 
			
		||||
 | 
			
		||||
      .animate {
 | 
			
		||||
        &:hover {
 | 
			
		||||
          box-shadow: 0 0 10px rgba(71, 255, 241, 0.6); /* 添加阴影效果 */
 | 
			
		||||
          box-shadow: 0 0 10px var(--shadow-color); /* 添加阴影效果 */
 | 
			
		||||
          transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								web/src/assets/img/no-data.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 39 KiB  | 
@@ -256,7 +256,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
 | 
			
		||||
            code {
 | 
			
		||||
              color:var(--theme-text-color-primary);
 | 
			
		||||
              background-color #e7e7e8
 | 
			
		||||
              background-color var(--el-color-primary-light-3)
 | 
			
		||||
              padding 0 3px;
 | 
			
		||||
              border-radius 5px;
 | 
			
		||||
            }
 | 
			
		||||
@@ -348,7 +348,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
          padding 10px 10px 10px 0;
 | 
			
		||||
 | 
			
		||||
          .bar-item {
 | 
			
		||||
            background-color #e7e7e8;
 | 
			
		||||
            background-color var( --little-btn-bg);
 | 
			
		||||
            color #888
 | 
			
		||||
            padding 3px 5px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
@@ -433,7 +433,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
 | 
			
		||||
              code {
 | 
			
		||||
                color:var(--theme-text-color-primary);
 | 
			
		||||
                background-color #e7e7e8
 | 
			
		||||
                background-color var( --little-btn-bg)
 | 
			
		||||
                padding 0 3px;
 | 
			
		||||
                border-radius 5px;
 | 
			
		||||
              }
 | 
			
		||||
@@ -539,7 +539,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .bar-item.bg {
 | 
			
		||||
            background-color #e7e7e8
 | 
			
		||||
            background-color var( --gray-btn-bg)
 | 
			
		||||
            cursor pointer
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,76 +1,90 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="invite-list" v-loading="loading">
 | 
			
		||||
    <el-row v-if="items.length > 0">
 | 
			
		||||
      <el-table :data="items" :row-key="row => row.id" table-layout="auto" border
 | 
			
		||||
                style="--el-table-border-color:#373C47;
 | 
			
		||||
                --el-table-tr-bg-color:#2D323B;
 | 
			
		||||
                --el-table-row-hover-bg-color:#373C47;
 | 
			
		||||
                --el-table-header-bg-color:#474E5C;
 | 
			
		||||
                --el-table-text-color:#d1d1d1">
 | 
			
		||||
        <el-table-column prop="username" label="用户"/>
 | 
			
		||||
        <el-table-column prop="invite_code" label="邀请码"/>
 | 
			
		||||
        <el-table-column prop="remark" label="邀请奖励"/>
 | 
			
		||||
      <el-table
 | 
			
		||||
        :data="items"
 | 
			
		||||
        :row-key="(row) => row.id"
 | 
			
		||||
        table-layout="auto"
 | 
			
		||||
        border
 | 
			
		||||
        style="
 | 
			
		||||
          --el-table-border-color: #373c47;
 | 
			
		||||
          --el-table-tr-bg-color: #2d323b;
 | 
			
		||||
          --el-table-row-hover-bg-color: #373c47;
 | 
			
		||||
          --el-table-header-bg-color: #474e5c;
 | 
			
		||||
          --el-table-text-color: #d1d1d1;
 | 
			
		||||
        "
 | 
			
		||||
      >
 | 
			
		||||
        <el-table-column prop="username" label="用户" />
 | 
			
		||||
        <el-table-column prop="invite_code" label="邀请码" />
 | 
			
		||||
        <el-table-column prop="remark" label="邀请奖励" />
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="注册时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
            <span>{{ dateFormat(scope.row["created_at"]) }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
      </el-table>
 | 
			
		||||
    </el-row>
 | 
			
		||||
    <el-empty :image-size="100" v-else/>
 | 
			
		||||
    <el-empty :image-size="100" :image="nodata" description="暂无数据" v-else />
 | 
			
		||||
    <div class="pagination">
 | 
			
		||||
      <el-pagination v-if="total > 0" background
 | 
			
		||||
      <el-pagination
 | 
			
		||||
        v-if="total > 0"
 | 
			
		||||
        background
 | 
			
		||||
        layout="total,prev, pager, next"
 | 
			
		||||
        :hide-on-single-page="true"
 | 
			
		||||
        v-model:current-page="page"
 | 
			
		||||
        v-model:page-size="pageSize"
 | 
			
		||||
        style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
 | 
			
		||||
        @current-change="fetchData()"
 | 
			
		||||
                     :total="total"/>
 | 
			
		||||
 | 
			
		||||
        :total="total"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import nodata from "@/assets/img/no-data.png";
 | 
			
		||||
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { httpGet } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { dateFormat } from "@/utils/libs";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
 | 
			
		||||
const items = ref([])
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
const page = ref(1)
 | 
			
		||||
const pageSize = ref(10)
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const items = ref([]);
 | 
			
		||||
const total = ref(0);
 | 
			
		||||
const page = ref(1);
 | 
			
		||||
const pageSize = ref(10);
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  fetchData()
 | 
			
		||||
  const clipboard = new Clipboard('.copy-order-no');
 | 
			
		||||
  clipboard.on('success', () => {
 | 
			
		||||
  fetchData();
 | 
			
		||||
  const clipboard = new Clipboard(".copy-order-no");
 | 
			
		||||
  clipboard.on("success", () => {
 | 
			
		||||
    ElMessage.success("复制成功!");
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
  clipboard.on("error", () => {
 | 
			
		||||
    ElMessage.error("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 获取数据
 | 
			
		||||
const fetchData = () => {
 | 
			
		||||
  httpGet('/api/invite/list', {page: page.value, page_size: pageSize.value}).then((res) => {
 | 
			
		||||
  httpGet("/api/invite/list", { page: page.value, page_size: pageSize.value })
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data) {
 | 
			
		||||
      items.value = res.data.items
 | 
			
		||||
      total.value = res.data.total
 | 
			
		||||
      page.value = res.data.page
 | 
			
		||||
      pageSize.value = res.data.page_size
 | 
			
		||||
        items.value = res.data.items;
 | 
			
		||||
        total.value = res.data.total;
 | 
			
		||||
        page.value = res.data.page;
 | 
			
		||||
        pageSize.value = res.data.page_size;
 | 
			
		||||
      }
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container class="realtime-conversation" :style="{height: height}">
 | 
			
		||||
  <el-container class="realtime-conversation" :style="{ height: height }">
 | 
			
		||||
    <!-- connection animation -->
 | 
			
		||||
    <el-container class="connection-container" v-if="!isConnected">
 | 
			
		||||
      <div class="phone-container">
 | 
			
		||||
@@ -36,14 +36,18 @@
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="call-controls">
 | 
			
		||||
        <el-tooltip content="长按发送语音" placement="top" effect="light">
 | 
			
		||||
        <el-tooltip content="长按发送语音" placement="top">
 | 
			
		||||
          <ripple-button>
 | 
			
		||||
            <button class="call-button answer" @mousedown="startRecording" @mouseup="stopRecording">
 | 
			
		||||
            <button
 | 
			
		||||
              class="call-button answer"
 | 
			
		||||
              @mousedown="startRecording"
 | 
			
		||||
              @mouseup="stopRecording"
 | 
			
		||||
            >
 | 
			
		||||
              <i class="iconfont icon-mic-bold"></i>
 | 
			
		||||
            </button>
 | 
			
		||||
          </ripple-button>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
        <el-tooltip content="结束通话" placement="top" effect="light">
 | 
			
		||||
        <el-tooltip content="结束通话" placement="top">
 | 
			
		||||
          <button class="call-button hangup" @click="hangUp">
 | 
			
		||||
            <i class="iconfont icon-hung-up"></i>
 | 
			
		||||
          </button>
 | 
			
		||||
@@ -51,32 +55,31 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </el-container>
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import RippleButton from "@/components/ui/RippleButton.vue";
 | 
			
		||||
import { ref, onMounted, onUnmounted } from 'vue';
 | 
			
		||||
import { RealtimeClient } from '@openai/realtime-api-beta';
 | 
			
		||||
import { WavRecorder, WavStreamPlayer } from '@/lib/wavtools/index.js';
 | 
			
		||||
import { instructions } from '@/utils/conversation_config.js';
 | 
			
		||||
import { WavRenderer } from '@/utils/wav_renderer';
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
import {getUserToken} from "@/store/session";
 | 
			
		||||
import { ref, onMounted, onUnmounted } from "vue";
 | 
			
		||||
import { RealtimeClient } from "@openai/realtime-api-beta";
 | 
			
		||||
import { WavRecorder, WavStreamPlayer } from "@/lib/wavtools/index.js";
 | 
			
		||||
import { instructions } from "@/utils/conversation_config.js";
 | 
			
		||||
import { WavRenderer } from "@/utils/wav_renderer";
 | 
			
		||||
import { showMessageError } from "@/utils/dialog";
 | 
			
		||||
import { getUserToken } from "@/store/session";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-unused-vars,no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  height: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '100vh'
 | 
			
		||||
    default: "100vh"
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const emits = defineEmits(['close']);
 | 
			
		||||
const emits = defineEmits(["close"]);
 | 
			
		||||
 | 
			
		||||
/********************** connection animation code *************************/
 | 
			
		||||
const fullText = "正在接通中...";
 | 
			
		||||
const connectingText = ref("")
 | 
			
		||||
const connectingText = ref("");
 | 
			
		||||
let index = 0;
 | 
			
		||||
const typeText = () => {
 | 
			
		||||
  if (index < fullText.length) {
 | 
			
		||||
@@ -85,12 +88,12 @@ const typeText = () => {
 | 
			
		||||
    setTimeout(typeText, 200); // 每300毫秒显示一个字
 | 
			
		||||
  } else {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      connectingText.value = '';
 | 
			
		||||
      connectingText.value = "";
 | 
			
		||||
      index = 0;
 | 
			
		||||
      typeText();
 | 
			
		||||
    }, 1000); // 等待1秒后重新开始
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
/*************************** end of code ****************************************/
 | 
			
		||||
 | 
			
		||||
/********************** conversation process code ***************************/
 | 
			
		||||
@@ -102,31 +105,29 @@ const animateVoice = () => {
 | 
			
		||||
  rightVoiceActive.value = Math.random() > 0.5;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const wavRecorder = ref(new WavRecorder({ sampleRate: 24000 }));
 | 
			
		||||
const wavStreamPlayer = ref(new WavStreamPlayer({ sampleRate: 24000 }));
 | 
			
		||||
let host = process.env.VUE_APP_WS_HOST
 | 
			
		||||
if (host === '') {
 | 
			
		||||
  if (location.protocol === 'https:') {
 | 
			
		||||
    host = 'wss://' + location.host;
 | 
			
		||||
let host = process.env.VUE_APP_WS_HOST;
 | 
			
		||||
if (host === "") {
 | 
			
		||||
  if (location.protocol === "https:") {
 | 
			
		||||
    host = "wss://" + location.host;
 | 
			
		||||
  } else {
 | 
			
		||||
    host = 'ws://' + location.host;
 | 
			
		||||
    host = "ws://" + location.host;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
const client = ref(
 | 
			
		||||
  new RealtimeClient({
 | 
			
		||||
    url: `${host}/api/realtime`,
 | 
			
		||||
    apiKey: getUserToken(),
 | 
			
		||||
      dangerouslyAllowAPIKeyInBrowser: true,
 | 
			
		||||
    dangerouslyAllowAPIKeyInBrowser: true
 | 
			
		||||
  })
 | 
			
		||||
);
 | 
			
		||||
// // Set up client instructions and transcription
 | 
			
		||||
client.value.updateSession({
 | 
			
		||||
  instructions: instructions,
 | 
			
		||||
  turn_detection: null,
 | 
			
		||||
  input_audio_transcription: { model: 'whisper-1' },
 | 
			
		||||
  voice: 'alloy',
 | 
			
		||||
  input_audio_transcription: { model: "whisper-1" },
 | 
			
		||||
  voice: "alloy"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// set voice wave canvas
 | 
			
		||||
@@ -137,50 +138,52 @@ const isRecording = ref(false);
 | 
			
		||||
const backgroundAudio = ref(null);
 | 
			
		||||
const hangUpAudio = ref(null);
 | 
			
		||||
function sleep(ms) {
 | 
			
		||||
  return new Promise(resolve => setTimeout(resolve, ms));
 | 
			
		||||
  return new Promise((resolve) => setTimeout(resolve, ms));
 | 
			
		||||
}
 | 
			
		||||
const connect = async () => {
 | 
			
		||||
  if (isConnected.value) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // 播放背景音乐
 | 
			
		||||
  if (backgroundAudio.value) {
 | 
			
		||||
    backgroundAudio.value.play().catch(error => {
 | 
			
		||||
      console.error('播放失败,可能是浏览器的自动播放策略导致的:', error);
 | 
			
		||||
    backgroundAudio.value.play().catch((error) => {
 | 
			
		||||
      console.error("播放失败,可能是浏览器的自动播放策略导致的:", error);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  // 模拟拨号延时
 | 
			
		||||
  await sleep(3000)
 | 
			
		||||
  await sleep(3000);
 | 
			
		||||
  try {
 | 
			
		||||
    await client.value.connect();
 | 
			
		||||
    await wavRecorder.value.begin();
 | 
			
		||||
    await wavStreamPlayer.value.connect();
 | 
			
		||||
    console.log("对话连接成功!")
 | 
			
		||||
    console.log("对话连接成功!");
 | 
			
		||||
    if (!client.value.isConnected()) {
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isConnected.value = true;
 | 
			
		||||
    backgroundAudio.value?.pause()
 | 
			
		||||
    backgroundAudio.value.currentTime = 0
 | 
			
		||||
    backgroundAudio.value?.pause();
 | 
			
		||||
    backgroundAudio.value.currentTime = 0;
 | 
			
		||||
    client.value.sendUserMessageContent([
 | 
			
		||||
      {
 | 
			
		||||
        type: 'input_text',
 | 
			
		||||
        text: '你好,我是极客学长!',
 | 
			
		||||
      },
 | 
			
		||||
        type: "input_text",
 | 
			
		||||
        text: "你好,我是极客学长!"
 | 
			
		||||
      }
 | 
			
		||||
    ]);
 | 
			
		||||
    if (client.value.getTurnDetectionType() === 'server_vad') {
 | 
			
		||||
      await wavRecorder.value.record((data) => client.value.appendInputAudio(data.mono));
 | 
			
		||||
    if (client.value.getTurnDetectionType() === "server_vad") {
 | 
			
		||||
      await wavRecorder.value.record((data) =>
 | 
			
		||||
        client.value.appendInputAudio(data.mono)
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 开始语音输入
 | 
			
		||||
const startRecording = async () => {
 | 
			
		||||
  if (isRecording.value) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isRecording.value = true;
 | 
			
		||||
@@ -190,9 +193,11 @@ const startRecording = async () => {
 | 
			
		||||
      const { trackId, offset } = trackSampleOffset;
 | 
			
		||||
      client.value.cancelResponse(trackId, offset);
 | 
			
		||||
    }
 | 
			
		||||
   await wavRecorder.value.record((data) => client.value.appendInputAudio(data.mono));
 | 
			
		||||
    await wavRecorder.value.record((data) =>
 | 
			
		||||
      client.value.appendInputAudio(data.mono)
 | 
			
		||||
    );
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
   console.error(e)
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -203,7 +208,7 @@ const stopRecording = async () => {
 | 
			
		||||
    await wavRecorder.value.pause();
 | 
			
		||||
    client.value.createResponse();
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -232,13 +237,13 @@ const initialize = async () => {
 | 
			
		||||
          canvas.width = canvas.offsetWidth;
 | 
			
		||||
          canvas.height = canvas.offsetHeight;
 | 
			
		||||
        }
 | 
			
		||||
        const ctx = canvas.getContext('2d');
 | 
			
		||||
        const ctx = canvas.getContext("2d");
 | 
			
		||||
        if (ctx) {
 | 
			
		||||
          ctx.clearRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
          const result = wavRecorder.value.recording
 | 
			
		||||
              ? wavRecorder.value.getFrequencies('voice')
 | 
			
		||||
            ? wavRecorder.value.getFrequencies("voice")
 | 
			
		||||
            : { values: new Float32Array([0]) };
 | 
			
		||||
          WavRenderer.drawBars(canvas, ctx, result.values, '#0099ff', 10, 0, 8);
 | 
			
		||||
          WavRenderer.drawBars(canvas, ctx, result.values, "#0099ff", 10, 0, 8);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (serverCanvasRef.value) {
 | 
			
		||||
@@ -247,13 +252,13 @@ const initialize = async () => {
 | 
			
		||||
          canvas.width = canvas.offsetWidth;
 | 
			
		||||
          canvas.height = canvas.offsetHeight;
 | 
			
		||||
        }
 | 
			
		||||
        const ctx = canvas.getContext('2d');
 | 
			
		||||
        const ctx = canvas.getContext("2d");
 | 
			
		||||
        if (ctx) {
 | 
			
		||||
          ctx.clearRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
          const result = wavStreamPlayer.value.analyser
 | 
			
		||||
              ?  wavStreamPlayer.value.getFrequencies('voice')
 | 
			
		||||
            ? wavStreamPlayer.value.getFrequencies("voice")
 | 
			
		||||
            : { values: new Float32Array([0]) };
 | 
			
		||||
          WavRenderer.drawBars(canvas, ctx, result.values, '#009900', 10, 0, 8);
 | 
			
		||||
          WavRenderer.drawBars(canvas, ctx, result.values, "#009900", 10, 0, 8);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      requestAnimationFrame(render);
 | 
			
		||||
@@ -261,17 +266,17 @@ const initialize = async () => {
 | 
			
		||||
  };
 | 
			
		||||
  render();
 | 
			
		||||
 | 
			
		||||
  client.value.on('error', (event) => {
 | 
			
		||||
    showMessageError(event.error)
 | 
			
		||||
  client.value.on("error", (event) => {
 | 
			
		||||
    showMessageError(event.error);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  client.value.on('realtime.event', (re) => {
 | 
			
		||||
    if (re.event.type === 'error') {
 | 
			
		||||
      showMessageError(re.event.error)
 | 
			
		||||
  client.value.on("realtime.event", (re) => {
 | 
			
		||||
    if (re.event.type === "error") {
 | 
			
		||||
      showMessageError(re.event.error);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  client.value.on('conversation.interrupted', async () => {
 | 
			
		||||
  client.value.on("conversation.interrupted", async () => {
 | 
			
		||||
    const trackSampleOffset = await wavStreamPlayer.value.interrupt();
 | 
			
		||||
    if (trackSampleOffset?.trackId) {
 | 
			
		||||
      const { trackId, offset } = trackSampleOffset;
 | 
			
		||||
@@ -279,21 +284,20 @@ const initialize = async () => {
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  client.value.on('conversation.updated', async ({ item, delta }) => {
 | 
			
		||||
  client.value.on("conversation.updated", async ({ item, delta }) => {
 | 
			
		||||
    // console.log('item updated', item, delta)
 | 
			
		||||
    if (delta?.audio) {
 | 
			
		||||
      wavStreamPlayer.value.add16BitPCM(delta.audio, item.id);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const voiceInterval = ref(null);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initialize()
 | 
			
		||||
  initialize();
 | 
			
		||||
  // 启动聊天进行中的动画
 | 
			
		||||
  voiceInterval.value = setInterval(animateVoice, 200);
 | 
			
		||||
  typeText()
 | 
			
		||||
  typeText();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
@@ -304,32 +308,31 @@ onUnmounted(() => {
 | 
			
		||||
// 挂断通话
 | 
			
		||||
const hangUp = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    isConnected.value = false
 | 
			
		||||
    isConnected.value = false;
 | 
			
		||||
    // 停止播放拨号音乐
 | 
			
		||||
    if (backgroundAudio.value?.currentTime) {
 | 
			
		||||
      backgroundAudio.value?.pause()
 | 
			
		||||
      backgroundAudio.value.currentTime = 0
 | 
			
		||||
      backgroundAudio.value?.pause();
 | 
			
		||||
      backgroundAudio.value.currentTime = 0;
 | 
			
		||||
    }
 | 
			
		||||
    // 断开客户端的连接
 | 
			
		||||
    client.value.reset()
 | 
			
		||||
    client.value.reset();
 | 
			
		||||
    // 中断语音输入和输出服务
 | 
			
		||||
    await wavRecorder.value.end()
 | 
			
		||||
    await wavStreamPlayer.value.interrupt()
 | 
			
		||||
    await wavRecorder.value.end();
 | 
			
		||||
    await wavStreamPlayer.value.interrupt();
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  } finally {
 | 
			
		||||
    // 播放挂断音乐
 | 
			
		||||
    hangUpAudio.value?.play()
 | 
			
		||||
    emits('close')
 | 
			
		||||
    hangUpAudio.value?.play();
 | 
			
		||||
    emits("close");
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
defineExpose({ connect,hangUp });
 | 
			
		||||
defineExpose({ connect, hangUp });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
 | 
			
		||||
@import "@/assets/css/realtime.styl"
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
@@ -3,12 +3,14 @@
 | 
			
		||||
    <div class="running-job-box" v-if="list.length > 0">
 | 
			
		||||
      <div class="job-item" v-for="item in list" :key="item.id">
 | 
			
		||||
        <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
 | 
			
		||||
          <div class="progress" v-if="item.progress > 0">
 | 
			
		||||
          <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                       color="#47fff1"/>
 | 
			
		||||
            <el-progress
 | 
			
		||||
              type="circle"
 | 
			
		||||
              :percentage="item.progress"
 | 
			
		||||
              :width="100"
 | 
			
		||||
              color="#47fff1"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-image fit="cover" v-else>
 | 
			
		||||
          <template #error>
 | 
			
		||||
@@ -20,19 +22,20 @@
 | 
			
		||||
        </el-image>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <el-empty :image-size="100" v-else/>
 | 
			
		||||
    <el-empty :image-size="100" v-else :image="nodata" description="暂无任务" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import nodata from "@/assets/img/no-data.png";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  list: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    default:[],
 | 
			
		||||
    default: []
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,95 +1,111 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="user-bill" v-loading="loading" element-loading-background="rgba(255,255,255,.3)">
 | 
			
		||||
  <div
 | 
			
		||||
    class="user-bill"
 | 
			
		||||
    v-loading="loading"
 | 
			
		||||
    element-loading-background="rgba(255,255,255,.3)"
 | 
			
		||||
  >
 | 
			
		||||
    <el-row v-if="items.length > 0">
 | 
			
		||||
      <el-table :data="items" :row-key="row => row.id" table-layout="auto" border
 | 
			
		||||
                style="--el-table-border-color:#373C47;
 | 
			
		||||
                --el-table-tr-bg-color:#2D323B;
 | 
			
		||||
                --el-table-row-hover-bg-color:#373C47;
 | 
			
		||||
                --el-table-header-bg-color:#474E5C;
 | 
			
		||||
                --el-table-text-color:#d1d1d1">
 | 
			
		||||
      <el-table
 | 
			
		||||
        :data="items"
 | 
			
		||||
        :row-key="(row) => row.id"
 | 
			
		||||
        table-layout="auto"
 | 
			
		||||
        border
 | 
			
		||||
      >
 | 
			
		||||
        <el-table-column prop="order_no" label="订单号">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ scope.row.order_no }}</span>
 | 
			
		||||
            <el-icon class="copy-order-no" :data-clipboard-text="scope.row.order_no">
 | 
			
		||||
              <DocumentCopy/>
 | 
			
		||||
            <el-icon
 | 
			
		||||
              class="copy-order-no"
 | 
			
		||||
              :data-clipboard-text="scope.row.order_no"
 | 
			
		||||
            >
 | 
			
		||||
              <DocumentCopy />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="subject" label="产品名称"/>
 | 
			
		||||
        <el-table-column prop="amount" label="订单金额"/>
 | 
			
		||||
        <el-table-column prop="subject" label="产品名称" />
 | 
			
		||||
        <el-table-column prop="amount" label="订单金额" />
 | 
			
		||||
        <el-table-column label="订单算力">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ scope.row.remark?.power }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="pay_method" label="支付渠道"/>
 | 
			
		||||
        <el-table-column prop="pay_name" label="支付名称"/>
 | 
			
		||||
        <el-table-column prop="pay_method" label="支付渠道" />
 | 
			
		||||
        <el-table-column prop="pay_name" label="支付名称" />
 | 
			
		||||
        <el-table-column label="支付时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span v-if="scope.row['pay_time']">{{ dateFormat(scope.row['pay_time']) }}</span>
 | 
			
		||||
            <span v-if="scope.row['pay_time']">{{
 | 
			
		||||
              dateFormat(scope.row["pay_time"])
 | 
			
		||||
            }}</span>
 | 
			
		||||
            <el-tag v-else>未支付</el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
      </el-table>
 | 
			
		||||
    </el-row>
 | 
			
		||||
    <el-empty :image-size="100" v-else/>
 | 
			
		||||
    <el-empty :image-size="100" v-else />
 | 
			
		||||
    <div class="pagination">
 | 
			
		||||
      <el-pagination v-if="total > 0" background
 | 
			
		||||
      <el-pagination
 | 
			
		||||
        v-if="total > 0"
 | 
			
		||||
        background
 | 
			
		||||
        layout="total,prev, pager, next"
 | 
			
		||||
        :hide-on-single-page="true"
 | 
			
		||||
        v-model:current-page="page"
 | 
			
		||||
        v-model:page-size="pageSize"
 | 
			
		||||
        @current-change="fetchData()"
 | 
			
		||||
                     :total="total"/>
 | 
			
		||||
 | 
			
		||||
        style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
 | 
			
		||||
        :total="total"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import {DocumentCopy} from "@element-plus/icons-vue";
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { httpGet } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { dateFormat } from "@/utils/libs";
 | 
			
		||||
import { DocumentCopy } from "@element-plus/icons-vue";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
 | 
			
		||||
const items = ref([])
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
const page = ref(1)
 | 
			
		||||
const pageSize = ref(12)
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const items = ref([]);
 | 
			
		||||
const total = ref(0);
 | 
			
		||||
const page = ref(1);
 | 
			
		||||
const pageSize = ref(12);
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  fetchData()
 | 
			
		||||
  const clipboard = new Clipboard('.copy-order-no');
 | 
			
		||||
  clipboard.on('success', () => {
 | 
			
		||||
  fetchData();
 | 
			
		||||
  const clipboard = new Clipboard(".copy-order-no");
 | 
			
		||||
  clipboard.on("success", () => {
 | 
			
		||||
    ElMessage.success("复制成功!");
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
  clipboard.on("error", () => {
 | 
			
		||||
    ElMessage.error("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 获取数据
 | 
			
		||||
const fetchData = () => {
 | 
			
		||||
  httpGet('/api/order/list', {page: page.value, page_size: pageSize.value}).then((res) => {
 | 
			
		||||
  httpGet("/api/order/list", { page: page.value, page_size: pageSize.value })
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data) {
 | 
			
		||||
      items.value = res.data.items
 | 
			
		||||
      total.value = res.data.total
 | 
			
		||||
      page.value = res.data.page
 | 
			
		||||
      pageSize.value = res.data.page_size
 | 
			
		||||
        items.value = res.data.items;
 | 
			
		||||
        total.value = res.data.total;
 | 
			
		||||
        page.value = res.data.page;
 | 
			
		||||
        pageSize.value = res.data.page_size;
 | 
			
		||||
      }
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
.user-bill {
 | 
			
		||||
  background-color: var(--chat-bg);
 | 
			
		||||
 | 
			
		||||
  .pagination {
 | 
			
		||||
    margin: 20px 0 0 0;
 | 
			
		||||
    display: flex;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,71 +9,86 @@
 | 
			
		||||
          :http-request="afterRead"
 | 
			
		||||
          accept=".png,.jpg,.jpeg,.bmp"
 | 
			
		||||
        >
 | 
			
		||||
          <el-avatar v-if="user.avatar" :src="user.avatar" shape="circle" :size="100"/>
 | 
			
		||||
          <el-avatar
 | 
			
		||||
            v-if="user.avatar"
 | 
			
		||||
            :src="user.avatar"
 | 
			
		||||
            shape="circle"
 | 
			
		||||
            :size="100"
 | 
			
		||||
          />
 | 
			
		||||
          <el-icon v-else class="avatar-uploader-icon">
 | 
			
		||||
            <Plus/>
 | 
			
		||||
            <Plus />
 | 
			
		||||
          </el-icon>
 | 
			
		||||
        </el-upload>
 | 
			
		||||
      </el-row>
 | 
			
		||||
      <el-form-item label="昵称">
 | 
			
		||||
        <el-input v-model="user['nickname']"/>
 | 
			
		||||
        <el-input v-model="user['nickname']" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="账号">
 | 
			
		||||
        <div class="flex">
 | 
			
		||||
          <span>{{ user.username }}</span>
 | 
			
		||||
          <el-tooltip
 | 
			
		||||
            class="box-item"
 | 
			
		||||
            effect="light"
 | 
			
		||||
            content="您已经是 VIP 会员"
 | 
			
		||||
            placement="right"
 | 
			
		||||
          >
 | 
			
		||||
          <span class="vip-icon"><el-image v-if="user.vip" :src="vipImg" style="height: 25px;margin-left: 10px"/></span>
 | 
			
		||||
            <span class="vip-icon"
 | 
			
		||||
              ><el-image
 | 
			
		||||
                v-if="user.vip"
 | 
			
		||||
                :src="vipImg"
 | 
			
		||||
                style="height: 25px; margin-left: 10px"
 | 
			
		||||
            /></span>
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="剩余算力">
 | 
			
		||||
        <el-tag>{{ user['power'] }}</el-tag>
 | 
			
		||||
        <el-text type="warning">{{ user["power"] }}</el-text>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="会员到期时间" v-if="user['expired_time'] > 0">
 | 
			
		||||
        <el-tag type="danger">{{ dateFormat(user['expired_time']) }}</el-tag>
 | 
			
		||||
        <el-tag type="danger">{{ dateFormat(user["expired_time"]) }}</el-tag>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
 | 
			
		||||
      <el-row class="opt-line">
 | 
			
		||||
        <el-button color="#47fff1" :dark="false" @click="save">保存</el-button>
 | 
			
		||||
        <el-button :dark="false" type="primary" @click="save">保存</el-button>
 | 
			
		||||
      </el-row>
 | 
			
		||||
    </el-form>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { Plus } from "@element-plus/icons-vue";
 | 
			
		||||
import Compressor from "compressorjs";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import { dateFormat } from "@/utils/libs";
 | 
			
		||||
import { checkSession } from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const user = ref({
 | 
			
		||||
  vip: false,
 | 
			
		||||
  username: '演示数据',
 | 
			
		||||
  nickname: '演示数据',
 | 
			
		||||
  avatar: '/images/vip.png',
 | 
			
		||||
  mobile: '演示数据',
 | 
			
		||||
  power: 99999,
 | 
			
		||||
})
 | 
			
		||||
const vipImg = ref("/images/vip.png")
 | 
			
		||||
  username: "演示数据",
 | 
			
		||||
  nickname: "演示数据",
 | 
			
		||||
  avatar: "/images/menu/member.png",
 | 
			
		||||
  mobile: "演示数据",
 | 
			
		||||
  power: 99999
 | 
			
		||||
});
 | 
			
		||||
const vipImg = ref("/images/menu/member.png");
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      // 获取最新用户信息
 | 
			
		||||
    httpGet('/api/user/profile').then(res => {
 | 
			
		||||
      user.value = res.data
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("获取用户信息失败:" + e.message)
 | 
			
		||||
    });
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    console.log(e)
 | 
			
		||||
      httpGet("/api/user/profile")
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          user.value = res.data;
 | 
			
		||||
        })
 | 
			
		||||
})
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("获取用户信息失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      console.log(e);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const afterRead = (file) => {
 | 
			
		||||
  // 压缩图片并上传
 | 
			
		||||
@@ -81,28 +96,32 @@ const afterRead = (file) => {
 | 
			
		||||
    quality: 0.6,
 | 
			
		||||
    success(result) {
 | 
			
		||||
      const formData = new FormData();
 | 
			
		||||
      formData.append('file', result, result.name);
 | 
			
		||||
      formData.append("file", result, result.name);
 | 
			
		||||
      // 执行上传操作
 | 
			
		||||
      httpPost('/api/upload', formData).then((res) => {
 | 
			
		||||
        user.value.avatar = res.data.url
 | 
			
		||||
        ElMessage.success({message: "上传成功", duration: 500})
 | 
			
		||||
      }).catch((e) => {
 | 
			
		||||
        ElMessage.error('图片上传失败:' + e.message)
 | 
			
		||||
      httpPost("/api/upload", formData)
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          user.value.avatar = res.data.url;
 | 
			
		||||
          ElMessage.success({ message: "上传成功", duration: 500 });
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("图片上传失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    error(err) {
 | 
			
		||||
      console.log(err.message);
 | 
			
		||||
    },
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const save = () => {
 | 
			
		||||
  httpPost('/api/user/profile/update', user.value).then(() => {
 | 
			
		||||
    ElMessage.success({message: '更新成功', duration: 500})
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error('更新失败:' + e.message)
 | 
			
		||||
  httpPost("/api/user/profile/update", user.value)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success({ message: "更新成功", duration: 500 });
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("更新失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="welcome">
 | 
			
		||||
    <div class="container">
 | 
			
		||||
      <h1 class="title">{{ title }}-{{ version }}</h1>
 | 
			
		||||
      <h2 class="title">{{ title }}-{{ version }}</h2>
 | 
			
		||||
 | 
			
		||||
      <el-row :gutter="20">
 | 
			
		||||
        <el-col :span="8">
 | 
			
		||||
@@ -128,10 +128,11 @@ const send = (text) => {
 | 
			
		||||
    width 100%
 | 
			
		||||
 | 
			
		||||
    .title {
 | 
			
		||||
      font-size: 2.25rem
 | 
			
		||||
      // font-size: 2.25rem
 | 
			
		||||
      line-height: 2.5rem
 | 
			
		||||
      font-weight 600
 | 
			
		||||
      margin-bottom: 4rem
 | 
			
		||||
      color var( --theme-textcolor-normal)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .grid-content {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,57 +1,58 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="black-input-wrapper">
 | 
			
		||||
    <el-input v-model="model" :type="type" :rows="rows"
 | 
			
		||||
    <el-input
 | 
			
		||||
      v-model="model"
 | 
			
		||||
      :type="type"
 | 
			
		||||
      :rows="rows"
 | 
			
		||||
      @input="onInput"
 | 
			
		||||
              style="--el-input-bg-color:#252020;
 | 
			
		||||
            --el-input-border-color:#414141;
 | 
			
		||||
            --el-input-focus-border-color:#414141;
 | 
			
		||||
            --el-text-color-regular: #f1f1f1;
 | 
			
		||||
            --el-input-border-radius: 10px;
 | 
			
		||||
            --el-border-color-hover:#616161"
 | 
			
		||||
      resize="none"
 | 
			
		||||
              :placeholder="placeholder" :maxlength="maxlength"/>
 | 
			
		||||
      :placeholder="placeholder"
 | 
			
		||||
      :maxlength="maxlength"
 | 
			
		||||
    />
 | 
			
		||||
    <div class="word-stat" v-if="rows > 1">
 | 
			
		||||
      <span>{{value.length}}</span>/<span>{{maxlength}}</span>
 | 
			
		||||
      <span>{{ value.length }}</span
 | 
			
		||||
      >/<span>{{ maxlength }}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {ref, watch} from "vue";
 | 
			
		||||
import { ref, watch } from "vue";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  value : {
 | 
			
		||||
  value: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
    default: ""
 | 
			
		||||
  },
 | 
			
		||||
  placeholder: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '',
 | 
			
		||||
    default: ""
 | 
			
		||||
  },
 | 
			
		||||
  type: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'input',
 | 
			
		||||
    default: "input"
 | 
			
		||||
  },
 | 
			
		||||
  rows: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 5,
 | 
			
		||||
    default: 5
 | 
			
		||||
  },
 | 
			
		||||
  maxlength: {
 | 
			
		||||
    type: Number,
 | 
			
		||||
    default: 1024
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
watch(() => props.value, (newValue) => {
 | 
			
		||||
  model.value = newValue
 | 
			
		||||
})
 | 
			
		||||
const model = ref(props.value)
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.value,
 | 
			
		||||
  (newValue) => {
 | 
			
		||||
    model.value = newValue;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
const model = ref(props.value);
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const emits = defineEmits(['update:value']);
 | 
			
		||||
const emits = defineEmits(["update:value"]);
 | 
			
		||||
const onInput = (value) => {
 | 
			
		||||
  emits('update:value',value)
 | 
			
		||||
}
 | 
			
		||||
  emits("update:value", value);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,32 @@
 | 
			
		||||
<template>
 | 
			
		||||
 | 
			
		||||
  <el-select v-model="model" :placeholder="placeholder"
 | 
			
		||||
             :value="value" @change="$emit('update:value', $event)"
 | 
			
		||||
             style="--el-fill-color-blank:#252020;
 | 
			
		||||
                   --el-text-color-regular: #a1a1a1;
 | 
			
		||||
                   --el-select-disabled-color:#0E0808;
 | 
			
		||||
                   --el-color-primary-light-9:#0E0808;
 | 
			
		||||
                   --el-border-radius-base:20px;
 | 
			
		||||
                   --el-border-color:#0E0808;">
 | 
			
		||||
    <el-option v-for="item in options"
 | 
			
		||||
  <el-select
 | 
			
		||||
    v-model="model"
 | 
			
		||||
    :placeholder="placeholder"
 | 
			
		||||
    :value="value"
 | 
			
		||||
    @change="$emit('update:value', $event)"
 | 
			
		||||
    style="--el-border-radius-base: 20px"
 | 
			
		||||
  >
 | 
			
		||||
    <el-option
 | 
			
		||||
      v-for="item in options"
 | 
			
		||||
      :key="item.value"
 | 
			
		||||
      :label="item.label"
 | 
			
		||||
               :value="item.value">
 | 
			
		||||
      :value="item.value"
 | 
			
		||||
    >
 | 
			
		||||
    </el-option>
 | 
			
		||||
  </el-select>
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'BlackSelect',
 | 
			
		||||
  name: "BlackSelect",
 | 
			
		||||
  props: {
 | 
			
		||||
    value : {
 | 
			
		||||
    value: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '',
 | 
			
		||||
      default: ""
 | 
			
		||||
    },
 | 
			
		||||
    placeholder: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '请选择',
 | 
			
		||||
      default: "请选择"
 | 
			
		||||
    },
 | 
			
		||||
    options: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
@@ -37,7 +36,7 @@ export default {
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      model: this.value
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,26 @@
 | 
			
		||||
<template>
 | 
			
		||||
 | 
			
		||||
  <el-switch v-model="model" :size="size"
 | 
			
		||||
  <el-switch
 | 
			
		||||
    v-model="model"
 | 
			
		||||
    :size="size"
 | 
			
		||||
    @change="$emit('update:value', $event)"
 | 
			
		||||
             style="--el-switch-on-color:#555555;--el-color-white:#0E0808"/>
 | 
			
		||||
 | 
			
		||||
  />
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {ref, watch} from "vue";
 | 
			
		||||
import { ref, watch } from "vue";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  value : Boolean,
 | 
			
		||||
  value: Boolean,
 | 
			
		||||
  size: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'default',
 | 
			
		||||
    default: "default"
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
const model = ref(props.value)
 | 
			
		||||
const model = ref(props.value);
 | 
			
		||||
 | 
			
		||||
watch(() => props.value, (newValue) => {
 | 
			
		||||
  model.value = newValue
 | 
			
		||||
})
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.value,
 | 
			
		||||
  (newValue) => {
 | 
			
		||||
    model.value = newValue;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,32 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
 | 
			
		||||
    <div class="page-apps custom-scroll">
 | 
			
		||||
      <div class="apps-type-nav">
 | 
			
		||||
        <el-scrollbar>
 | 
			
		||||
          <ul class="scrollbar-type-nav">
 | 
			
		||||
            <li :class="{active: typeId === ''}" @click="getAppList('')">全部分类</li>
 | 
			
		||||
            <li v-for="item in appTypes" :key="item.id" :class="{active: typeId === item.id}" @click="getAppList(item.id)">
 | 
			
		||||
            <li :class="{ active: typeId === '' }" @click="getAppList('')">
 | 
			
		||||
              全部分类
 | 
			
		||||
            </li>
 | 
			
		||||
            <li
 | 
			
		||||
              v-for="item in appTypes"
 | 
			
		||||
              :key="item.id"
 | 
			
		||||
              :class="{ active: typeId === item.id }"
 | 
			
		||||
              @click="getAppList(item.id)"
 | 
			
		||||
            >
 | 
			
		||||
              <div class="image" v-if="item.icon">
 | 
			
		||||
                <el-image :src="item.icon" fit="cover"/>
 | 
			
		||||
                <el-image :src="item.icon" fit="cover" />
 | 
			
		||||
              </div>
 | 
			
		||||
              {{ item.name }}
 | 
			
		||||
            </li>
 | 
			
		||||
          </ul>
 | 
			
		||||
        </el-scrollbar>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="app-list-container" :style="{height: listBoxHeight + 'px'}">
 | 
			
		||||
      <div class="app-list-container" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
        <ItemList :items="list" v-if="list.length > 0" :gap="15" :width="300">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <div class="item">
 | 
			
		||||
              <div class="image">
 | 
			
		||||
                <el-image :src="scope.item.icon" fit="cover"/>
 | 
			
		||||
                <el-image :src="scope.item.icon" fit="cover" />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="inner">
 | 
			
		||||
@@ -29,17 +35,34 @@
 | 
			
		||||
                  <div class="info-text">{{ scope.item.hello_msg }}</div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="btn">
 | 
			
		||||
                  <el-button size="small" color="#21aa93" @click="useRole(scope.item)">使用</el-button>
 | 
			
		||||
                  <el-tooltip effect="light" content="从工作区移除" placement="top" v-if="hasRole(scope.item.key)">
 | 
			
		||||
                    <el-button size="small" type="danger" @click="updateRole(scope.item,'remove')">移除</el-button>
 | 
			
		||||
                  <el-button
 | 
			
		||||
                    size="small"
 | 
			
		||||
                    class="sm-btn-theme"
 | 
			
		||||
                    @click="useRole(scope.item)"
 | 
			
		||||
                    >使用</el-button
 | 
			
		||||
                  >
 | 
			
		||||
                  <el-tooltip
 | 
			
		||||
                    content="从工作区移除"
 | 
			
		||||
                    placement="top"
 | 
			
		||||
                    v-if="hasRole(scope.item.key)"
 | 
			
		||||
                  >
 | 
			
		||||
                    <el-button
 | 
			
		||||
                      size="small"
 | 
			
		||||
                      type="danger"
 | 
			
		||||
                      @click="updateRole(scope.item, 'remove')"
 | 
			
		||||
                      >移除</el-button
 | 
			
		||||
                    >
 | 
			
		||||
                  </el-tooltip>
 | 
			
		||||
                  <el-tooltip effect="light" content="添加到工作区" placement="top" v-else>
 | 
			
		||||
                    <el-button size="small" style="--el-color-primary:#009999" @click="updateRole(scope.item, 'add')">添加</el-button>
 | 
			
		||||
                  <el-tooltip content="添加到工作区" placement="top" v-else>
 | 
			
		||||
                    <el-button
 | 
			
		||||
                      size="small"
 | 
			
		||||
                      style="--el-color-primary: #009999"
 | 
			
		||||
                      @click="updateRole(scope.item, 'add')"
 | 
			
		||||
                      >添加</el-button
 | 
			
		||||
                    >
 | 
			
		||||
                  </el-tooltip>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
            <!--            <div class="app-item">-->
 | 
			
		||||
            <!--              <el-image :src="scope.item.icon" fit="cover"/>-->
 | 
			
		||||
@@ -73,95 +96,108 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import {arrayContains, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { checkSession } from "@/store/cache";
 | 
			
		||||
import { arrayContains, removeArrayItem, substr } from "@/utils/libs";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
import ItemList from "@/components/ItemList.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = window.innerHeight - 133
 | 
			
		||||
const listBoxHeight = window.innerHeight - 133;
 | 
			
		||||
 | 
			
		||||
const typeId = ref('')
 | 
			
		||||
const appTypes = ref([])
 | 
			
		||||
const list = ref([])
 | 
			
		||||
const roles = ref([])
 | 
			
		||||
const typeId = ref("");
 | 
			
		||||
const appTypes = ref([]);
 | 
			
		||||
const list = ref([]);
 | 
			
		||||
const roles = ref([]);
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  getAppType()
 | 
			
		||||
  getAppList()
 | 
			
		||||
  getRoles()
 | 
			
		||||
})
 | 
			
		||||
  getAppType();
 | 
			
		||||
  getAppList();
 | 
			
		||||
  getRoles();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const getRoles = () => {
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    roles.value = user.chat_roles
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    console.log(e.message)
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then((user) => {
 | 
			
		||||
      roles.value = user.chat_roles;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      console.log(e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getAppType = () => {
 | 
			
		||||
  httpGet("/api/app/type/list").then((res) => {
 | 
			
		||||
    appTypes.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取分类失败:" + e.message)
 | 
			
		||||
  httpGet("/api/app/type/list")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      appTypes.value = res.data;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取分类失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getAppList = (tid = '') => {
 | 
			
		||||
const getAppList = (tid = "") => {
 | 
			
		||||
  typeId.value = tid;
 | 
			
		||||
  httpGet("/api/app/list", { tid }).then((res) => {
 | 
			
		||||
    const items = res.data
 | 
			
		||||
  httpGet("/api/app/list", { tid })
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      const items = res.data;
 | 
			
		||||
      // 处理 hello message
 | 
			
		||||
      for (let i = 0; i < items.length; i++) {
 | 
			
		||||
      items[i].intro = substr(items[i].hello_msg, 80)
 | 
			
		||||
        items[i].intro = substr(items[i].hello_msg, 80);
 | 
			
		||||
      }
 | 
			
		||||
    list.value = items
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取应用失败:" + e.message)
 | 
			
		||||
      list.value = items;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取应用失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const updateRole = (row, opt) => {
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
    const title = ref("")
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      const title = ref("");
 | 
			
		||||
      if (opt === "add") {
 | 
			
		||||
      title.value = "添加应用"
 | 
			
		||||
      const exists = arrayContains(roles.value, row.key)
 | 
			
		||||
        title.value = "添加应用";
 | 
			
		||||
        const exists = arrayContains(roles.value, row.key);
 | 
			
		||||
        if (exists) {
 | 
			
		||||
        return
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      roles.value.push(row.key)
 | 
			
		||||
        roles.value.push(row.key);
 | 
			
		||||
      } else {
 | 
			
		||||
      title.value = "移除应用"
 | 
			
		||||
      const exists = arrayContains(roles.value, row.key)
 | 
			
		||||
        title.value = "移除应用";
 | 
			
		||||
        const exists = arrayContains(roles.value, row.key);
 | 
			
		||||
        if (!exists) {
 | 
			
		||||
        return
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
      roles.value = removeArrayItem(roles.value, row.key)
 | 
			
		||||
        roles.value = removeArrayItem(roles.value, row.key);
 | 
			
		||||
      }
 | 
			
		||||
    httpPost("/api/app/update", {keys: roles.value}).then(() => {
 | 
			
		||||
      ElMessage.success({message: title.value + "成功!", duration: 1000})
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error(title.value + "失败:" + e.message)
 | 
			
		||||
      httpPost("/api/app/update", { keys: roles.value })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          ElMessage.success({
 | 
			
		||||
            message: title.value + "成功!",
 | 
			
		||||
            duration: 1000
 | 
			
		||||
          });
 | 
			
		||||
        })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error(title.value + "失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      store.setShowLoginDialog(true);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const hasRole = (roleKey) => {
 | 
			
		||||
  return arrayContains(roles.value, roleKey, (v1, v2) => v1 === v2)
 | 
			
		||||
}
 | 
			
		||||
  return arrayContains(roles.value, roleKey, (v1, v2) => v1 === v2);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const useRole = (role) => {
 | 
			
		||||
  router.push(`/chat?role_id=${role.id}`)
 | 
			
		||||
}
 | 
			
		||||
  router.push(`/chat?role_id=${role.id}`);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -880,6 +880,7 @@ const autofillPrompt = (text) => {
 | 
			
		||||
// 发送消息
 | 
			
		||||
const sendMessage = function () {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    console.log("未登录");
 | 
			
		||||
    store.setShowLoginDialog(true);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,13 @@
 | 
			
		||||
                <el-form-item label="图片质量">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-select v-model="params.quality" style="width:176px">
 | 
			
		||||
                        <el-option v-for="v in qualities" :label="v.name" :value="v.value" :key="v.value"/>
 | 
			
		||||
                      <el-select v-model="params.quality" style="width: 176px">
 | 
			
		||||
                        <el-option
 | 
			
		||||
                          v-for="v in qualities"
 | 
			
		||||
                          :label="v.name"
 | 
			
		||||
                          :value="v.value"
 | 
			
		||||
                          :key="v.value"
 | 
			
		||||
                        />
 | 
			
		||||
                      </el-select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </template>
 | 
			
		||||
@@ -23,8 +28,13 @@
 | 
			
		||||
                <el-form-item label="图片尺寸">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-select v-model="params.size" style="width:176px">
 | 
			
		||||
                        <el-option v-for="v in sizes" :label="v" :value="v" :key="v"/>
 | 
			
		||||
                      <el-select v-model="params.size" style="width: 176px">
 | 
			
		||||
                        <el-option
 | 
			
		||||
                          v-for="v in sizes"
 | 
			
		||||
                          :label="v"
 | 
			
		||||
                          :value="v"
 | 
			
		||||
                          :key="v"
 | 
			
		||||
                        />
 | 
			
		||||
                      </el-select>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </template>
 | 
			
		||||
@@ -35,17 +45,21 @@
 | 
			
		||||
                <el-form-item label="图片样式">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-select v-model="params.style" style="width:176px">
 | 
			
		||||
                        <el-option v-for="v in styles" :label="v.name" :value="v.value" :key="v.value"/>
 | 
			
		||||
                      <el-select v-model="params.style" style="width: 176px">
 | 
			
		||||
                        <el-option
 | 
			
		||||
                          v-for="v in styles"
 | 
			
		||||
                          :label="v.name"
 | 
			
		||||
                          :value="v.value"
 | 
			
		||||
                          :key="v.value"
 | 
			
		||||
                        />
 | 
			
		||||
                      </el-select>
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="light"
 | 
			
		||||
                        content="生动使模型倾向于生成超真实和戏剧性的图像"
 | 
			
		||||
                        raw-content
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon class="info-icon">
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                          <InfoFilled />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -61,42 +75,58 @@
 | 
			
		||||
                  ref="promptRef"
 | 
			
		||||
                  placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
 | 
			
		||||
                  v-loading="isGenerating"
 | 
			
		||||
                    style="--el-mask-color:rgba(100, 100, 100, 0.8)"
 | 
			
		||||
                  style="--el-mask-color: rgba(100, 100, 100, 0.8)"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <el-row class="text-info">
 | 
			
		||||
                <el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2" :disabled="isGenerating">
 | 
			
		||||
                  <i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
 | 
			
		||||
                <el-button
 | 
			
		||||
                  class="generate-btn"
 | 
			
		||||
                  size="small"
 | 
			
		||||
                  @click="generatePrompt"
 | 
			
		||||
                  color="#5865f2"
 | 
			
		||||
                  :disabled="isGenerating"
 | 
			
		||||
                >
 | 
			
		||||
                  <i
 | 
			
		||||
                    class="iconfont icon-chuangzuo"
 | 
			
		||||
                    style="margin-right: 5px"
 | 
			
		||||
                  ></i>
 | 
			
		||||
                  <span>生成专业绘画指令</span>
 | 
			
		||||
                </el-button>
 | 
			
		||||
              </el-row>
 | 
			
		||||
 | 
			
		||||
              <div class="text-info">
 | 
			
		||||
                <el-row :gutter="10">
 | 
			
		||||
                  <el-col :span="12">
 | 
			
		||||
                    <el-tag>每次绘图消耗{{ dallPower }}算力</el-tag>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                  <el-col :span="12">
 | 
			
		||||
                    <el-tag type="success">当前可用{{ power }}算力</el-tag>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                  <el-text type="primary"
 | 
			
		||||
                    >每次绘图消耗
 | 
			
		||||
                    <el-text type="warning"
 | 
			
		||||
                      >{{ dallPower }}算力;</el-text
 | 
			
		||||
                    ></el-text
 | 
			
		||||
                  >
 | 
			
		||||
                    
 | 
			
		||||
                  <el-text type="primary"
 | 
			
		||||
                    >当前可用
 | 
			
		||||
                    <el-text type="warning"> {{ power }}算力</el-text>
 | 
			
		||||
                  </el-text>
 | 
			
		||||
                </el-row>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
            </el-form>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="submit-btn">
 | 
			
		||||
            <el-button color="#47fff1" :dark="false" round @click="generate">
 | 
			
		||||
            <el-button type="primary" :dark="false" round @click="generate">
 | 
			
		||||
              立即生成
 | 
			
		||||
            </el-button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="task-list-box">
 | 
			
		||||
          <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
          <div
 | 
			
		||||
            class="task-list-inner"
 | 
			
		||||
            :style="{ height: listBoxHeight + 'px' }"
 | 
			
		||||
          >
 | 
			
		||||
            <div class="job-list-box">
 | 
			
		||||
              <h2>任务列表</h2>
 | 
			
		||||
              <task-list :list="runningJobs" />
 | 
			
		||||
 | 
			
		||||
              <template v-if="finishedJobs.length > 0">
 | 
			
		||||
                <h2>创作记录</h2>
 | 
			
		||||
                <div class="finish-job-list">
 | 
			
		||||
                  <div v-if="finishedJobs.length > 0">
 | 
			
		||||
@@ -110,7 +140,8 @@
 | 
			
		||||
                      :distanceToScroll="100"
 | 
			
		||||
                      :isLoading="loading"
 | 
			
		||||
                      :isOver="isOver"
 | 
			
		||||
                      @scrollReachBottom="fetchFinishJobs()">
 | 
			
		||||
                      @scrollReachBottom="fetchFinishJobs()"
 | 
			
		||||
                    >
 | 
			
		||||
                      <template #default="slotProp">
 | 
			
		||||
                        <div class="job-item">
 | 
			
		||||
                          <el-image
 | 
			
		||||
@@ -118,17 +149,16 @@
 | 
			
		||||
                            @click="previewImg(slotProp.item)"
 | 
			
		||||
                            :src="slotProp.item['img_thumb']"
 | 
			
		||||
                            fit="cover"
 | 
			
		||||
                            loading="lazy">
 | 
			
		||||
                            loading="lazy"
 | 
			
		||||
                          >
 | 
			
		||||
                            <template #placeholder>
 | 
			
		||||
                            <div class="image-slot">
 | 
			
		||||
                              正在加载图片
 | 
			
		||||
                            </div>
 | 
			
		||||
                              <div class="image-slot">正在加载图片</div>
 | 
			
		||||
                            </template>
 | 
			
		||||
 | 
			
		||||
                            <template #error>
 | 
			
		||||
                              <div class="image-slot">
 | 
			
		||||
                                <el-icon>
 | 
			
		||||
                                <Picture/>
 | 
			
		||||
                                  <Picture />
 | 
			
		||||
                                </el-icon>
 | 
			
		||||
                              </div>
 | 
			
		||||
                            </template>
 | 
			
		||||
@@ -140,12 +170,22 @@
 | 
			
		||||
                                <div class="err-msg-container">
 | 
			
		||||
                                  <div class="title">任务失败</div>
 | 
			
		||||
                                  <div class="opt">
 | 
			
		||||
                                  <el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
 | 
			
		||||
                                    <el-popover
 | 
			
		||||
                                      title="错误详情"
 | 
			
		||||
                                      trigger="click"
 | 
			
		||||
                                      :width="250"
 | 
			
		||||
                                      :content="slotProp.item['err_msg']"
 | 
			
		||||
                                      placement="top"
 | 
			
		||||
                                    >
 | 
			
		||||
                                      <template #reference>
 | 
			
		||||
                                        <el-button type="info">详情</el-button>
 | 
			
		||||
                                      </template>
 | 
			
		||||
                                    </el-popover>
 | 
			
		||||
                                  <el-button type="danger"  @click="removeImage(slotProp.item)">删除</el-button>
 | 
			
		||||
                                    <el-button
 | 
			
		||||
                                      type="danger"
 | 
			
		||||
                                      @click="removeImage(slotProp.item)"
 | 
			
		||||
                                      >删除</el-button
 | 
			
		||||
                                    >
 | 
			
		||||
                                  </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </div>
 | 
			
		||||
@@ -162,25 +202,44 @@
 | 
			
		||||
                          </el-image>
 | 
			
		||||
 | 
			
		||||
                          <div class="remove">
 | 
			
		||||
                          <el-tooltip content="删除" placement="top" effect="light">
 | 
			
		||||
                            <el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
 | 
			
		||||
                            <el-tooltip content="删除" placement="top">
 | 
			
		||||
                              <el-button
 | 
			
		||||
                                type="danger"
 | 
			
		||||
                                :icon="Delete"
 | 
			
		||||
                                @click="removeImage(slotProp.item)"
 | 
			
		||||
                                circle
 | 
			
		||||
                              />
 | 
			
		||||
                            </el-tooltip>
 | 
			
		||||
                          <el-tooltip content="取消分享" placement="top" effect="light" v-if="slotProp.item.publish">
 | 
			
		||||
                            <el-button type="warning"
 | 
			
		||||
                            <el-tooltip
 | 
			
		||||
                              content="取消分享"
 | 
			
		||||
                              placement="top"
 | 
			
		||||
                              v-if="slotProp.item.publish"
 | 
			
		||||
                            >
 | 
			
		||||
                              <el-button
 | 
			
		||||
                                type="warning"
 | 
			
		||||
                                @click="publishImage(slotProp.item, false)"
 | 
			
		||||
                                       circle>
 | 
			
		||||
                                circle
 | 
			
		||||
                              >
 | 
			
		||||
                                <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                              </el-button>
 | 
			
		||||
                            </el-tooltip>
 | 
			
		||||
                          <el-tooltip content="分享" placement="top" effect="light" v-else>
 | 
			
		||||
                            <el-button type="success" @click="publishImage(slotProp.item, true)" circle>
 | 
			
		||||
                            <el-tooltip content="分享" placement="top" v-else>
 | 
			
		||||
                              <el-button
 | 
			
		||||
                                type="success"
 | 
			
		||||
                                @click="publishImage(slotProp.item, true)"
 | 
			
		||||
                                circle
 | 
			
		||||
                              >
 | 
			
		||||
                                <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                              </el-button>
 | 
			
		||||
                            </el-tooltip>
 | 
			
		||||
 | 
			
		||||
                          <el-tooltip content="复制提示词" placement="top" effect="light">
 | 
			
		||||
                            <el-button type="info" circle class="copy-prompt"
 | 
			
		||||
                                       :data-clipboard-text="slotProp.item.prompt">
 | 
			
		||||
                            <el-tooltip content="复制提示词" placement="top">
 | 
			
		||||
                              <el-button
 | 
			
		||||
                                type="info"
 | 
			
		||||
                                circle
 | 
			
		||||
                                class="copy-prompt"
 | 
			
		||||
                                :data-clipboard-text="slotProp.item.prompt"
 | 
			
		||||
                              >
 | 
			
		||||
                                <i class="iconfont icon-file"></i>
 | 
			
		||||
                              </el-button>
 | 
			
		||||
                            </el-tooltip>
 | 
			
		||||
@@ -195,246 +254,276 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </v3-waterfall>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <el-empty
 | 
			
		||||
                    :image-size="100"
 | 
			
		||||
                    :image="nodata"
 | 
			
		||||
                    description="暂无记录"
 | 
			
		||||
                    v-else
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
              </template>
 | 
			
		||||
 | 
			
		||||
                </div>
 | 
			
		||||
                <el-empty :image-size="100" v-else/>
 | 
			
		||||
              </div> <!-- end finish job list-->
 | 
			
		||||
              <!-- end finish job list-->
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
 | 
			
		||||
        </div><!-- end task list box -->
 | 
			
		||||
          <back-top :right="30" :bottom="30" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- end task list box -->
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-image-viewer @close="() => { previewURL = '' }" v-if="previewURL !== ''" :url-list="[previewURL]"/>
 | 
			
		||||
    <el-image-viewer
 | 
			
		||||
      @close="
 | 
			
		||||
        () => {
 | 
			
		||||
          previewURL = '';
 | 
			
		||||
        }
 | 
			
		||||
      "
 | 
			
		||||
      v-if="previewURL !== ''"
 | 
			
		||||
      :url-list="[previewURL]"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {Delete, InfoFilled, Picture} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import nodata from "@/assets/img/no-data.png";
 | 
			
		||||
 | 
			
		||||
import { nextTick, onMounted, onUnmounted, ref } from "vue";
 | 
			
		||||
import { Delete, InfoFilled, Picture } from "@element-plus/icons-vue";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage, ElMessageBox } from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { checkSession, getClientId, getSystemInfo } from "@/store/cache";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
import { showMessageError } from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
const listBoxHeight = ref(0);
 | 
			
		||||
// const paramBoxHeight = ref(0)
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const colWidth = ref(220)
 | 
			
		||||
const isOver = ref(false)
 | 
			
		||||
const previewURL = ref("")
 | 
			
		||||
const isLogin = ref(false);
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
const colWidth = ref(220);
 | 
			
		||||
const isOver = ref(false);
 | 
			
		||||
const previewURL = ref("");
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
const resizeElement = function () {
 | 
			
		||||
  listBoxHeight.value = window.innerHeight - 90
 | 
			
		||||
  listBoxHeight.value = window.innerHeight - 90;
 | 
			
		||||
  // paramBoxHeight.value = window.innerHeight - 110
 | 
			
		||||
};
 | 
			
		||||
resizeElement()
 | 
			
		||||
resizeElement();
 | 
			
		||||
window.onresize = () => {
 | 
			
		||||
  resizeElement()
 | 
			
		||||
}
 | 
			
		||||
  resizeElement();
 | 
			
		||||
};
 | 
			
		||||
const qualities = [
 | 
			
		||||
  {name: "标准", value: "standard"},
 | 
			
		||||
  {name: "高清", value: "hd"},
 | 
			
		||||
]
 | 
			
		||||
const sizes = ["1024x1024", "1792x1024", "1024x1792"]
 | 
			
		||||
  { name: "标准", value: "standard" },
 | 
			
		||||
  { name: "高清", value: "hd" }
 | 
			
		||||
];
 | 
			
		||||
const sizes = ["1024x1024", "1792x1024", "1024x1792"];
 | 
			
		||||
const styles = [
 | 
			
		||||
  {name: "生动", value: "vivid"},
 | 
			
		||||
  {name: "自然", value: "natural"}
 | 
			
		||||
]
 | 
			
		||||
  { name: "生动", value: "vivid" },
 | 
			
		||||
  { name: "自然", value: "natural" }
 | 
			
		||||
];
 | 
			
		||||
const params = ref({
 | 
			
		||||
  client_id: getClientId(),
 | 
			
		||||
  quality: "standard",
 | 
			
		||||
  size: "1024x1024",
 | 
			
		||||
  style: "vivid",
 | 
			
		||||
  prompt: ""
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const finishedJobs = ref([])
 | 
			
		||||
const runningJobs = ref([])
 | 
			
		||||
const power = ref(0)
 | 
			
		||||
const dallPower = ref(0) // 画一张 SD 图片消耗算力
 | 
			
		||||
const clipboard = ref(null)
 | 
			
		||||
const userId = ref(0)
 | 
			
		||||
const finishedJobs = ref([]);
 | 
			
		||||
const runningJobs = ref([]);
 | 
			
		||||
const power = ref(0);
 | 
			
		||||
const dallPower = ref(0); // 画一张 SD 图片消耗算力
 | 
			
		||||
const clipboard = ref(null);
 | 
			
		||||
const userId = ref(0);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initData()
 | 
			
		||||
  clipboard.value = new Clipboard('.copy-prompt');
 | 
			
		||||
  clipboard.value.on('success', () => {
 | 
			
		||||
  initData();
 | 
			
		||||
  clipboard.value = new Clipboard(".copy-prompt");
 | 
			
		||||
  clipboard.value.on("success", () => {
 | 
			
		||||
    ElMessage.success("复制成功!");
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.value.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
  clipboard.value.on("error", () => {
 | 
			
		||||
    ElMessage.error("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    dallPower.value = res.data["dall_power"]
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
  getSystemInfo()
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      dallPower.value = res.data["dall_power"];
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取系统配置失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  store.addMessageHandler("dall",(data) => {
 | 
			
		||||
  store.addMessageHandler("dall", (data) => {
 | 
			
		||||
    // 丢弃无关消息
 | 
			
		||||
    if (data.channel !== "dall" || data.clientId !== getClientId()) {
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (data.body === "FINISH" || data.body === "FAIL") {
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
      fetchFinishJobs()
 | 
			
		||||
      page.value = 0;
 | 
			
		||||
      isOver.value = false;
 | 
			
		||||
      fetchFinishJobs();
 | 
			
		||||
    }
 | 
			
		||||
    nextTick(() => fetchRunningJobs())
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
    nextTick(() => fetchRunningJobs());
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  clipboard.value.destroy()
 | 
			
		||||
  store.removeMessageHandler("dall")
 | 
			
		||||
})
 | 
			
		||||
  clipboard.value.destroy();
 | 
			
		||||
  store.removeMessageHandler("dall");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    power.value = user['power']
 | 
			
		||||
    userId.value = user.id
 | 
			
		||||
    isLogin.value = true
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then((user) => {
 | 
			
		||||
      power.value = user["power"];
 | 
			
		||||
      userId.value = user.id;
 | 
			
		||||
      isLogin.value = true;
 | 
			
		||||
 | 
			
		||||
    page.value = 0
 | 
			
		||||
    fetchRunningJobs()
 | 
			
		||||
    fetchFinishJobs()
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
      page.value = 0;
 | 
			
		||||
      fetchRunningJobs();
 | 
			
		||||
      fetchFinishJobs();
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const fetchRunningJobs = () => {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=false`).then(res => {
 | 
			
		||||
    runningJobs.value = res.data.items
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=false`)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      runningJobs.value = res.data.items;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取任务失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const page = ref(1)
 | 
			
		||||
const pageSize = ref(15)
 | 
			
		||||
const page = ref(1);
 | 
			
		||||
const pageSize = ref(15);
 | 
			
		||||
// 获取已完成的任务
 | 
			
		||||
const fetchFinishJobs = () => {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  page.value = page.value + 1
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  page.value = page.value + 1;
 | 
			
		||||
 | 
			
		||||
  httpGet(`/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
  httpGet(
 | 
			
		||||
    `/api/dall/jobs?finish=true&page=${page.value}&page_size=${pageSize.value}`
 | 
			
		||||
  )
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data.items.length < pageSize.value) {
 | 
			
		||||
      isOver.value = true
 | 
			
		||||
        isOver.value = true;
 | 
			
		||||
      }
 | 
			
		||||
    const imageList = res.data.items
 | 
			
		||||
      const imageList = res.data.items;
 | 
			
		||||
      for (let i = 0; i < imageList.length; i++) {
 | 
			
		||||
      imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
 | 
			
		||||
        imageList[i]["img_thumb"] =
 | 
			
		||||
          imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75";
 | 
			
		||||
      }
 | 
			
		||||
      if (page.value === 1) {
 | 
			
		||||
      finishedJobs.value = imageList
 | 
			
		||||
        finishedJobs.value = imageList;
 | 
			
		||||
      } else {
 | 
			
		||||
      finishedJobs.value = finishedJobs.value.concat(imageList)
 | 
			
		||||
        finishedJobs.value = finishedJobs.value.concat(imageList);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取任务失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 创建绘图任务
 | 
			
		||||
const promptRef = ref(null)
 | 
			
		||||
const promptRef = ref(null);
 | 
			
		||||
const generate = () => {
 | 
			
		||||
  if (params.value.prompt === '') {
 | 
			
		||||
    promptRef.value.focus()
 | 
			
		||||
    return ElMessage.error("请输入绘画提示词!")
 | 
			
		||||
  if (params.value.prompt === "") {
 | 
			
		||||
    promptRef.value.focus();
 | 
			
		||||
    return ElMessage.error("请输入绘画提示词!");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    return
 | 
			
		||||
    store.setShowLoginDialog(true);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/dall/image", params.value).then(() => {
 | 
			
		||||
    ElMessage.success("任务执行成功!")
 | 
			
		||||
    power.value -= dallPower.value
 | 
			
		||||
    fetchRunningJobs()
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("任务执行失败:" + e.message)
 | 
			
		||||
  httpPost("/api/dall/image", params.value)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("任务执行成功!");
 | 
			
		||||
      power.value -= dallPower.value;
 | 
			
		||||
      fetchRunningJobs();
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("任务执行失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeImage = (item) => {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
      '删除提示',
 | 
			
		||||
      {
 | 
			
		||||
        confirmButtonText: '确认',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet("/api/dall/remove", {id: item.id}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
      fetchFinishJobs()
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("任务删除失败:" + e.message)
 | 
			
		||||
  ElMessageBox.confirm("此操作将会删除任务和图片,继续操作码?", "删除提示", {
 | 
			
		||||
    confirmButtonText: "确认",
 | 
			
		||||
    cancelButtonText: "取消",
 | 
			
		||||
    type: "warning"
 | 
			
		||||
  })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      httpGet("/api/dall/remove", { id: item.id })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          ElMessage.success("任务删除成功");
 | 
			
		||||
          page.value = 0;
 | 
			
		||||
          isOver.value = false;
 | 
			
		||||
          fetchFinishJobs();
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("任务删除失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const previewImg = (item) => {
 | 
			
		||||
  previewURL.value = item.img_url
 | 
			
		||||
}
 | 
			
		||||
  previewURL.value = item.img_url;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 发布图片到作品墙
 | 
			
		||||
const publishImage = (item, action) => {
 | 
			
		||||
  let text = "图片发布"
 | 
			
		||||
  let text = "图片发布";
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
    text = "取消发布";
 | 
			
		||||
  }
 | 
			
		||||
  httpGet("/api/dall/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
    ElMessage.success(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
    isOver.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error(text + "失败:" + e.message)
 | 
			
		||||
  httpGet("/api/dall/publish", { id: item.id, action: action })
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success(text + "成功");
 | 
			
		||||
      item.publish = action;
 | 
			
		||||
      page.value = 0;
 | 
			
		||||
      isOver.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error(text + "失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isGenerating = ref(false)
 | 
			
		||||
const isGenerating = ref(false);
 | 
			
		||||
const generatePrompt = () => {
 | 
			
		||||
  if (params.value.prompt === "") {
 | 
			
		||||
    return showMessageError("请输入原始提示词")
 | 
			
		||||
    return showMessageError("请输入原始提示词");
 | 
			
		||||
  }
 | 
			
		||||
  isGenerating.value = true
 | 
			
		||||
  httpPost("/api/prompt/image", {prompt: params.value.prompt}).then(res => {
 | 
			
		||||
    params.value.prompt = res.data
 | 
			
		||||
    isGenerating.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("生成提示词失败:"+e.message)
 | 
			
		||||
    isGenerating.value = false
 | 
			
		||||
  isGenerating.value = true;
 | 
			
		||||
  httpPost("/api/prompt/image", { prompt: params.value.prompt })
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      params.value.prompt = res.data;
 | 
			
		||||
      isGenerating.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      showMessageError("生成提示词失败:" + e.message);
 | 
			
		||||
      isGenerating.value = false;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -180,7 +180,14 @@
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- :style="{ 'padding-left': isCollapse ? '65px' : '170px' }" -->
 | 
			
		||||
    <div class="right-main">
 | 
			
		||||
      <div
 | 
			
		||||
        v-if="loginUser.id === undefined || !loginUser.id"
 | 
			
		||||
        class="loginMask"
 | 
			
		||||
        :style="{ left: isCollapse ? '65px' : '170px' }"
 | 
			
		||||
        @click="showNoticeLogin = true"
 | 
			
		||||
      ></div>
 | 
			
		||||
      <div class="topheader" v-if="loginUser.id === undefined || !loginUser.id">
 | 
			
		||||
        <el-button
 | 
			
		||||
          @click="router.push('/login')"
 | 
			
		||||
@@ -189,6 +196,7 @@
 | 
			
		||||
          >登录</el-button
 | 
			
		||||
        >
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- <div class="content custom-scroll"> -->
 | 
			
		||||
      <div class="content custom-scroll">
 | 
			
		||||
        <router-view :key="routerViewKey" v-slot="{ Component }">
 | 
			
		||||
          <transition name="move" mode="out-in">
 | 
			
		||||
@@ -196,6 +204,7 @@
 | 
			
		||||
          </transition>
 | 
			
		||||
        </router-view>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- </div> -->
 | 
			
		||||
    </div>
 | 
			
		||||
    <config-dialog
 | 
			
		||||
      v-if="loginUser.id"
 | 
			
		||||
@@ -203,12 +212,21 @@
 | 
			
		||||
      @hide="showConfigDialog = false"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
  <el-dialog v-model="showNoticeLogin">
 | 
			
		||||
    <el-result icon="warning" title="未登录" sub-title="登录后解锁功能">
 | 
			
		||||
      <template #extra>
 | 
			
		||||
        <el-button type="primary" @click="router.push('/login')"
 | 
			
		||||
          >登录</el-button
 | 
			
		||||
        >
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-result>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { CirclePlus, Setting } from "@element-plus/icons-vue";
 | 
			
		||||
import ThemeChange from "@/components/ThemeChange.vue";
 | 
			
		||||
import { avatarImg } from "@/assets/img/avatar.jpg";
 | 
			
		||||
import avatarImg from "@/assets/img/avatar.jpg";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { onMounted, ref, watch } from "vue";
 | 
			
		||||
import { httpGet } from "@/utils/http";
 | 
			
		||||
@@ -226,10 +244,37 @@ const router = useRouter();
 | 
			
		||||
const logo = ref("");
 | 
			
		||||
const mainNavs = ref([]);
 | 
			
		||||
const moreNavs = ref([]);
 | 
			
		||||
const curPath = ref(router.currentRoute.value.path);
 | 
			
		||||
// const curPath = ref(router.currentRoute.value.path);
 | 
			
		||||
const curPath = ref();
 | 
			
		||||
 | 
			
		||||
const title = ref("");
 | 
			
		||||
const showNoticeLogin = ref(false);
 | 
			
		||||
// const mainWinHeight = window.innerHeight - 50;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 从路径名中提取第一个路径段
 | 
			
		||||
 * @param pathname - URL 的路径名部分,例如 '/chat/12345'
 | 
			
		||||
 * @returns 第一个路径段(不含斜杠),例如 'chat',如果不存在则返回 null
 | 
			
		||||
 */
 | 
			
		||||
const extractFirstSegment = (pathname) => {
 | 
			
		||||
  const segments = pathname.split("/").filter((segment) => segment.length > 0);
 | 
			
		||||
  return segments.length > 0 ? segments[0] : null;
 | 
			
		||||
};
 | 
			
		||||
const getFirstPathSegment = (url) => {
 | 
			
		||||
  try {
 | 
			
		||||
    // 尝试使用 URL 构造函数解析完整的 URL
 | 
			
		||||
    const parsedUrl = new URL(url);
 | 
			
		||||
    return extractFirstSegment(parsedUrl.pathname);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    // 如果解析失败,假设是相对路径,使用当前窗口的位置作为基准
 | 
			
		||||
    if (typeof window !== "undefined") {
 | 
			
		||||
      const parsedUrl = new URL(url, window.location.origin);
 | 
			
		||||
      return extractFirstSegment(parsedUrl.pathname);
 | 
			
		||||
    }
 | 
			
		||||
    // 如果无法解析,返回 null
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
const loginUser = ref({});
 | 
			
		||||
const mainWinHeight = loginUser.value.id
 | 
			
		||||
  ? window.innerHeight
 | 
			
		||||
@@ -252,10 +297,10 @@ watch(
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 监听路由变化
 | 
			
		||||
router.beforeEach((to, from, next) => {
 | 
			
		||||
  curPath.value = to.path;
 | 
			
		||||
  next();
 | 
			
		||||
});
 | 
			
		||||
// router.beforeEach((to, from, next) => {
 | 
			
		||||
//   curPath.value = to.path;
 | 
			
		||||
//   next();
 | 
			
		||||
// });
 | 
			
		||||
 | 
			
		||||
if (curPath.value === "/external") {
 | 
			
		||||
  curPath.value = router.currentRoute.value.query.url;
 | 
			
		||||
@@ -302,7 +347,7 @@ onMounted(() => {
 | 
			
		||||
      license.value = { de_copy: false };
 | 
			
		||||
      showMessageError("获取 License 配置:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  curPath.value = "/" + getFirstPathSegment(window.location.href);
 | 
			
		||||
  init();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,17 +11,21 @@
 | 
			
		||||
                <el-form-item label="采样方法">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-select v-model="params.sampler" style="width:176px">
 | 
			
		||||
                        <el-option v-for="item in samplers" :label="item" :value="item" :key="item"/>
 | 
			
		||||
                      <el-select v-model="params.sampler" style="width: 176px">
 | 
			
		||||
                        <el-option
 | 
			
		||||
                          v-for="item in samplers"
 | 
			
		||||
                          :label="item"
 | 
			
		||||
                          :value="item"
 | 
			
		||||
                          :key="item"
 | 
			
		||||
                        />
 | 
			
		||||
                      </el-select>
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="light"
 | 
			
		||||
                        content="出图效果比较好的一般是 Euler 和 DPM 系列算法"
 | 
			
		||||
                        raw-content
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon class="info-icon">
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                          <InfoFilled />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -33,17 +37,24 @@
 | 
			
		||||
                <el-form-item label="采样调度">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-select v-model="params.scheduler" style="width:176px">
 | 
			
		||||
                        <el-option v-for="item in schedulers" :label="item" :value="item" :key="item"/>
 | 
			
		||||
                      <el-select
 | 
			
		||||
                        v-model="params.scheduler"
 | 
			
		||||
                        style="width: 176px"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-option
 | 
			
		||||
                          v-for="item in schedulers"
 | 
			
		||||
                          :label="item"
 | 
			
		||||
                          :value="item"
 | 
			
		||||
                          :key="item"
 | 
			
		||||
                        />
 | 
			
		||||
                      </el-select>
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="light"
 | 
			
		||||
                        content="推荐自动或者 Karras"
 | 
			
		||||
                        raw-content
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon class="info-icon">
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                          <InfoFilled />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -57,10 +68,16 @@
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-row :gutter="20">
 | 
			
		||||
                        <el-col :span="12">
 | 
			
		||||
                          <el-input v-model.number="params.width" placeholder="图片宽度"/>
 | 
			
		||||
                          <el-input
 | 
			
		||||
                            v-model.number="params.width"
 | 
			
		||||
                            placeholder="图片宽度"
 | 
			
		||||
                          />
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                        <el-col :span="12">
 | 
			
		||||
                          <el-input v-model.number="params.height" placeholder="图片高度"/>
 | 
			
		||||
                          <el-input
 | 
			
		||||
                            v-model.number="params.height"
 | 
			
		||||
                            placeholder="图片高度"
 | 
			
		||||
                          />
 | 
			
		||||
                        </el-col>
 | 
			
		||||
                      </el-row>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -72,15 +89,14 @@
 | 
			
		||||
                <el-form-item label="迭代步数">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-input v-model.number="params.steps"/>
 | 
			
		||||
                      <el-input v-model.number="params.steps" />
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="light"
 | 
			
		||||
                        content="值越大则代表细节越多,同时也意味着出图速度越慢"
 | 
			
		||||
                        raw-content
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon class="info-icon">
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                          <InfoFilled />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -92,15 +108,14 @@
 | 
			
		||||
                <el-form-item label="引导系数">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-input v-model.number="params.cfg_scale"/>
 | 
			
		||||
                      <el-input v-model.number="params.cfg_scale" />
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="light"
 | 
			
		||||
                        content="提示词引导系数,图像在多大程度上服从提示词<br/> 较低值会产生更有创意的结果"
 | 
			
		||||
                        raw-content
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon class="info-icon">
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                          <InfoFilled />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -112,26 +127,24 @@
 | 
			
		||||
                <el-form-item label="随机因子">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-input v-model.number="params.seed"/>
 | 
			
		||||
                      <el-input v-model.number="params.seed" />
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="light"
 | 
			
		||||
                        content="随机数种子,相同的种子会得到相同的结果<br/> 设置为 -1 则每次随机生成种子"
 | 
			
		||||
                        raw-content
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon class="info-icon">
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                          <InfoFilled />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="light"
 | 
			
		||||
                        content="使用随机数"
 | 
			
		||||
                        raw-content
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon @click="params.seed = -1" class="info-icon">
 | 
			
		||||
                          <Orange/>
 | 
			
		||||
                          <Orange />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -143,15 +156,18 @@
 | 
			
		||||
                <el-form-item label="高清修复">
 | 
			
		||||
                  <template #default>
 | 
			
		||||
                    <div class="form-item-inner">
 | 
			
		||||
                      <el-switch v-model="params.hd_fix" style="--el-switch-on-color: #47fff1;" size="large"/>
 | 
			
		||||
                      <el-switch
 | 
			
		||||
                        v-model="params.hd_fix"
 | 
			
		||||
                        style="--el-switch-on-color: #47fff1"
 | 
			
		||||
                        size="large"
 | 
			
		||||
                      />
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                          effect="light"
 | 
			
		||||
                        content="先以较小的分辨率生成图像,接着方法图像<br />然后在不更改构图的情况下再修改细节"
 | 
			
		||||
                        raw-content
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon style="margin-left: 10px; top: 12px">
 | 
			
		||||
                          <InfoFilled/>
 | 
			
		||||
                          <InfoFilled />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
                    </div>
 | 
			
		||||
@@ -164,16 +180,22 @@
 | 
			
		||||
                  <el-form-item label="重绘幅度">
 | 
			
		||||
                    <template #default>
 | 
			
		||||
                      <div class="form-item-inner">
 | 
			
		||||
                        <el-slider v-model.number="params.hd_redraw_rate" :max="1" :step="0.1"
 | 
			
		||||
                                   style="width: 180px;--el-slider-main-bg-color:#47fff1"/>
 | 
			
		||||
                        <el-slider
 | 
			
		||||
                          v-model.number="params.hd_redraw_rate"
 | 
			
		||||
                          :max="1"
 | 
			
		||||
                          :step="0.1"
 | 
			
		||||
                          style="
 | 
			
		||||
                            width: 180px;
 | 
			
		||||
                            --el-slider-main-bg-color: #47fff1;
 | 
			
		||||
                          "
 | 
			
		||||
                        />
 | 
			
		||||
                        <el-tooltip
 | 
			
		||||
                            effect="light"
 | 
			
		||||
                          content="决定算法对图像内容的影响程度<br />较大的值将得到越有创意的图像"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                        >
 | 
			
		||||
                          <el-icon class="info-icon">
 | 
			
		||||
                            <InfoFilled/>
 | 
			
		||||
                            <InfoFilled />
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                      </div>
 | 
			
		||||
@@ -185,17 +207,24 @@
 | 
			
		||||
                  <el-form-item label="放大算法">
 | 
			
		||||
                    <template #default>
 | 
			
		||||
                      <div class="form-item-inner">
 | 
			
		||||
                        <el-select v-model="params.hd_scale_alg" style="width:176px">
 | 
			
		||||
                          <el-option v-for="item in scaleAlg" :label="item" :value="item" :key="item"/>
 | 
			
		||||
                        <el-select
 | 
			
		||||
                          v-model="params.hd_scale_alg"
 | 
			
		||||
                          style="width: 176px"
 | 
			
		||||
                        >
 | 
			
		||||
                          <el-option
 | 
			
		||||
                            v-for="item in scaleAlg"
 | 
			
		||||
                            :label="item"
 | 
			
		||||
                            :value="item"
 | 
			
		||||
                            :key="item"
 | 
			
		||||
                          />
 | 
			
		||||
                        </el-select>
 | 
			
		||||
                        <el-tooltip
 | 
			
		||||
                            effect="light"
 | 
			
		||||
                          content="高清修复放大算法,主流算法有Latent和ESRGAN_4x"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                        >
 | 
			
		||||
                          <el-icon class="info-icon">
 | 
			
		||||
                            <InfoFilled/>
 | 
			
		||||
                            <InfoFilled />
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                      </div>
 | 
			
		||||
@@ -207,15 +236,14 @@
 | 
			
		||||
                  <el-form-item label="放大倍数">
 | 
			
		||||
                    <template #default>
 | 
			
		||||
                      <div class="form-item-inner">
 | 
			
		||||
                        <el-input v-model.number="params.hd_scale"/>
 | 
			
		||||
                        <el-input v-model.number="params.hd_scale" />
 | 
			
		||||
                        <el-tooltip
 | 
			
		||||
                            effect="light"
 | 
			
		||||
                          content="随机数种子,相同的种子会得到相同的结果<br/> 设置为 -1 则每次随机生成种子"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                        >
 | 
			
		||||
                          <el-icon class="info-icon">
 | 
			
		||||
                            <InfoFilled/>
 | 
			
		||||
                            <InfoFilled />
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                      </div>
 | 
			
		||||
@@ -227,15 +255,14 @@
 | 
			
		||||
                  <el-form-item label="迭代步数">
 | 
			
		||||
                    <template #default>
 | 
			
		||||
                      <div class="form-item-inner">
 | 
			
		||||
                        <el-input v-model.number="params.hd_steps"/>
 | 
			
		||||
                        <el-input v-model.number="params.hd_steps" />
 | 
			
		||||
                        <el-tooltip
 | 
			
		||||
                            effect="light"
 | 
			
		||||
                          content="重绘迭代步数,如果设置为0,则设置跟原图相同的迭代步数"
 | 
			
		||||
                          raw-content
 | 
			
		||||
                          placement="right"
 | 
			
		||||
                        >
 | 
			
		||||
                          <el-icon class="info-icon">
 | 
			
		||||
                            <InfoFilled/>
 | 
			
		||||
                            <InfoFilled />
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </el-tooltip>
 | 
			
		||||
                      </div>
 | 
			
		||||
@@ -252,13 +279,22 @@
 | 
			
		||||
                  ref="promptRef"
 | 
			
		||||
                  placeholder="请在此输入绘画提示词,您也可以点击下面的提示词助手生成绘画提示词"
 | 
			
		||||
                  v-loading="isGenerating"
 | 
			
		||||
                    style="--el-mask-color:rgba(100, 100, 100, 0.8)"
 | 
			
		||||
                  style="--el-mask-color: rgba(100, 100, 100, 0.8)"
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <el-row class="text-info">
 | 
			
		||||
                <el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2" :disabled="isGenerating">
 | 
			
		||||
                  <i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
 | 
			
		||||
                <el-button
 | 
			
		||||
                  class="generate-btn"
 | 
			
		||||
                  size="small"
 | 
			
		||||
                  @click="generatePrompt"
 | 
			
		||||
                  color="#5865f2"
 | 
			
		||||
                  :disabled="isGenerating"
 | 
			
		||||
                >
 | 
			
		||||
                  <i
 | 
			
		||||
                    class="iconfont icon-chuangzuo"
 | 
			
		||||
                    style="margin-right: 5px"
 | 
			
		||||
                  ></i>
 | 
			
		||||
                  <span>生成专业绘画指令</span>
 | 
			
		||||
                </el-button>
 | 
			
		||||
              </el-row>
 | 
			
		||||
@@ -266,12 +302,11 @@
 | 
			
		||||
              <div class="param-line pt">
 | 
			
		||||
                <span>反向提示词:</span>
 | 
			
		||||
                <el-tooltip
 | 
			
		||||
                    effect="light"
 | 
			
		||||
                  content="不希望出现的元素,下面给了默认的起手式"
 | 
			
		||||
                  placement="right"
 | 
			
		||||
                >
 | 
			
		||||
                  <el-icon class="info-icon">
 | 
			
		||||
                    <InfoFilled/>
 | 
			
		||||
                    <InfoFilled />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
              </div>
 | 
			
		||||
@@ -287,26 +322,38 @@
 | 
			
		||||
              <div class="text-info">
 | 
			
		||||
                <el-row :gutter="10">
 | 
			
		||||
                  <el-col :span="12">
 | 
			
		||||
                    <el-tag>单次绘图消耗{{ sdPower }}算力</el-tag>
 | 
			
		||||
                    <el-text type="primary"
 | 
			
		||||
                      >单次绘图消耗
 | 
			
		||||
                      <el-text type="warning">{{ sdPower }}算力;</el-text>
 | 
			
		||||
                    </el-text>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                  <el-col :span="12">
 | 
			
		||||
                    <el-tag type="success">当前可用{{ power }}算力</el-tag>
 | 
			
		||||
                    <el-text type="primary"
 | 
			
		||||
                      >当前可用
 | 
			
		||||
                      <el-text type="warning">
 | 
			
		||||
                        {{ power }}算力</el-text
 | 
			
		||||
                      ></el-text
 | 
			
		||||
                    >
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                </el-row>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
            </el-form>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="submit-btn">
 | 
			
		||||
            <el-button color="#47fff1" :dark="false" round @click="generate">立即生成</el-button>
 | 
			
		||||
            <el-button type="primary" :dark="false" round @click="generate"
 | 
			
		||||
              >立即生成</el-button
 | 
			
		||||
            >
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="task-list-box">
 | 
			
		||||
          <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
          <div
 | 
			
		||||
            class="task-list-inner"
 | 
			
		||||
            :style="{ height: listBoxHeight + 'px' }"
 | 
			
		||||
          >
 | 
			
		||||
            <div class="job-list-box">
 | 
			
		||||
              <h2>任务列表</h2>
 | 
			
		||||
              <task-list :list="runningJobs" />
 | 
			
		||||
              
 | 
			
		||||
              <template v-if="finishedJobs.length > 0">
 | 
			
		||||
                <h2>创作记录</h2>
 | 
			
		||||
                <div class="finish-job-list">
 | 
			
		||||
                  <div v-if="finishedJobs.length > 0">
 | 
			
		||||
@@ -320,7 +367,8 @@
 | 
			
		||||
                      :distanceToScroll="100"
 | 
			
		||||
                      :isLoading="loading"
 | 
			
		||||
                      :isOver="isOver"
 | 
			
		||||
                      @scrollReachBottom="fetchFinishJobs()">
 | 
			
		||||
                      @scrollReachBottom="fetchFinishJobs()"
 | 
			
		||||
                    >
 | 
			
		||||
                      <template #default="slotProp">
 | 
			
		||||
                        <div class="job-item animate">
 | 
			
		||||
                          <el-image v-if="slotProp.item.progress === 101">
 | 
			
		||||
@@ -329,12 +377,22 @@
 | 
			
		||||
                                <div class="err-msg-container">
 | 
			
		||||
                                  <div class="title">任务失败</div>
 | 
			
		||||
                                  <div class="opt">
 | 
			
		||||
                                  <el-popover title="错误详情" trigger="click" :width="250" :content="slotProp.item['err_msg']" placement="top">
 | 
			
		||||
                                    <el-popover
 | 
			
		||||
                                      title="错误详情"
 | 
			
		||||
                                      trigger="click"
 | 
			
		||||
                                      :width="250"
 | 
			
		||||
                                      :content="slotProp.item['err_msg']"
 | 
			
		||||
                                      placement="top"
 | 
			
		||||
                                    >
 | 
			
		||||
                                      <template #reference>
 | 
			
		||||
                                        <el-button type="info">详情</el-button>
 | 
			
		||||
                                      </template>
 | 
			
		||||
                                    </el-popover>
 | 
			
		||||
                                  <el-button type="danger"  @click="removeImage(slotProp.item)">删除</el-button>
 | 
			
		||||
                                    <el-button
 | 
			
		||||
                                      type="danger"
 | 
			
		||||
                                      @click="removeImage(slotProp.item)"
 | 
			
		||||
                                      >删除</el-button
 | 
			
		||||
                                    >
 | 
			
		||||
                                  </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                              </div>
 | 
			
		||||
@@ -345,15 +403,29 @@
 | 
			
		||||
                              :src="slotProp.item['img_thumb']"
 | 
			
		||||
                              @click="showTask(slotProp.item)"
 | 
			
		||||
                              fit="cover"
 | 
			
		||||
                              loading="lazy"/>
 | 
			
		||||
                              loading="lazy"
 | 
			
		||||
                            />
 | 
			
		||||
                            <div class="remove">
 | 
			
		||||
                            <el-button type="danger" :icon="Delete" @click="removeImage(slotProp.item)" circle/>
 | 
			
		||||
                            <el-button type="warning" v-if="slotProp.item.publish"
 | 
			
		||||
                              <el-button
 | 
			
		||||
                                type="danger"
 | 
			
		||||
                                :icon="Delete"
 | 
			
		||||
                                @click="removeImage(slotProp.item)"
 | 
			
		||||
                                circle
 | 
			
		||||
                              />
 | 
			
		||||
                              <el-button
 | 
			
		||||
                                type="warning"
 | 
			
		||||
                                v-if="slotProp.item.publish"
 | 
			
		||||
                                @click="publishImage(slotProp.item, false)"
 | 
			
		||||
                                       circle>
 | 
			
		||||
                                circle
 | 
			
		||||
                              >
 | 
			
		||||
                                <i class="iconfont icon-cancel-share"></i>
 | 
			
		||||
                              </el-button>
 | 
			
		||||
                            <el-button type="success" v-else @click="publishImage(slotProp.item, true)" circle>
 | 
			
		||||
                              <el-button
 | 
			
		||||
                                type="success"
 | 
			
		||||
                                v-else
 | 
			
		||||
                                @click="publishImage(slotProp.item, true)"
 | 
			
		||||
                                circle
 | 
			
		||||
                              >
 | 
			
		||||
                                <i class="iconfont icon-share-bold"></i>
 | 
			
		||||
                              </el-button>
 | 
			
		||||
                            </div>
 | 
			
		||||
@@ -368,47 +440,63 @@
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </v3-waterfall>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <el-empty
 | 
			
		||||
                    :image-size="100"
 | 
			
		||||
                    v-else
 | 
			
		||||
                    :image="nodata"
 | 
			
		||||
                    description="暂无记录"
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
              </template>
 | 
			
		||||
 | 
			
		||||
                </div>
 | 
			
		||||
                <el-empty :image-size="100" v-else/>
 | 
			
		||||
              </div> <!-- end finish job list-->
 | 
			
		||||
              <!-- end finish job list-->
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
 | 
			
		||||
        </div><!-- end task list box -->
 | 
			
		||||
          <back-top :right="30" :bottom="30" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <!-- end task list box -->
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <!-- 任务详情弹框 -->
 | 
			
		||||
      <el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
 | 
			
		||||
      <el-dialog
 | 
			
		||||
        v-model="showTaskDialog"
 | 
			
		||||
        title="绘画任务详情"
 | 
			
		||||
        :fullscreen="true"
 | 
			
		||||
      >
 | 
			
		||||
        <el-row :gutter="20">
 | 
			
		||||
          <el-col :span="16">
 | 
			
		||||
            <div class="img-container" :style="{maxHeight: fullImgHeight+'px'}">
 | 
			
		||||
              <el-image :src="item['img_url']" fit="contain"/>
 | 
			
		||||
            <div
 | 
			
		||||
              class="img-container"
 | 
			
		||||
              :style="{ maxHeight: fullImgHeight + 'px' }"
 | 
			
		||||
            >
 | 
			
		||||
              <el-image :src="item['img_url']" fit="contain" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-col>
 | 
			
		||||
          <el-col :span="8">
 | 
			
		||||
            <div class="task-info">
 | 
			
		||||
              <div class="info-line">
 | 
			
		||||
                <el-divider>
 | 
			
		||||
                  正向提示词
 | 
			
		||||
                </el-divider>
 | 
			
		||||
                <el-divider> 正向提示词 </el-divider>
 | 
			
		||||
                <div class="prompt">
 | 
			
		||||
                  <span>{{ item.prompt }}</span>
 | 
			
		||||
                  <el-icon class="copy-prompt-sd" :data-clipboard-text="item.prompt">
 | 
			
		||||
                    <DocumentCopy/>
 | 
			
		||||
                  <el-icon
 | 
			
		||||
                    class="copy-prompt-sd"
 | 
			
		||||
                    :data-clipboard-text="item.prompt"
 | 
			
		||||
                  >
 | 
			
		||||
                    <DocumentCopy />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="info-line">
 | 
			
		||||
                <el-divider>
 | 
			
		||||
                  反向提示词
 | 
			
		||||
                </el-divider>
 | 
			
		||||
                <el-divider> 反向提示词 </el-divider>
 | 
			
		||||
                <div class="prompt">
 | 
			
		||||
                  <span>{{ item.params.neg_prompt }}</span>
 | 
			
		||||
                  <el-icon class="copy-prompt-sd" :data-clipboard-text="item.params.neg_prompt">
 | 
			
		||||
                    <DocumentCopy/>
 | 
			
		||||
                  <el-icon
 | 
			
		||||
                    class="copy-prompt-sd"
 | 
			
		||||
                    :data-clipboard-text="item.params.neg_prompt"
 | 
			
		||||
                  >
 | 
			
		||||
                    <DocumentCopy />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
@@ -423,7 +511,9 @@
 | 
			
		||||
              <div class="info-line">
 | 
			
		||||
                <div class="wrapper">
 | 
			
		||||
                  <label>图片尺寸:</label>
 | 
			
		||||
                  <div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
 | 
			
		||||
                  <div class="item-value">
 | 
			
		||||
                    {{ item.params.width }} x {{ item.params.height }}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
@@ -449,13 +539,13 @@
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div v-if="item.params.hd_fix">
 | 
			
		||||
                <el-divider>
 | 
			
		||||
                  高清修复
 | 
			
		||||
                </el-divider>
 | 
			
		||||
                <el-divider> 高清修复 </el-divider>
 | 
			
		||||
                <div class="info-line">
 | 
			
		||||
                  <div class="wrapper">
 | 
			
		||||
                    <label>重绘幅度:</label>
 | 
			
		||||
                    <div class="item-value">{{ item.params.hd_redraw_rate }}</div>
 | 
			
		||||
                    <div class="item-value">
 | 
			
		||||
                      {{ item.params.hd_redraw_rate }}
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
@@ -482,53 +572,68 @@
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="copy-params">
 | 
			
		||||
                <el-button type="primary" round @click="copyParams(item)">画一张同款的</el-button>
 | 
			
		||||
                <el-button type="primary" round @click="copyParams(item)"
 | 
			
		||||
                  >画一张同款的</el-button
 | 
			
		||||
                >
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
      </el-dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {Delete, DocumentCopy, InfoFilled, Orange} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import { nextTick, onMounted, onUnmounted, ref } from "vue";
 | 
			
		||||
import {
 | 
			
		||||
  Delete,
 | 
			
		||||
  DocumentCopy,
 | 
			
		||||
  InfoFilled,
 | 
			
		||||
  Orange
 | 
			
		||||
} from "@element-plus/icons-vue";
 | 
			
		||||
import nodata from "@/assets/img/no-data.png";
 | 
			
		||||
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage, ElMessageBox } from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { checkSession, getClientId, getSystemInfo } from "@/store/cache";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { getSessionId } from "@/store/session";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
import { showMessageError } from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
const listBoxHeight = ref(0);
 | 
			
		||||
// const paramBoxHeight = ref(0)
 | 
			
		||||
const fullImgHeight = ref(window.innerHeight - 60)
 | 
			
		||||
const showTaskDialog = ref(false)
 | 
			
		||||
const item = ref({})
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const colWidth = ref(220)
 | 
			
		||||
const fullImgHeight = ref(window.innerHeight - 60);
 | 
			
		||||
const showTaskDialog = ref(false);
 | 
			
		||||
const item = ref({});
 | 
			
		||||
const isLogin = ref(false);
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
const colWidth = ref(220);
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
const resizeElement = function () {
 | 
			
		||||
  listBoxHeight.value = window.innerHeight - 80
 | 
			
		||||
  listBoxHeight.value = window.innerHeight - 80;
 | 
			
		||||
  // paramBoxHeight.value = window.innerHeight - 200
 | 
			
		||||
};
 | 
			
		||||
resizeElement()
 | 
			
		||||
resizeElement();
 | 
			
		||||
window.onresize = () => {
 | 
			
		||||
  resizeElement()
 | 
			
		||||
}
 | 
			
		||||
const samplers = ["Euler a", "DPM++ 2S a", "DPM++ 2M", "DPM++ SDE", "DPM++ 2M SDE", "UniPC", "Restart"]
 | 
			
		||||
const schedulers = ["Automatic", "Karras", "Exponential", "Uniform"]
 | 
			
		||||
const scaleAlg = ["Latent", "ESRGAN_4x", "R-ESRGAN 4x+", "SwinIR_4x", "LDSR"]
 | 
			
		||||
  resizeElement();
 | 
			
		||||
};
 | 
			
		||||
const samplers = [
 | 
			
		||||
  "Euler a",
 | 
			
		||||
  "DPM++ 2S a",
 | 
			
		||||
  "DPM++ 2M",
 | 
			
		||||
  "DPM++ SDE",
 | 
			
		||||
  "DPM++ 2M SDE",
 | 
			
		||||
  "UniPC",
 | 
			
		||||
  "Restart"
 | 
			
		||||
];
 | 
			
		||||
const schedulers = ["Automatic", "Karras", "Exponential", "Uniform"];
 | 
			
		||||
const scaleAlg = ["Latent", "ESRGAN_4x", "R-ESRGAN 4x+", "SwinIR_4x", "LDSR"];
 | 
			
		||||
const params = ref({
 | 
			
		||||
  client_id: getClientId(),
 | 
			
		||||
  width: 1024,
 | 
			
		||||
@@ -544,209 +649,221 @@ const params = ref({
 | 
			
		||||
  hd_scale_alg: scaleAlg[0],
 | 
			
		||||
  hd_steps: 0,
 | 
			
		||||
  prompt: "",
 | 
			
		||||
  neg_prompt: "nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet",
 | 
			
		||||
})
 | 
			
		||||
  neg_prompt:
 | 
			
		||||
    "nsfw, paintings,low quality,easynegative,ng_deepnegative ,lowres,bad anatomy,bad hands,bad feet"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const runningJobs = ref([])
 | 
			
		||||
const finishedJobs = ref([])
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const runningJobs = ref([]);
 | 
			
		||||
const finishedJobs = ref([]);
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
// 检查是否有画同款的参数
 | 
			
		||||
const _params = router.currentRoute.value.params["copyParams"]
 | 
			
		||||
const _params = router.currentRoute.value.params["copyParams"];
 | 
			
		||||
if (_params) {
 | 
			
		||||
  params.value = JSON.parse(_params)
 | 
			
		||||
  params.value = JSON.parse(_params);
 | 
			
		||||
}
 | 
			
		||||
const power = ref(0)
 | 
			
		||||
const sdPower = ref(0) // 画一张 SD 图片消耗算力
 | 
			
		||||
const power = ref(0);
 | 
			
		||||
const sdPower = ref(0); // 画一张 SD 图片消耗算力
 | 
			
		||||
 | 
			
		||||
const userId = ref(0)
 | 
			
		||||
const clipboard = ref(null)
 | 
			
		||||
const userId = ref(0);
 | 
			
		||||
const clipboard = ref(null);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initData()
 | 
			
		||||
  clipboard.value = new Clipboard('.copy-prompt-sd');
 | 
			
		||||
  clipboard.value.on('success', () => {
 | 
			
		||||
  initData();
 | 
			
		||||
  clipboard.value = new Clipboard(".copy-prompt-sd");
 | 
			
		||||
  clipboard.value.on("success", () => {
 | 
			
		||||
    ElMessage.success("复制成功!");
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.value.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
  clipboard.value.on("error", () => {
 | 
			
		||||
    ElMessage.error("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    sdPower.value = res.data.sd_power
 | 
			
		||||
    params.value.neg_prompt = res.data.sd_neg_prompt
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
  getSystemInfo()
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      sdPower.value = res.data.sd_power;
 | 
			
		||||
      params.value.neg_prompt = res.data.sd_neg_prompt;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取系统配置失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  store.addMessageHandler("sd",(data) => {
 | 
			
		||||
  store.addMessageHandler("sd", (data) => {
 | 
			
		||||
    // 丢弃无关消息
 | 
			
		||||
    if (data.channel !== "sd" || data.clientId !== getClientId()) {
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (data.body === "FINISH" || data.body === "FAIL") {
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
      fetchFinishJobs()
 | 
			
		||||
      page.value = 0;
 | 
			
		||||
      isOver.value = false;
 | 
			
		||||
      fetchFinishJobs();
 | 
			
		||||
    }
 | 
			
		||||
    nextTick(() => fetchRunningJobs())
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
    nextTick(() => fetchRunningJobs());
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  clipboard.value.destroy()
 | 
			
		||||
  store.removeMessageHandler("sd")
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
  clipboard.value.destroy();
 | 
			
		||||
  store.removeMessageHandler("sd");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    power.value = user['power']
 | 
			
		||||
    userId.value = user.id
 | 
			
		||||
    isLogin.value = true
 | 
			
		||||
    page.value = 0
 | 
			
		||||
    fetchRunningJobs()
 | 
			
		||||
    fetchFinishJobs()
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then((user) => {
 | 
			
		||||
      power.value = user["power"];
 | 
			
		||||
      userId.value = user.id;
 | 
			
		||||
      isLogin.value = true;
 | 
			
		||||
      page.value = 0;
 | 
			
		||||
      fetchRunningJobs();
 | 
			
		||||
      fetchFinishJobs();
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const fetchRunningJobs = () => {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 获取运行中的任务
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=0`).then(res => {
 | 
			
		||||
    runningJobs.value = res.data.items
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=0`)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      runningJobs.value = res.data.items;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取任务失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const page = ref(0)
 | 
			
		||||
const pageSize = ref(20)
 | 
			
		||||
const isOver = ref(false)
 | 
			
		||||
const page = ref(0);
 | 
			
		||||
const pageSize = ref(20);
 | 
			
		||||
const isOver = ref(false);
 | 
			
		||||
// 获取已完成的任务
 | 
			
		||||
const fetchFinishJobs = () => {
 | 
			
		||||
  if (!isLogin.value || isOver.value === true) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  page.value = page.value + 1
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  page.value = page.value + 1;
 | 
			
		||||
 | 
			
		||||
  httpGet(`/api/sd/jobs?finish=1&page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
  httpGet(
 | 
			
		||||
    `/api/sd/jobs?finish=1&page=${page.value}&page_size=${pageSize.value}`
 | 
			
		||||
  )
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data.items.length < pageSize.value) {
 | 
			
		||||
      isOver.value = true
 | 
			
		||||
        isOver.value = true;
 | 
			
		||||
      }
 | 
			
		||||
    const imageList = res.data.items
 | 
			
		||||
      const imageList = res.data.items;
 | 
			
		||||
      for (let i = 0; i < imageList.length; i++) {
 | 
			
		||||
      imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
 | 
			
		||||
        imageList[i]["img_thumb"] =
 | 
			
		||||
          imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75";
 | 
			
		||||
      }
 | 
			
		||||
      if (page.value === 1) {
 | 
			
		||||
      finishedJobs.value = imageList
 | 
			
		||||
        finishedJobs.value = imageList;
 | 
			
		||||
      } else {
 | 
			
		||||
      finishedJobs.value = finishedJobs.value.concat(imageList)
 | 
			
		||||
        finishedJobs.value = finishedJobs.value.concat(imageList);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取任务失败:" + e.message)
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取任务失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 创建绘图任务
 | 
			
		||||
const promptRef = ref(null)
 | 
			
		||||
const promptRef = ref(null);
 | 
			
		||||
const generate = () => {
 | 
			
		||||
  if (params.value.prompt === '') {
 | 
			
		||||
    promptRef.value.focus()
 | 
			
		||||
    return ElMessage.error("请输入绘画提示词!")
 | 
			
		||||
  if (params.value.prompt === "") {
 | 
			
		||||
    promptRef.value.focus();
 | 
			
		||||
    return ElMessage.error("请输入绘画提示词!");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    return
 | 
			
		||||
    store.setShowLoginDialog(true);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!params.value.seed) {
 | 
			
		||||
    params.value.seed = -1
 | 
			
		||||
    params.value.seed = -1;
 | 
			
		||||
  }
 | 
			
		||||
  params.value.session_id = getSessionId()
 | 
			
		||||
  httpPost("/api/sd/image", params.value).then(() => {
 | 
			
		||||
    ElMessage.success("绘画任务推送成功,请耐心等待任务执行...")
 | 
			
		||||
    power.value -= sdPower.value
 | 
			
		||||
    fetchRunningJobs()
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("任务推送失败:" + e.message)
 | 
			
		||||
  params.value.session_id = getSessionId();
 | 
			
		||||
  httpPost("/api/sd/image", params.value)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("绘画任务推送成功,请耐心等待任务执行...");
 | 
			
		||||
      power.value -= sdPower.value;
 | 
			
		||||
      fetchRunningJobs();
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("任务推送失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showTask = (row) => {
 | 
			
		||||
  item.value = row
 | 
			
		||||
  showTaskDialog.value = true
 | 
			
		||||
}
 | 
			
		||||
  item.value = row;
 | 
			
		||||
  showTaskDialog.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const copyParams = (row) => {
 | 
			
		||||
  params.value = row.params
 | 
			
		||||
  showTaskDialog.value = false
 | 
			
		||||
}
 | 
			
		||||
  params.value = row.params;
 | 
			
		||||
  showTaskDialog.value = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeImage = (item) => {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会删除任务和图片,继续操作码?',
 | 
			
		||||
      '删除提示',
 | 
			
		||||
      {
 | 
			
		||||
        confirmButtonText: '确认',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet("/api/sd/remove", {id: item.id}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      page.value = 0
 | 
			
		||||
      isOver.value = false
 | 
			
		||||
      fetchFinishJobs()
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("任务删除失败:" + e.message)
 | 
			
		||||
  ElMessageBox.confirm("此操作将会删除任务和图片,继续操作码?", "删除提示", {
 | 
			
		||||
    confirmButtonText: "确认",
 | 
			
		||||
    cancelButtonText: "取消",
 | 
			
		||||
    type: "warning"
 | 
			
		||||
  })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      httpGet("/api/sd/remove", { id: item.id })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          ElMessage.success("任务删除成功");
 | 
			
		||||
          page.value = 0;
 | 
			
		||||
          isOver.value = false;
 | 
			
		||||
          fetchFinishJobs();
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("任务删除失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 发布图片到作品墙
 | 
			
		||||
const publishImage = (item, action) => {
 | 
			
		||||
  let text = "图片发布"
 | 
			
		||||
  let text = "图片发布";
 | 
			
		||||
  if (action === false) {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
    text = "取消发布";
 | 
			
		||||
  }
 | 
			
		||||
  httpGet("/api/sd/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
    ElMessage.success(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
    page.value = 0
 | 
			
		||||
    isOver.value = false
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error(text + "失败:" + e.message)
 | 
			
		||||
  httpGet("/api/sd/publish", { id: item.id, action: action })
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success(text + "成功");
 | 
			
		||||
      item.publish = action;
 | 
			
		||||
      page.value = 0;
 | 
			
		||||
      isOver.value = false;
 | 
			
		||||
      item.publish = action;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error(text + "失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isGenerating = ref(false)
 | 
			
		||||
const isGenerating = ref(false);
 | 
			
		||||
const generatePrompt = () => {
 | 
			
		||||
  if (params.value.prompt === "") {
 | 
			
		||||
    return showMessageError("请输入原始提示词")
 | 
			
		||||
    return showMessageError("请输入原始提示词");
 | 
			
		||||
  }
 | 
			
		||||
  isGenerating.value = true
 | 
			
		||||
  httpPost("/api/prompt/image", {prompt: params.value.prompt}).then(res => {
 | 
			
		||||
    params.value.prompt = res.data
 | 
			
		||||
    isGenerating.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("生成提示词失败:"+e.message)
 | 
			
		||||
    isGenerating.value = false
 | 
			
		||||
  isGenerating.value = true;
 | 
			
		||||
  httpPost("/api/prompt/image", { prompt: params.value.prompt })
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      params.value.prompt = res.data;
 | 
			
		||||
      isGenerating.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      showMessageError("生成提示词失败:" + e.message);
 | 
			
		||||
      isGenerating.value = false;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,13 @@
 | 
			
		||||
          </el-radio-group>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="waterfall" :style="{ height:listBoxHeight + 'px' }" id="waterfall-box">
 | 
			
		||||
        <v3-waterfall v-if="imgType === 'mj'"
 | 
			
		||||
      <div
 | 
			
		||||
        class="waterfall"
 | 
			
		||||
        :style="{ height: listBoxHeight + 'px' }"
 | 
			
		||||
        id="waterfall-box"
 | 
			
		||||
      >
 | 
			
		||||
        <v3-waterfall
 | 
			
		||||
          v-if="imgType === 'mj'"
 | 
			
		||||
          id="waterfall"
 | 
			
		||||
          :list="data['mj']"
 | 
			
		||||
          srcKey="img_thumb"
 | 
			
		||||
@@ -22,26 +27,27 @@
 | 
			
		||||
          :distanceToScroll="100"
 | 
			
		||||
          :isLoading="loading"
 | 
			
		||||
          :isOver="isOver"
 | 
			
		||||
                      @scrollReachBottom="getNext">
 | 
			
		||||
          @scrollReachBottom="getNext"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="slotProp">
 | 
			
		||||
            <div class="list-item">
 | 
			
		||||
              <div class="image">
 | 
			
		||||
                <el-image :src="slotProp.item['img_thumb']"
 | 
			
		||||
                <el-image
 | 
			
		||||
                  :src="slotProp.item['img_thumb']"
 | 
			
		||||
                  :zoom-rate="1.2"
 | 
			
		||||
                  :preview-src-list="[slotProp.item['img_url']]"
 | 
			
		||||
                  :preview-teleported="true"
 | 
			
		||||
                  :initial-index="10"
 | 
			
		||||
                          loading="lazy">
 | 
			
		||||
                  loading="lazy"
 | 
			
		||||
                >
 | 
			
		||||
                  <template #placeholder>
 | 
			
		||||
                    <div class="image-slot">
 | 
			
		||||
                      正在加载图片
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="image-slot">正在加载图片</div>
 | 
			
		||||
                  </template>
 | 
			
		||||
 | 
			
		||||
                  <template #error>
 | 
			
		||||
                    <div class="image-slot">
 | 
			
		||||
                      <el-icon>
 | 
			
		||||
                        <Picture/>
 | 
			
		||||
                        <Picture />
 | 
			
		||||
                      </el-icon>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </template>
 | 
			
		||||
@@ -50,29 +56,30 @@
 | 
			
		||||
              <div class="opt">
 | 
			
		||||
                <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                    effect="light"
 | 
			
		||||
                  content="复制提示词"
 | 
			
		||||
                  placement="top"
 | 
			
		||||
                >
 | 
			
		||||
                  <el-icon class="copy-prompt-wall" :data-clipboard-text="slotProp.item.prompt">
 | 
			
		||||
                    <DocumentCopy/>
 | 
			
		||||
                  <el-icon
 | 
			
		||||
                    class="copy-prompt-wall"
 | 
			
		||||
                    :data-clipboard-text="slotProp.item.prompt"
 | 
			
		||||
                  >
 | 
			
		||||
                    <DocumentCopy />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
 | 
			
		||||
                <el-tooltip
 | 
			
		||||
                    class="box-item"
 | 
			
		||||
                    effect="light"
 | 
			
		||||
                    content="画同款"
 | 
			
		||||
                    placement="top"
 | 
			
		||||
                >
 | 
			
		||||
                  <i class="iconfont icon-palette-pen" @click="drawSameMj(slotProp.item)"></i>
 | 
			
		||||
                <el-tooltip class="box-item" content="画同款" placement="top">
 | 
			
		||||
                  <i
 | 
			
		||||
                    class="iconfont icon-palette-pen"
 | 
			
		||||
                    @click="drawSameMj(slotProp.item)"
 | 
			
		||||
                  ></i>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
        </v3-waterfall>
 | 
			
		||||
 | 
			
		||||
        <v3-waterfall v-else-if="imgType === 'dall'"
 | 
			
		||||
        <v3-waterfall
 | 
			
		||||
          v-else-if="imgType === 'dall'"
 | 
			
		||||
          id="waterfall"
 | 
			
		||||
          :list="data['dall']"
 | 
			
		||||
          srcKey="img_thumb"
 | 
			
		||||
@@ -82,26 +89,27 @@
 | 
			
		||||
          :distanceToScroll="100"
 | 
			
		||||
          :isLoading="loading"
 | 
			
		||||
          :isOver="isOver"
 | 
			
		||||
                      @scrollReachBottom="getNext">
 | 
			
		||||
          @scrollReachBottom="getNext"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="slotProp">
 | 
			
		||||
            <div class="list-item">
 | 
			
		||||
              <div class="image">
 | 
			
		||||
                <el-image :src="slotProp.item['img_thumb']"
 | 
			
		||||
                <el-image
 | 
			
		||||
                  :src="slotProp.item['img_thumb']"
 | 
			
		||||
                  :zoom-rate="1.2"
 | 
			
		||||
                  :preview-src-list="[slotProp.item['img_url']]"
 | 
			
		||||
                  :preview-teleported="true"
 | 
			
		||||
                  :initial-index="10"
 | 
			
		||||
                          loading="lazy">
 | 
			
		||||
                  loading="lazy"
 | 
			
		||||
                >
 | 
			
		||||
                  <template #placeholder>
 | 
			
		||||
                    <div class="image-slot">
 | 
			
		||||
                      正在加载图片
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="image-slot">正在加载图片</div>
 | 
			
		||||
                  </template>
 | 
			
		||||
 | 
			
		||||
                  <template #error>
 | 
			
		||||
                    <div class="image-slot">
 | 
			
		||||
                      <el-icon>
 | 
			
		||||
                        <Picture/>
 | 
			
		||||
                        <Picture />
 | 
			
		||||
                      </el-icon>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </template>
 | 
			
		||||
@@ -110,12 +118,14 @@
 | 
			
		||||
              <div class="opt">
 | 
			
		||||
                <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                    effect="light"
 | 
			
		||||
                  content="复制提示词"
 | 
			
		||||
                  placement="top"
 | 
			
		||||
                >
 | 
			
		||||
                  <el-icon class="copy-prompt-wall" :data-clipboard-text="slotProp.item.prompt">
 | 
			
		||||
                    <DocumentCopy/>
 | 
			
		||||
                  <el-icon
 | 
			
		||||
                    class="copy-prompt-wall"
 | 
			
		||||
                    :data-clipboard-text="slotProp.item.prompt"
 | 
			
		||||
                  >
 | 
			
		||||
                    <DocumentCopy />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
              </div>
 | 
			
		||||
@@ -123,7 +133,8 @@
 | 
			
		||||
          </template>
 | 
			
		||||
        </v3-waterfall>
 | 
			
		||||
 | 
			
		||||
        <v3-waterfall v-else
 | 
			
		||||
        <v3-waterfall
 | 
			
		||||
          v-else
 | 
			
		||||
          id="waterfall"
 | 
			
		||||
          :list="data['sd']"
 | 
			
		||||
          srcKey="img_thumb"
 | 
			
		||||
@@ -133,22 +144,24 @@
 | 
			
		||||
          :distanceToScroll="100"
 | 
			
		||||
          :isLoading="loading"
 | 
			
		||||
          :isOver="isOver"
 | 
			
		||||
                      @scrollReachBottom="getNext">
 | 
			
		||||
          @scrollReachBottom="getNext"
 | 
			
		||||
        >
 | 
			
		||||
          <template #default="slotProp">
 | 
			
		||||
            <div class="list-item">
 | 
			
		||||
              <div class="image">
 | 
			
		||||
                <el-image :src="slotProp.item['img_thumb']" loading="lazy"
 | 
			
		||||
                          @click="showTask(slotProp.item)">
 | 
			
		||||
                <el-image
 | 
			
		||||
                  :src="slotProp.item['img_thumb']"
 | 
			
		||||
                  loading="lazy"
 | 
			
		||||
                  @click="showTask(slotProp.item)"
 | 
			
		||||
                >
 | 
			
		||||
                  <template #placeholder>
 | 
			
		||||
                    <div class="image-slot">
 | 
			
		||||
                      正在加载图片
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="image-slot">正在加载图片</div>
 | 
			
		||||
                  </template>
 | 
			
		||||
 | 
			
		||||
                  <template #error>
 | 
			
		||||
                    <div class="image-slot">
 | 
			
		||||
                      <el-icon>
 | 
			
		||||
                        <Picture/>
 | 
			
		||||
                        <Picture />
 | 
			
		||||
                      </el-icon>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </template>
 | 
			
		||||
@@ -159,31 +172,36 @@
 | 
			
		||||
        </v3-waterfall>
 | 
			
		||||
 | 
			
		||||
        <div class="footer" v-if="isOver">
 | 
			
		||||
          <span>没有更多数据了</span>
 | 
			
		||||
          <i class="iconfont icon-face"></i>
 | 
			
		||||
          <el-empty
 | 
			
		||||
            :image-size="100"
 | 
			
		||||
            :image="nodata"
 | 
			
		||||
            description="没有更多数据了"
 | 
			
		||||
          />
 | 
			
		||||
          <!-- <span>没有更多数据了</span>
 | 
			
		||||
          <i class="iconfont icon-face"></i> -->
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <back-top :right="30" :bottom="30" bg-color="#0f7a71"/>
 | 
			
		||||
 | 
			
		||||
      </div><!-- end of waterfall -->
 | 
			
		||||
 | 
			
		||||
        <back-top :right="30" :bottom="30" />
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- end of waterfall -->
 | 
			
		||||
    </div>
 | 
			
		||||
    <!-- 任务详情弹框 -->
 | 
			
		||||
    <el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
 | 
			
		||||
      <el-row :gutter="20">
 | 
			
		||||
        <el-col :span="16">
 | 
			
		||||
          <div class="img-container" :style="{maxHeight: fullImgHeight+'px'}">
 | 
			
		||||
          <div
 | 
			
		||||
            class="img-container"
 | 
			
		||||
            :style="{ maxHeight: fullImgHeight + 'px' }"
 | 
			
		||||
          >
 | 
			
		||||
            <el-image :src="item['img_url']" fit="contain">
 | 
			
		||||
              <template #placeholder>
 | 
			
		||||
                <div class="image-slot">
 | 
			
		||||
                  正在加载图片
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="image-slot">正在加载图片</div>
 | 
			
		||||
              </template>
 | 
			
		||||
 | 
			
		||||
              <template #error>
 | 
			
		||||
                <div class="image-slot">
 | 
			
		||||
                  <el-icon>
 | 
			
		||||
                    <Picture/>
 | 
			
		||||
                    <Picture />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </div>
 | 
			
		||||
              </template>
 | 
			
		||||
@@ -193,26 +211,27 @@
 | 
			
		||||
        <el-col :span="8">
 | 
			
		||||
          <div class="task-info">
 | 
			
		||||
            <div class="info-line">
 | 
			
		||||
              <el-divider>
 | 
			
		||||
                正向提示词
 | 
			
		||||
              </el-divider>
 | 
			
		||||
              <el-divider> 正向提示词 </el-divider>
 | 
			
		||||
              <div class="prompt">
 | 
			
		||||
                <span>{{ item.prompt }}</span>
 | 
			
		||||
                <el-icon class="copy-prompt-wall" :data-clipboard-text="item.prompt">
 | 
			
		||||
                  <DocumentCopy/>
 | 
			
		||||
                <el-icon
 | 
			
		||||
                  class="copy-prompt-wall"
 | 
			
		||||
                  :data-clipboard-text="item.prompt"
 | 
			
		||||
                >
 | 
			
		||||
                  <DocumentCopy />
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="info-line">
 | 
			
		||||
              <el-divider>
 | 
			
		||||
                反向提示词
 | 
			
		||||
              </el-divider>
 | 
			
		||||
              <el-divider> 反向提示词 </el-divider>
 | 
			
		||||
              <div class="prompt">
 | 
			
		||||
                <span>{{ item.params.negative_prompt }}</span>
 | 
			
		||||
                <el-icon class="copy-prompt-wall" :data-clipboard-text="item.params.negative_prompt">
 | 
			
		||||
                  <DocumentCopy/>
 | 
			
		||||
                <el-icon
 | 
			
		||||
                  class="copy-prompt-wall"
 | 
			
		||||
                  :data-clipboard-text="item.params.negative_prompt"
 | 
			
		||||
                >
 | 
			
		||||
                  <DocumentCopy />
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -227,7 +246,9 @@
 | 
			
		||||
            <div class="info-line">
 | 
			
		||||
              <div class="wrapper">
 | 
			
		||||
                <label>图片尺寸:</label>
 | 
			
		||||
                <div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
 | 
			
		||||
                <div class="item-value">
 | 
			
		||||
                  {{ item.params.width }} x {{ item.params.height }}
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
@@ -253,9 +274,7 @@
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div v-if="item.params.hd_fix">
 | 
			
		||||
              <el-divider>
 | 
			
		||||
                高清修复
 | 
			
		||||
              </el-divider>
 | 
			
		||||
              <el-divider> 高清修复 </el-divider>
 | 
			
		||||
              <div class="info-line">
 | 
			
		||||
                <div class="wrapper">
 | 
			
		||||
                  <label>重绘幅度:</label>
 | 
			
		||||
@@ -286,146 +305,151 @@
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="copy-params">
 | 
			
		||||
              <el-button type="primary" round @click="drawSameSd(item)">画一张同款的</el-button>
 | 
			
		||||
              <el-button type="primary" round @click="drawSameSd(item)"
 | 
			
		||||
                >画一张同款的</el-button
 | 
			
		||||
              >
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-col>
 | 
			
		||||
      </el-row>
 | 
			
		||||
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {DocumentCopy, Picture} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import nodata from "@/assets/img/no-data.png";
 | 
			
		||||
import { nextTick, onMounted, onUnmounted, ref } from "vue";
 | 
			
		||||
import { DocumentCopy, Picture } from "@element-plus/icons-vue";
 | 
			
		||||
import { httpGet } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import BackTop from "@/components/BackTop.vue";
 | 
			
		||||
 | 
			
		||||
const data = ref({
 | 
			
		||||
  "mj": [],
 | 
			
		||||
  "sd": [],
 | 
			
		||||
  "dall": [],
 | 
			
		||||
})
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const isOver = ref(false)
 | 
			
		||||
const imgType = ref("mj") // 图片类别
 | 
			
		||||
const listBoxHeight = window.innerHeight - 124
 | 
			
		||||
const colWidth = ref(220)
 | 
			
		||||
const fullImgHeight = ref(window.innerHeight - 60)
 | 
			
		||||
const showTaskDialog = ref(false)
 | 
			
		||||
const item = ref({})
 | 
			
		||||
  mj: [],
 | 
			
		||||
  sd: [],
 | 
			
		||||
  dall: []
 | 
			
		||||
});
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
const isOver = ref(false);
 | 
			
		||||
const imgType = ref("mj"); // 图片类别
 | 
			
		||||
const listBoxHeight = window.innerHeight - 124;
 | 
			
		||||
const colWidth = ref(220);
 | 
			
		||||
const fullImgHeight = ref(window.innerHeight - 60);
 | 
			
		||||
const showTaskDialog = ref(false);
 | 
			
		||||
const item = ref({});
 | 
			
		||||
 | 
			
		||||
// 计算瀑布流列宽度
 | 
			
		||||
const calcColWidth = () => {
 | 
			
		||||
  const listBoxWidth = window.innerWidth - 60 - 80
 | 
			
		||||
  const rows = Math.floor(listBoxWidth / colWidth.value)
 | 
			
		||||
  colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows)
 | 
			
		||||
}
 | 
			
		||||
calcColWidth()
 | 
			
		||||
  const listBoxWidth = window.innerWidth - 60 - 80;
 | 
			
		||||
  const rows = Math.floor(listBoxWidth / colWidth.value);
 | 
			
		||||
  colWidth.value = Math.floor((listBoxWidth - (rows - 1) * 12) / rows);
 | 
			
		||||
};
 | 
			
		||||
calcColWidth();
 | 
			
		||||
window.onresize = () => {
 | 
			
		||||
  calcColWidth()
 | 
			
		||||
}
 | 
			
		||||
  calcColWidth();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const page = ref(0)
 | 
			
		||||
const pageSize = ref(15)
 | 
			
		||||
const page = ref(0);
 | 
			
		||||
const pageSize = ref(15);
 | 
			
		||||
// 获取下一页数据
 | 
			
		||||
const getNext = () => {
 | 
			
		||||
  if (isOver.value) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  page.value = page.value + 1
 | 
			
		||||
  let url = ""
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  page.value = page.value + 1;
 | 
			
		||||
  let url = "";
 | 
			
		||||
  switch (imgType.value) {
 | 
			
		||||
    case "mj":
 | 
			
		||||
      url = "/api/mj/imgWall"
 | 
			
		||||
      break
 | 
			
		||||
      url = "/api/mj/imgWall";
 | 
			
		||||
      break;
 | 
			
		||||
    case "sd":
 | 
			
		||||
      url = "/api/sd/imgWall"
 | 
			
		||||
      break
 | 
			
		||||
      url = "/api/sd/imgWall";
 | 
			
		||||
      break;
 | 
			
		||||
    case "dall":
 | 
			
		||||
      url = "/api/dall/imgWall"
 | 
			
		||||
      break
 | 
			
		||||
      url = "/api/dall/imgWall";
 | 
			
		||||
      break;
 | 
			
		||||
  }
 | 
			
		||||
  httpGet(`${url}?page=${page.value}&page_size=${pageSize.value}`).then(res => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  httpGet(`${url}?page=${page.value}&page_size=${pageSize.value}`)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
      if (!res.data.items || res.data.items.length === 0) {
 | 
			
		||||
      isOver.value = true
 | 
			
		||||
      return
 | 
			
		||||
        isOver.value = true;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 生成缩略图
 | 
			
		||||
    const imageList = res.data.items
 | 
			
		||||
      const imageList = res.data.items;
 | 
			
		||||
      for (let i = 0; i < imageList.length; i++) {
 | 
			
		||||
      imageList[i]["img_thumb"] = imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75"
 | 
			
		||||
        imageList[i]["img_thumb"] =
 | 
			
		||||
          imageList[i]["img_url"] + "?imageView2/4/w/300/h/0/q/75";
 | 
			
		||||
      }
 | 
			
		||||
      if (data.value[imgType.value].length === 0) {
 | 
			
		||||
      data.value[imgType.value] = imageList
 | 
			
		||||
      return
 | 
			
		||||
        data.value[imgType.value] = imageList;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (imageList.length < pageSize.value) {
 | 
			
		||||
      isOver.value = true
 | 
			
		||||
        isOver.value = true;
 | 
			
		||||
      }
 | 
			
		||||
    data.value[imgType.value] = data.value[imgType.value].concat(imageList)
 | 
			
		||||
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取图片失败:" + e.message)
 | 
			
		||||
      data.value[imgType.value] = data.value[imgType.value].concat(imageList);
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取图片失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
getNext()
 | 
			
		||||
getNext();
 | 
			
		||||
 | 
			
		||||
const clipboard = ref(null)
 | 
			
		||||
const clipboard = ref(null);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  clipboard.value = new Clipboard('.copy-prompt-wall');
 | 
			
		||||
  clipboard.value.on('success', () => {
 | 
			
		||||
  clipboard.value = new Clipboard(".copy-prompt-wall");
 | 
			
		||||
  clipboard.value.on("success", () => {
 | 
			
		||||
    ElMessage.success("复制成功!");
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.value.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
  clipboard.value.on("error", () => {
 | 
			
		||||
    ElMessage.error("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  clipboard.value.destroy()
 | 
			
		||||
})
 | 
			
		||||
  clipboard.value.destroy();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const changeImgType = () => {
 | 
			
		||||
  console.log(imgType.value)
 | 
			
		||||
  document.getElementById('waterfall-box').scrollTo(0, 0)
 | 
			
		||||
  page.value = 0
 | 
			
		||||
  console.log(imgType.value);
 | 
			
		||||
  document.getElementById("waterfall-box").scrollTo(0, 0);
 | 
			
		||||
  page.value = 0;
 | 
			
		||||
  data.value = {
 | 
			
		||||
    "mj": [],
 | 
			
		||||
    "sd": [],
 | 
			
		||||
    "dall": [],
 | 
			
		||||
  }
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  isOver.value = false
 | 
			
		||||
  nextTick(() => getNext())
 | 
			
		||||
}
 | 
			
		||||
    mj: [],
 | 
			
		||||
    sd: [],
 | 
			
		||||
    dall: []
 | 
			
		||||
  };
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  isOver.value = false;
 | 
			
		||||
  nextTick(() => getNext());
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showTask = (row) => {
 | 
			
		||||
  item.value = row
 | 
			
		||||
  showTaskDialog.value = true
 | 
			
		||||
}
 | 
			
		||||
  item.value = row;
 | 
			
		||||
  showTaskDialog.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const drawSameSd = (row) => {
 | 
			
		||||
  router.push({name: "image-sd", params: {copyParams: JSON.stringify(row.params)}})
 | 
			
		||||
}
 | 
			
		||||
  router.push({
 | 
			
		||||
    name: "image-sd",
 | 
			
		||||
    params: { copyParams: JSON.stringify(row.params) }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const drawSameMj = (row) => {
 | 
			
		||||
  router.push({name: "image-mj", params: {prompt: row.prompt}})
 | 
			
		||||
}
 | 
			
		||||
  router.push({ name: "image-mj", params: { prompt: row.prompt } });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
              v-if="!license.de_copy"
 | 
			
		||||
              class="box-item"
 | 
			
		||||
              effect="light"
 | 
			
		||||
              content="部署文档"
 | 
			
		||||
              placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
@@ -26,7 +25,6 @@
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
              v-if="!license.de_copy"
 | 
			
		||||
              class="box-item"
 | 
			
		||||
              effect="light"
 | 
			
		||||
              content="项目源码"
 | 
			
		||||
              placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
 
 | 
			
		||||
@@ -5,18 +5,25 @@
 | 
			
		||||
        <h2>会员推广计划</h2>
 | 
			
		||||
        <div class="share-box">
 | 
			
		||||
          <div class="info">
 | 
			
		||||
            我们非常欢迎您把此应用分享给您身边的朋友,分享成功注册后您和被邀请人都将获得 <strong>{{ invitePower }}</strong>
 | 
			
		||||
            我们非常欢迎您把此应用分享给您身边的朋友,分享成功注册后您和被邀请人都将获得
 | 
			
		||||
            <strong>{{ invitePower }}</strong>
 | 
			
		||||
            算力额度作为奖励。
 | 
			
		||||
            你可以保存下面的二维码或者直接复制分享您的专属推广链接发送给微信好友。
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="invite-qrcode">
 | 
			
		||||
            <el-image :src="qrImg"/>
 | 
			
		||||
            <el-image :src="qrImg" />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="invite-url">
 | 
			
		||||
            <span>{{ inviteURL }}</span>
 | 
			
		||||
            <el-button type="primary" plain class="copy-link" :data-clipboard-text="inviteURL">复制链接</el-button>
 | 
			
		||||
            <el-button
 | 
			
		||||
              type="primary"
 | 
			
		||||
              plain
 | 
			
		||||
              class="copy-link"
 | 
			
		||||
              :data-clipboard-text="inviteURL"
 | 
			
		||||
              >复制链接</el-button
 | 
			
		||||
            >
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
@@ -76,10 +83,10 @@
 | 
			
		||||
          </el-row>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <h2>您推荐用户</h2>
 | 
			
		||||
        <h2>您推荐的用户</h2>
 | 
			
		||||
 | 
			
		||||
        <div class="invite-logs">
 | 
			
		||||
          <invite-list v-if="isLogin"/>
 | 
			
		||||
          <invite-list v-if="isLogin" />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -87,69 +94,79 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import QRCode from "qrcode";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import { httpGet } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import InviteList from "@/components/InviteList.vue";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { checkSession, getSystemInfo } from "@/store/cache";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
const inviteURL = ref("")
 | 
			
		||||
const qrImg = ref("/images/wx.png")
 | 
			
		||||
const invitePower = ref(0)
 | 
			
		||||
const hits = ref(0)
 | 
			
		||||
const regNum = ref(0)
 | 
			
		||||
const rate = ref(0)
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
const inviteURL = ref("");
 | 
			
		||||
const qrImg = ref("/images/wx.png");
 | 
			
		||||
const invitePower = ref(0);
 | 
			
		||||
const hits = ref(0);
 | 
			
		||||
const regNum = ref(0);
 | 
			
		||||
const rate = ref(0);
 | 
			
		||||
const isLogin = ref(false);
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initData()
 | 
			
		||||
  initData();
 | 
			
		||||
 | 
			
		||||
  // 复制链接
 | 
			
		||||
  const clipboard = new Clipboard('.copy-link');
 | 
			
		||||
  clipboard.on('success', () => {
 | 
			
		||||
    ElMessage.success('复制成功!');
 | 
			
		||||
  })
 | 
			
		||||
  const clipboard = new Clipboard(".copy-link");
 | 
			
		||||
  clipboard.on("success", () => {
 | 
			
		||||
    ElMessage.success("复制成功!");
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
  clipboard.on("error", () => {
 | 
			
		||||
    ElMessage.error("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
    isLogin.value = true
 | 
			
		||||
    httpGet("/api/invite/code").then(res => {
 | 
			
		||||
      const text = `${location.protocol}//${location.host}/register?invite_code=${res.data.code}`
 | 
			
		||||
      hits.value = res.data["hits"]
 | 
			
		||||
      regNum.value = res.data["reg_num"]
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      isLogin.value = true;
 | 
			
		||||
      httpGet("/api/invite/code")
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          const text = `${location.protocol}//${location.host}/register?invite_code=${res.data.code}`;
 | 
			
		||||
          hits.value = res.data["hits"];
 | 
			
		||||
          regNum.value = res.data["reg_num"];
 | 
			
		||||
          if (hits.value > 0) {
 | 
			
		||||
        rate.value = ((regNum.value / hits.value) * 100).toFixed(2)
 | 
			
		||||
            rate.value = ((regNum.value / hits.value) * 100).toFixed(2);
 | 
			
		||||
          }
 | 
			
		||||
      QRCode.toDataURL(text, {width: 400, height: 400, margin: 2}, (error, url) => {
 | 
			
		||||
          QRCode.toDataURL(
 | 
			
		||||
            text,
 | 
			
		||||
            { width: 400, height: 400, margin: 2 },
 | 
			
		||||
            (error, url) => {
 | 
			
		||||
              if (error) {
 | 
			
		||||
          console.error(error)
 | 
			
		||||
                console.error(error);
 | 
			
		||||
              } else {
 | 
			
		||||
                qrImg.value = url;
 | 
			
		||||
              }
 | 
			
		||||
      });
 | 
			
		||||
      inviteURL.value = text
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("获取邀请码失败:" + e.message)
 | 
			
		||||
            }
 | 
			
		||||
          );
 | 
			
		||||
          inviteURL.value = text;
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("获取邀请码失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    getSystemInfo().then(res => {
 | 
			
		||||
      invitePower.value = res.data["invite_power"]
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
      getSystemInfo()
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          invitePower.value = res.data["invite_power"];
 | 
			
		||||
        })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("获取系统配置失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
}
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      store.setShowLoginDialog(true);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@@ -157,7 +174,7 @@ const initData = () => {
 | 
			
		||||
.page-invitation {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  background-color: #282c34;
 | 
			
		||||
  // background-color: #282c34;
 | 
			
		||||
  height 100%
 | 
			
		||||
  overflow-x hidden
 | 
			
		||||
  overflow-y visible
 | 
			
		||||
@@ -167,17 +184,18 @@ const initData = () => {
 | 
			
		||||
    flex-flow column
 | 
			
		||||
    max-width 1000px
 | 
			
		||||
    width 100%
 | 
			
		||||
    color #e1e1e1
 | 
			
		||||
    color: var(--text-theme-color);
 | 
			
		||||
 | 
			
		||||
    h2 {
 | 
			
		||||
      color #ffffff;
 | 
			
		||||
      color: var(--theme-textcolor-normal);
 | 
			
		||||
      text-align center
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .share-box {
 | 
			
		||||
      .info {
 | 
			
		||||
        line-height 1.5
 | 
			
		||||
        border 1px solid #444444
 | 
			
		||||
        // border 1px solid #444444
 | 
			
		||||
        background:var(--chat-bg)
 | 
			
		||||
        border-radius 10px
 | 
			
		||||
        padding 10px
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
      <div class="images">
 | 
			
		||||
        <template v-for="(img, index) in images" :key="img">
 | 
			
		||||
          <div class="item">
 | 
			
		||||
            <el-image :src="replaceImg(img)" fit="cover"/>
 | 
			
		||||
            <el-image :src="replaceImg(img)" fit="cover" />
 | 
			
		||||
            <el-icon @click="remove(img)"><CircleCloseFilled /></el-icon>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="btn-swap" v-if="images.length === 2 && index === 0">
 | 
			
		||||
@@ -30,7 +30,8 @@
 | 
			
		||||
            :rows="row"
 | 
			
		||||
            v-model="formData.prompt"
 | 
			
		||||
            placeholder="请输入提示词或者上传图片"
 | 
			
		||||
              autofocus>
 | 
			
		||||
            autofocus
 | 
			
		||||
          >
 | 
			
		||||
          </textarea>
 | 
			
		||||
          <div class="send-icon" @click="create">
 | 
			
		||||
            <i class="iconfont icon-send"></i>
 | 
			
		||||
@@ -39,26 +40,34 @@
 | 
			
		||||
 | 
			
		||||
        <div class="params">
 | 
			
		||||
          <div class="item-group">
 | 
			
		||||
            <el-button class="generate-btn" size="small" @click="generatePrompt" color="#5865f2" :disabled="isGenerating">
 | 
			
		||||
            <el-button
 | 
			
		||||
              class="generate-btn"
 | 
			
		||||
              size="small"
 | 
			
		||||
              @click="generatePrompt"
 | 
			
		||||
              color="#5865f2"
 | 
			
		||||
              :disabled="isGenerating"
 | 
			
		||||
            >
 | 
			
		||||
              <i class="iconfont icon-chuangzuo" style="margin-right: 5px"></i>
 | 
			
		||||
              <span>生成AI视频提示词</span>
 | 
			
		||||
            </el-button>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="item-group">
 | 
			
		||||
            <span class="label">循环参考图</span>
 | 
			
		||||
            <el-switch  v-model="formData.loop" size="small" style="--el-switch-on-color:#BF78BF;" />
 | 
			
		||||
            <el-switch v-model="formData.loop" size="small" />
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="item-group">
 | 
			
		||||
            <span class="label">提示词优化</span>
 | 
			
		||||
            <el-switch  v-model="formData.expand_prompt" size="small" style="--el-switch-on-color:#BF78BF;" />
 | 
			
		||||
            <el-switch v-model="formData.expand_prompt" size="small" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <el-container class="video-container" v-loading="loading" element-loading-background="rgba(100,100,100,0.3)">
 | 
			
		||||
    <el-container
 | 
			
		||||
      class="video-container"
 | 
			
		||||
      v-loading="loading"
 | 
			
		||||
      element-loading-background="rgba(100,100,100,0.3)"
 | 
			
		||||
    >
 | 
			
		||||
      <h2 class="h-title">你的作品</h2>
 | 
			
		||||
 | 
			
		||||
      <div class="list-box" v-if="!noData">
 | 
			
		||||
@@ -67,36 +76,63 @@
 | 
			
		||||
            <div class="left">
 | 
			
		||||
              <div class="container">
 | 
			
		||||
                <div v-if="item.progress === 100">
 | 
			
		||||
                  <video class="video" :src="replaceImg(item.video_url)"  preload="auto" loop="loop" muted="muted">
 | 
			
		||||
                  <video
 | 
			
		||||
                    class="video"
 | 
			
		||||
                    :src="replaceImg(item.video_url)"
 | 
			
		||||
                    preload="auto"
 | 
			
		||||
                    loop="loop"
 | 
			
		||||
                    muted="muted"
 | 
			
		||||
                  >
 | 
			
		||||
                    您的浏览器不支持视频播放
 | 
			
		||||
                  </video>
 | 
			
		||||
                  <button class="play" @click="play(item)">
 | 
			
		||||
                    <img src="/images/play.svg" alt=""/>
 | 
			
		||||
                    <img src="/images/play.svg" alt="" />
 | 
			
		||||
                  </button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <el-image :src="item.cover_url" fit="cover" v-else-if="item.progress > 100" />
 | 
			
		||||
                <el-image
 | 
			
		||||
                  :src="item.cover_url"
 | 
			
		||||
                  fit="cover"
 | 
			
		||||
                  v-else-if="item.progress > 100"
 | 
			
		||||
                />
 | 
			
		||||
                <generating message="正在生成视频" v-else />
 | 
			
		||||
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="center">
 | 
			
		||||
              <div class="failed" v-if="item.progress === 101">任务执行失败:{{item.err_msg}},任务提示词:{{item.prompt}}</div>
 | 
			
		||||
              <div class="prompt" v-else>{{item.prompt}}</div>
 | 
			
		||||
              <div class="failed" v-if="item.progress === 101">
 | 
			
		||||
                任务执行失败:{{ item.err_msg }},任务提示词:{{ item.prompt }}
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="prompt" v-else>{{ item.prompt }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="right" v-if="item.progress === 100">
 | 
			
		||||
              <div class="tools">
 | 
			
		||||
                <button class="btn btn-publish">
 | 
			
		||||
                  <span class="text">发布</span>
 | 
			
		||||
                  <black-switch v-model:value="item.publish" @change="publishJob(item)" size="small" />
 | 
			
		||||
                  <black-switch
 | 
			
		||||
                    v-model:value="item.publish"
 | 
			
		||||
                    @change="publishJob(item)"
 | 
			
		||||
                    size="small"
 | 
			
		||||
                  />
 | 
			
		||||
                </button>
 | 
			
		||||
 | 
			
		||||
                <el-tooltip effect="light" content="下载视频" placement="top">
 | 
			
		||||
                  <button class="btn btn-icon" @click="download(item)" :disabled="item.downloading">
 | 
			
		||||
                    <i class="iconfont icon-download" v-if="!item.downloading"></i>
 | 
			
		||||
                    <el-image src="/images/loading.gif" class="downloading" fit="cover" v-else />
 | 
			
		||||
                <el-tooltip content="下载视频" placement="top">
 | 
			
		||||
                  <button
 | 
			
		||||
                    class="btn btn-icon"
 | 
			
		||||
                    @click="download(item)"
 | 
			
		||||
                    :disabled="item.downloading"
 | 
			
		||||
                  >
 | 
			
		||||
                    <i
 | 
			
		||||
                      class="iconfont icon-download"
 | 
			
		||||
                      v-if="!item.downloading"
 | 
			
		||||
                    ></i>
 | 
			
		||||
                    <el-image
 | 
			
		||||
                      src="/images/loading.gif"
 | 
			
		||||
                      class="downloading"
 | 
			
		||||
                      fit="cover"
 | 
			
		||||
                      v-else
 | 
			
		||||
                    />
 | 
			
		||||
                  </button>
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
                <el-tooltip effect="light" content="删除" placement="top">
 | 
			
		||||
                <el-tooltip content="删除" placement="top">
 | 
			
		||||
                  <button class="btn btn-icon" @click="removeJob(item)">
 | 
			
		||||
                    <i class="iconfont icon-remove"></i>
 | 
			
		||||
                  </button>
 | 
			
		||||
@@ -111,25 +147,43 @@
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <el-empty :image-size="100" description="没有任何作品,赶紧去创作吧!" v-else />
 | 
			
		||||
      <el-empty
 | 
			
		||||
        :image-size="100"
 | 
			
		||||
        :image="nodata"
 | 
			
		||||
        description="没有任何作品,赶紧去创作吧!"
 | 
			
		||||
        v-else
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <div class="pagination">
 | 
			
		||||
        <el-pagination v-if="total > pageSize" background
 | 
			
		||||
          style="--el-pagination-button-bg-color:#414141;
 | 
			
		||||
          --el-pagination-button-color:#d1d1d1;
 | 
			
		||||
          --el-disabled-bg-color:#414141;
 | 
			
		||||
          --el-color-primary:#666666;
 | 
			
		||||
          --el-pagination-hover-color:#e1e1e1"
 | 
			
		||||
        <el-pagination
 | 
			
		||||
          v-if="total > pageSize"
 | 
			
		||||
          background
 | 
			
		||||
          style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
 | 
			
		||||
          layout="total,prev, pager, next"
 | 
			
		||||
          :hide-on-single-page="true"
 | 
			
		||||
          v-model:current-page="page"
 | 
			
		||||
          v-model:page-size="pageSize"
 | 
			
		||||
          @current-change="fetchData(page)"
 | 
			
		||||
          :total="total"/>
 | 
			
		||||
          :total="total"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </el-container>
 | 
			
		||||
    <black-dialog v-model:show="showDialog" title="预览视频" hide-footer @cancal="showDialog = false" width="auto">
 | 
			
		||||
      <video style="width: 100%; max-height: 90vh;" :src="currentVideoUrl"  preload="auto" :autoplay="true" loop="loop" muted="muted" v-show="showDialog">
 | 
			
		||||
    <black-dialog
 | 
			
		||||
      v-model:show="showDialog"
 | 
			
		||||
      title="预览视频"
 | 
			
		||||
      hide-footer
 | 
			
		||||
      @cancal="showDialog = false"
 | 
			
		||||
      width="auto"
 | 
			
		||||
    >
 | 
			
		||||
      <video
 | 
			
		||||
        style="width: 100%; max-height: 90vh"
 | 
			
		||||
        :src="currentVideoUrl"
 | 
			
		||||
        preload="auto"
 | 
			
		||||
        :autoplay="true"
 | 
			
		||||
        loop="loop"
 | 
			
		||||
        muted="muted"
 | 
			
		||||
        v-show="showDialog"
 | 
			
		||||
      >
 | 
			
		||||
        您的浏览器不支持视频播放
 | 
			
		||||
      </video>
 | 
			
		||||
    </black-dialog>
 | 
			
		||||
@@ -137,184 +191,199 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, onUnmounted, reactive, ref} from "vue";
 | 
			
		||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpDownload, httpPost, httpGet} from "@/utils/http";
 | 
			
		||||
import {checkSession, getClientId} from "@/store/cache";
 | 
			
		||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
 | 
			
		||||
import { replaceImg } from "@/utils/libs"
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import nodata from "@/assets/img/no-data.png";
 | 
			
		||||
 | 
			
		||||
import { onMounted, onUnmounted, reactive, ref } from "vue";
 | 
			
		||||
import { CircleCloseFilled } from "@element-plus/icons-vue";
 | 
			
		||||
import { httpDownload, httpPost, httpGet } from "@/utils/http";
 | 
			
		||||
import { checkSession, getClientId } from "@/store/cache";
 | 
			
		||||
import { showMessageError, showMessageOK } from "@/utils/dialog";
 | 
			
		||||
import { replaceImg } from "@/utils/libs";
 | 
			
		||||
import { ElMessage, ElMessageBox } from "element-plus";
 | 
			
		||||
import BlackSwitch from "@/components/ui/BlackSwitch.vue";
 | 
			
		||||
import Generating from "@/components/ui/Generating.vue";
 | 
			
		||||
import BlackDialog from "@/components/ui/BlackDialog.vue";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
const currentVideoUrl = ref('')
 | 
			
		||||
const row = ref(1)
 | 
			
		||||
const images = ref([])
 | 
			
		||||
const showDialog = ref(false);
 | 
			
		||||
const currentVideoUrl = ref("");
 | 
			
		||||
const row = ref(1);
 | 
			
		||||
const images = ref([]);
 | 
			
		||||
 | 
			
		||||
const formData = reactive({
 | 
			
		||||
  client_id: getClientId(),
 | 
			
		||||
  prompt: '',
 | 
			
		||||
  prompt: "",
 | 
			
		||||
  expand_prompt: false,
 | 
			
		||||
  loop: false,
 | 
			
		||||
  first_frame_img: '',
 | 
			
		||||
  end_frame_img: ''
 | 
			
		||||
})
 | 
			
		||||
  first_frame_img: "",
 | 
			
		||||
  end_frame_img: ""
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
onMounted(()=>{
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
    fetchData(1)
 | 
			
		||||
  })
 | 
			
		||||
    fetchData(1);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  store.addMessageHandler("luma",(data) => {
 | 
			
		||||
  store.addMessageHandler("luma", (data) => {
 | 
			
		||||
    // 丢弃无关消息
 | 
			
		||||
    if (data.channel !== "luma" || data.clientId !== getClientId()) {
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (data.body === "FINISH" || data.body === "FAIL") {
 | 
			
		||||
      fetchData(1)
 | 
			
		||||
      fetchData(1);
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  store.removeMessageHandler("luma")
 | 
			
		||||
})
 | 
			
		||||
  store.removeMessageHandler("luma");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const download = (item) => {
 | 
			
		||||
  const url = replaceImg(item.video_url)
 | 
			
		||||
  const downloadURL = `${process.env.VUE_APP_API_HOST}/api/download?url=${url}`
 | 
			
		||||
  const url = replaceImg(item.video_url);
 | 
			
		||||
  const downloadURL = `${process.env.VUE_APP_API_HOST}/api/download?url=${url}`;
 | 
			
		||||
  // parse filename
 | 
			
		||||
  const urlObj = new URL(url);
 | 
			
		||||
  const fileName = urlObj.pathname.split('/').pop();
 | 
			
		||||
  item.downloading = true
 | 
			
		||||
  httpDownload(downloadURL).then(response  => {
 | 
			
		||||
  const fileName = urlObj.pathname.split("/").pop();
 | 
			
		||||
  item.downloading = true;
 | 
			
		||||
  httpDownload(downloadURL)
 | 
			
		||||
    .then((response) => {
 | 
			
		||||
      const blob = new Blob([response.data]);
 | 
			
		||||
    const link = document.createElement('a');
 | 
			
		||||
      const link = document.createElement("a");
 | 
			
		||||
      link.href = URL.createObjectURL(blob);
 | 
			
		||||
      link.download = fileName;
 | 
			
		||||
      document.body.appendChild(link);
 | 
			
		||||
      link.click();
 | 
			
		||||
      document.body.removeChild(link);
 | 
			
		||||
      URL.revokeObjectURL(link.href);
 | 
			
		||||
    item.downloading = false
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    showMessageError("下载失败")
 | 
			
		||||
    item.downloading = false
 | 
			
		||||
      item.downloading = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      showMessageError("下载失败");
 | 
			
		||||
      item.downloading = false;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const play = (item) => {
 | 
			
		||||
  currentVideoUrl.value = replaceImg(item.video_url)
 | 
			
		||||
  showDialog.value = true
 | 
			
		||||
}
 | 
			
		||||
  currentVideoUrl.value = replaceImg(item.video_url);
 | 
			
		||||
  showDialog.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeJob = (item) => {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会删除任务相关文件,继续操作码?',
 | 
			
		||||
      '删除提示',
 | 
			
		||||
      {
 | 
			
		||||
        confirmButtonText: '确认',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet("/api/video/remove", {id: item.id}).then(() => {
 | 
			
		||||
      ElMessage.success("任务删除成功")
 | 
			
		||||
      fetchData()
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("任务删除失败:" + e.message)
 | 
			
		||||
  ElMessageBox.confirm("此操作将会删除任务相关文件,继续操作码?", "删除提示", {
 | 
			
		||||
    confirmButtonText: "确认",
 | 
			
		||||
    cancelButtonText: "取消",
 | 
			
		||||
    type: "warning"
 | 
			
		||||
  })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      httpGet("/api/video/remove", { id: item.id })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          ElMessage.success("任务删除成功");
 | 
			
		||||
          fetchData();
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("任务删除失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const publishJob = (item) => {
 | 
			
		||||
  httpGet("/api/video/publish", {id: item.id, publish:item.publish}).then(() => {
 | 
			
		||||
    ElMessage.success("操作成功")
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("操作失败:" + e.message)
 | 
			
		||||
  httpGet("/api/video/publish", { id: item.id, publish: item.publish })
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("操作成功");
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("操作失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const upload = (file) => {
 | 
			
		||||
  const formData = new FormData();
 | 
			
		||||
  formData.append('file', file.file, file.name);
 | 
			
		||||
  formData.append("file", file.file, file.name);
 | 
			
		||||
  // 执行上传操作
 | 
			
		||||
  httpPost('/api/upload', formData).then((res) => {
 | 
			
		||||
    images.value.push(res.data.url)
 | 
			
		||||
    ElMessage.success({message: "上传成功", duration: 500})
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error('图片上传失败:' + e.message)
 | 
			
		||||
  httpPost("/api/upload", formData)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      images.value.push(res.data.url);
 | 
			
		||||
      ElMessage.success({ message: "上传成功", duration: 500 });
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("图片上传失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const remove = (img) => {
 | 
			
		||||
  images.value = images.value.filter(item => item !== img)
 | 
			
		||||
}
 | 
			
		||||
  images.value = images.value.filter((item) => item !== img);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const switchReverse = () => {
 | 
			
		||||
  images.value = images.value.reverse()
 | 
			
		||||
}
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const list = ref([])
 | 
			
		||||
const noData = ref(true)
 | 
			
		||||
const page = ref(1)
 | 
			
		||||
const pageSize = ref(10)
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
  images.value = images.value.reverse();
 | 
			
		||||
};
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
const list = ref([]);
 | 
			
		||||
const noData = ref(true);
 | 
			
		||||
const page = ref(1);
 | 
			
		||||
const pageSize = ref(10);
 | 
			
		||||
const total = ref(0);
 | 
			
		||||
const fetchData = (_page) => {
 | 
			
		||||
  if (_page) {
 | 
			
		||||
    page.value = _page
 | 
			
		||||
    page.value = _page;
 | 
			
		||||
  }
 | 
			
		||||
  httpGet("/api/video/list",{page:page.value, page_size:pageSize.value, type: 'luma'}).then(res => {
 | 
			
		||||
    total.value = res.data.total
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    list.value = res.data.items
 | 
			
		||||
    noData.value = list.value.length === 0
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    noData.value = true
 | 
			
		||||
  httpGet("/api/video/list", {
 | 
			
		||||
    page: page.value,
 | 
			
		||||
    page_size: pageSize.value,
 | 
			
		||||
    type: "luma"
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      total.value = res.data.total;
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
      list.value = res.data.items;
 | 
			
		||||
      noData.value = list.value.length === 0;
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
      noData.value = true;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 创建视频
 | 
			
		||||
const create = () => {
 | 
			
		||||
 | 
			
		||||
  const len = images.value.length;
 | 
			
		||||
  if(len){
 | 
			
		||||
    formData.first_frame_img = images.value[0]
 | 
			
		||||
    if(len === 2){
 | 
			
		||||
      formData.end_frame_img = images.value[1]
 | 
			
		||||
  if (len) {
 | 
			
		||||
    formData.first_frame_img = images.value[0];
 | 
			
		||||
    if (len === 2) {
 | 
			
		||||
      formData.end_frame_img = images.value[1];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  httpPost("/api/video/luma/create", formData).then(() => {
 | 
			
		||||
    fetchData(1)
 | 
			
		||||
    showMessageOK("创建任务成功")
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("创建任务失败:"+e.message)
 | 
			
		||||
  httpPost("/api/video/luma/create", formData)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      fetchData(1);
 | 
			
		||||
      showMessageOK("创建任务成功");
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      showMessageError("创建任务失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isGenerating = ref(false)
 | 
			
		||||
const isGenerating = ref(false);
 | 
			
		||||
const generatePrompt = () => {
 | 
			
		||||
  if (formData.prompt === "") {
 | 
			
		||||
    return showMessageError("请输入原始提示词")
 | 
			
		||||
    return showMessageError("请输入原始提示词");
 | 
			
		||||
  }
 | 
			
		||||
  isGenerating.value = true
 | 
			
		||||
  httpPost("/api/prompt/image", {prompt: formData.prompt}).then(res => {
 | 
			
		||||
    formData.prompt = res.data
 | 
			
		||||
    isGenerating.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("生成提示词失败:"+e.message)
 | 
			
		||||
    isGenerating.value = false
 | 
			
		||||
  isGenerating.value = true;
 | 
			
		||||
  httpPost("/api/prompt/image", { prompt: formData.prompt })
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      formData.prompt = res.data;
 | 
			
		||||
      isGenerating.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      showMessageError("生成提示词失败:" + e.message);
 | 
			
		||||
      isGenerating.value = false;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,7 @@
 | 
			
		||||
 | 
			
		||||
          <div class="mark-map-params">
 | 
			
		||||
            <el-form label-width="80px" label-position="left">
 | 
			
		||||
              <div class="param-line">
 | 
			
		||||
                你的需求?
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="param-line">你的需求?</div>
 | 
			
		||||
              <div class="param-line">
 | 
			
		||||
                <el-input
 | 
			
		||||
                  v-model="prompt"
 | 
			
		||||
@@ -19,11 +17,13 @@
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="param-line">请选择生成思维导图的AI模型</div>
 | 
			
		||||
              <div class="param-line">
 | 
			
		||||
                请选择生成思维导图的AI模型
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="param-line">
 | 
			
		||||
                <el-select v-model="modelID" placeholder="请选择模型" style="width:100%">
 | 
			
		||||
                <el-select
 | 
			
		||||
                  v-model="modelID"
 | 
			
		||||
                  placeholder="请选择模型"
 | 
			
		||||
                  style="width: 100%"
 | 
			
		||||
                >
 | 
			
		||||
                  <el-option
 | 
			
		||||
                    v-for="item in models"
 | 
			
		||||
                    :key="item.id"
 | 
			
		||||
@@ -31,27 +31,38 @@
 | 
			
		||||
                    :value="item.id"
 | 
			
		||||
                  >
 | 
			
		||||
                    <span>{{ item.name }}</span>
 | 
			
		||||
                    <el-tag style="margin-left: 5px; position: relative; top:-2px" type="info" size="small">{{
 | 
			
		||||
                        item.power
 | 
			
		||||
                      }}算力
 | 
			
		||||
                    <el-tag
 | 
			
		||||
                      style="margin-left: 5px; position: relative; top: -2px"
 | 
			
		||||
                      type="info"
 | 
			
		||||
                      size="small"
 | 
			
		||||
                      >{{ item.power }}算力
 | 
			
		||||
                    </el-tag>
 | 
			
		||||
                  </el-option>
 | 
			
		||||
                </el-select>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="text-info">
 | 
			
		||||
                <el-tag type="success">当前可用算力:{{ loginUser.power }}</el-tag>
 | 
			
		||||
                <el-text type="primary"
 | 
			
		||||
                  >当前可用算力:<el-text type="warning">{{
 | 
			
		||||
                    loginUser.power
 | 
			
		||||
                  }}</el-text></el-text
 | 
			
		||||
                >
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="param-line">
 | 
			
		||||
                <el-button color="#47fff1" :dark="false" round @click="generateAI" :loading="loading">
 | 
			
		||||
              <div class="submit-btn flex-center">
 | 
			
		||||
                <el-button
 | 
			
		||||
                  style="width: 200px"
 | 
			
		||||
                  type="primary"
 | 
			
		||||
                  :dark="false"
 | 
			
		||||
                  round
 | 
			
		||||
                  @click="generateAI"
 | 
			
		||||
                  :loading="loading"
 | 
			
		||||
                >
 | 
			
		||||
                  生成思维导图
 | 
			
		||||
                </el-button>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="param-line">
 | 
			
		||||
                使用已有内容生成?
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="param-line">使用已有内容生成?</div>
 | 
			
		||||
              <div class="param-line">
 | 
			
		||||
                <el-input
 | 
			
		||||
                  v-model="content"
 | 
			
		||||
@@ -61,10 +72,16 @@
 | 
			
		||||
                />
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="param-line">
 | 
			
		||||
                <el-button color="#C5F9AE" :dark="false" round @click="generate">直接生成(免费)</el-button>
 | 
			
		||||
              <div class="param-line flex-center">
 | 
			
		||||
                <el-button
 | 
			
		||||
                  color="rgb(78, 51, 254)"
 | 
			
		||||
                  style="width: 200px"
 | 
			
		||||
                  :dark="false"
 | 
			
		||||
                  round
 | 
			
		||||
                  @click="generate"
 | 
			
		||||
                  >直接生成(免费)</el-button
 | 
			
		||||
                >
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
            </el-form>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
@@ -73,188 +90,196 @@
 | 
			
		||||
          <div class="top-bar">
 | 
			
		||||
            <el-button @click="downloadImage" type="primary">
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <Download/>
 | 
			
		||||
                <Download />
 | 
			
		||||
              </el-icon>
 | 
			
		||||
              <span>下载图片</span>
 | 
			
		||||
            </el-button>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="body" id="markmap">
 | 
			
		||||
            <svg ref="svgRef" :style="{ height: rightBoxHeight + 'px' }"/>
 | 
			
		||||
            <svg ref="svgRef" :style="{ height: rightBoxHeight + 'px' }" />
 | 
			
		||||
            <div id="toolbar"></div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div><!-- end task list box -->
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <!-- end task list box -->
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {nextTick, ref} from 'vue';
 | 
			
		||||
import {Markmap} from 'markmap-view';
 | 
			
		||||
import {Transformer} from 'markmap-lib';
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {Download} from "@element-plus/icons-vue";
 | 
			
		||||
import {Toolbar} from 'markmap-toolbar';
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { nextTick, ref } from "vue";
 | 
			
		||||
import { Markmap } from "markmap-view";
 | 
			
		||||
import { Transformer } from "markmap-lib";
 | 
			
		||||
import { checkSession, getSystemInfo } from "@/store/cache";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { Download } from "@element-plus/icons-vue";
 | 
			
		||||
import { Toolbar } from "markmap-toolbar";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
const leftBoxHeight = ref(window.innerHeight - 105)
 | 
			
		||||
const rightBoxHeight = ref(window.innerHeight - 115)
 | 
			
		||||
const leftBoxHeight = ref(window.innerHeight - 105);
 | 
			
		||||
const rightBoxHeight = ref(window.innerHeight - 115);
 | 
			
		||||
 | 
			
		||||
const prompt = ref("")
 | 
			
		||||
const text = ref("")
 | 
			
		||||
const content = ref(text.value)
 | 
			
		||||
const html = ref("")
 | 
			
		||||
const prompt = ref("");
 | 
			
		||||
const text = ref("");
 | 
			
		||||
const content = ref(text.value);
 | 
			
		||||
const html = ref("");
 | 
			
		||||
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
const loginUser = ref({power: 0})
 | 
			
		||||
const isLogin = ref(false);
 | 
			
		||||
const loginUser = ref({ power: 0 });
 | 
			
		||||
const transformer = new Transformer();
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
 | 
			
		||||
const svgRef = ref(null)
 | 
			
		||||
const markMap = ref(null)
 | 
			
		||||
const models = ref([])
 | 
			
		||||
const modelID = ref(0)
 | 
			
		||||
const svgRef = ref(null);
 | 
			
		||||
const markMap = ref(null);
 | 
			
		||||
const models = ref([]);
 | 
			
		||||
const modelID = ref(0);
 | 
			
		||||
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  text.value = res.data['mark_map_text']
 | 
			
		||||
  content.value = text.value
 | 
			
		||||
  initData()
 | 
			
		||||
getSystemInfo()
 | 
			
		||||
  .then((res) => {
 | 
			
		||||
    text.value = res.data["mark_map_text"];
 | 
			
		||||
    content.value = text.value;
 | 
			
		||||
    initData();
 | 
			
		||||
    nextTick(() => {
 | 
			
		||||
      try {
 | 
			
		||||
      markMap.value = Markmap.create(svgRef.value)
 | 
			
		||||
      const {el} = Toolbar.create(markMap.value);
 | 
			
		||||
      document.getElementById('toolbar').append(el);
 | 
			
		||||
      update()
 | 
			
		||||
        markMap.value = Markmap.create(svgRef.value);
 | 
			
		||||
        const { el } = Toolbar.create(markMap.value);
 | 
			
		||||
        document.getElementById("toolbar").append(el);
 | 
			
		||||
        update();
 | 
			
		||||
      } catch (e) {
 | 
			
		||||
      console.error(e)
 | 
			
		||||
        console.error(e);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  })
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
  .catch((e) => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
const initData = () => {
 | 
			
		||||
  httpGet("/api/model/list").then(res => {
 | 
			
		||||
  httpGet("/api/model/list")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      for (let v of res.data) {
 | 
			
		||||
      models.value.push(v)
 | 
			
		||||
        models.value.push(v);
 | 
			
		||||
      }
 | 
			
		||||
    modelID.value = models.value[0].id
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取模型失败:" + e.message)
 | 
			
		||||
      modelID.value = models.value[0].id;
 | 
			
		||||
    })
 | 
			
		||||
  
 | 
			
		||||
  checkSession().then(user => {
 | 
			
		||||
    loginUser.value = user
 | 
			
		||||
    isLogin.value = true
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取模型失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then((user) => {
 | 
			
		||||
      loginUser.value = user;
 | 
			
		||||
      isLogin.value = true;
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const update = () => {
 | 
			
		||||
  try {
 | 
			
		||||
    const {root} = transformer.transform(processContent(text.value))
 | 
			
		||||
    markMap.value.setData(root)
 | 
			
		||||
    markMap.value.fit()
 | 
			
		||||
    const { root } = transformer.transform(processContent(text.value));
 | 
			
		||||
    markMap.value.setData(root);
 | 
			
		||||
    markMap.value.fit();
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
    console.error(e);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const processContent = (text) => {
 | 
			
		||||
  if (!text) {
 | 
			
		||||
    return text
 | 
			
		||||
    return text;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const arr = []
 | 
			
		||||
  const lines = text.split("\n")
 | 
			
		||||
  const arr = [];
 | 
			
		||||
  const lines = text.split("\n");
 | 
			
		||||
  for (let line of lines) {
 | 
			
		||||
    if (line.indexOf("```") !== -1) {
 | 
			
		||||
      continue
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
    line = line.replace(/([*_~`>])|(\d+\.)\s/g, '')
 | 
			
		||||
    arr.push(line)
 | 
			
		||||
    line = line.replace(/([*_~`>])|(\d+\.)\s/g, "");
 | 
			
		||||
    arr.push(line);
 | 
			
		||||
  }
 | 
			
		||||
  return arr.join("\n")
 | 
			
		||||
}
 | 
			
		||||
  return arr.join("\n");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.onresize = () => {
 | 
			
		||||
  leftBoxHeight.value = window.innerHeight - 145
 | 
			
		||||
  rightBoxHeight.value = window.innerHeight - 85
 | 
			
		||||
}
 | 
			
		||||
  leftBoxHeight.value = window.innerHeight - 145;
 | 
			
		||||
  rightBoxHeight.value = window.innerHeight - 85;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const generate = () => {
 | 
			
		||||
  text.value = content.value
 | 
			
		||||
  update()
 | 
			
		||||
}
 | 
			
		||||
  text.value = content.value;
 | 
			
		||||
  update();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 使用 AI 智能生成
 | 
			
		||||
const generateAI = () => {
 | 
			
		||||
  html.value = ''
 | 
			
		||||
  text.value = ''
 | 
			
		||||
  if (prompt.value === '') {
 | 
			
		||||
    return ElMessage.error("请输入你的需求")
 | 
			
		||||
  html.value = "";
 | 
			
		||||
  text.value = "";
 | 
			
		||||
  if (prompt.value === "") {
 | 
			
		||||
    return ElMessage.error("请输入你的需求");
 | 
			
		||||
  }
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    return
 | 
			
		||||
    store.setShowLoginDialog(true);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  httpPost("/api/markMap/gen", {
 | 
			
		||||
    prompt:prompt.value,
 | 
			
		||||
    prompt: prompt.value,
 | 
			
		||||
    model_id: modelID.value
 | 
			
		||||
  }).then(res => {
 | 
			
		||||
    text.value = res.data
 | 
			
		||||
    content.value = processContent(text.value)
 | 
			
		||||
    const model = getModelById(modelID.value)
 | 
			
		||||
    loginUser.value.power -= model.power
 | 
			
		||||
    nextTick(() => update())
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("生成思维导图失败:" + e.message)
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      text.value = res.data;
 | 
			
		||||
      content.value = processContent(text.value);
 | 
			
		||||
      const model = getModelById(modelID.value);
 | 
			
		||||
      loginUser.value.power -= model.power;
 | 
			
		||||
      nextTick(() => update());
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("生成思维导图失败:" + e.message);
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getModelById = (modelId) => {
 | 
			
		||||
  for (let m of models.value) {
 | 
			
		||||
    if (m.id === modelId) {
 | 
			
		||||
      return m
 | 
			
		||||
      return m;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// download SVG to png file
 | 
			
		||||
const downloadImage = () => {
 | 
			
		||||
  const svgElement = document.getElementById("markmap");
 | 
			
		||||
  // 将 SVG 渲染到图片对象
 | 
			
		||||
  const serializer = new XMLSerializer()
 | 
			
		||||
  const source = '<?xml version="1.0" standalone="no"?>\r\n' + serializer.serializeToString(svgRef.value)
 | 
			
		||||
  const image = new Image()
 | 
			
		||||
  image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source)
 | 
			
		||||
  const serializer = new XMLSerializer();
 | 
			
		||||
  const source =
 | 
			
		||||
    '<?xml version="1.0" standalone="no"?>\r\n' +
 | 
			
		||||
    serializer.serializeToString(svgRef.value);
 | 
			
		||||
  const image = new Image();
 | 
			
		||||
  image.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(source);
 | 
			
		||||
 | 
			
		||||
  // 将图片对象渲染
 | 
			
		||||
  const canvas = document.createElement('canvas')
 | 
			
		||||
  canvas.width = svgElement.offsetWidth
 | 
			
		||||
  canvas.height = svgElement.offsetHeight
 | 
			
		||||
  let context = canvas.getContext('2d')
 | 
			
		||||
  const canvas = document.createElement("canvas");
 | 
			
		||||
  canvas.width = svgElement.offsetWidth;
 | 
			
		||||
  canvas.height = svgElement.offsetHeight;
 | 
			
		||||
  let context = canvas.getContext("2d");
 | 
			
		||||
  context.clearRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
  context.fillStyle = 'white';
 | 
			
		||||
  context.fillStyle = "white";
 | 
			
		||||
  context.fillRect(0, 0, canvas.width, canvas.height);
 | 
			
		||||
 | 
			
		||||
  image.onload = function () {
 | 
			
		||||
    context.drawImage(image, 0, 0)
 | 
			
		||||
    const a = document.createElement('a')
 | 
			
		||||
    a.download = "geek-ai-xmind.png"
 | 
			
		||||
    a.href = canvas.toDataURL(`image/png`)
 | 
			
		||||
    a.click()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    context.drawImage(image, 0, 0);
 | 
			
		||||
    const a = document.createElement("a");
 | 
			
		||||
    a.download = "geek-ai-xmind.png";
 | 
			
		||||
    a.href = canvas.toDataURL(`image/png`);
 | 
			
		||||
    a.click();
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,39 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div class="member custom-scroll" v-loading="loading" element-loading-background="rgba(255,255,255,.3)" :element-loading-text="loadingText">
 | 
			
		||||
    <div
 | 
			
		||||
      class="member custom-scroll"
 | 
			
		||||
      v-loading="loading"
 | 
			
		||||
      element-loading-background="rgba(255,255,255,.3)"
 | 
			
		||||
      :element-loading-text="loadingText"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="inner">
 | 
			
		||||
        <div class="user-profile">
 | 
			
		||||
          <user-profile :key="profileKey"/>
 | 
			
		||||
          <user-profile :key="profileKey" />
 | 
			
		||||
 | 
			
		||||
          <el-row class="user-opt" :gutter="20">
 | 
			
		||||
            <el-col :span="12">
 | 
			
		||||
              <el-button type="primary" @click="showBindEmailDialog = true">绑定邮箱</el-button>
 | 
			
		||||
              <el-button type="primary" @click="showBindEmailDialog = true"
 | 
			
		||||
                >绑定邮箱</el-button
 | 
			
		||||
              >
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="12">
 | 
			
		||||
              <el-button type="primary" @click="showBindMobileDialog = true">绑定手机</el-button>
 | 
			
		||||
              <el-button type="primary" @click="showBindMobileDialog = true"
 | 
			
		||||
                >绑定手机</el-button
 | 
			
		||||
              >
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="12">
 | 
			
		||||
              <el-button type="primary" @click="showThirdLoginDialog = true">第三方登录</el-button>
 | 
			
		||||
              <el-button type="primary" @click="showThirdLoginDialog = true"
 | 
			
		||||
                >第三方登录</el-button
 | 
			
		||||
              >
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="12">
 | 
			
		||||
              <el-button type="primary" @click="showPasswordDialog = true">修改密码</el-button>
 | 
			
		||||
              <el-button type="primary" @click="showPasswordDialog = true"
 | 
			
		||||
                >修改密码</el-button
 | 
			
		||||
              >
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="24">
 | 
			
		||||
              <el-button type="primary" @click="showRedeemVerifyDialog = true">卡密兑换
 | 
			
		||||
              <el-button type="primary" @click="showRedeemVerifyDialog = true"
 | 
			
		||||
                >卡密兑换
 | 
			
		||||
              </el-button>
 | 
			
		||||
            </el-col>
 | 
			
		||||
          </el-row>
 | 
			
		||||
@@ -36,7 +50,7 @@
 | 
			
		||||
            <el-col v-for="item in list" :key="item" :span="6">
 | 
			
		||||
              <div class="product-item">
 | 
			
		||||
                <div class="image-container">
 | 
			
		||||
                  <el-image :src="vipImg" fit="cover"/>
 | 
			
		||||
                  <el-image :src="vipImg" fit="cover" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="product-title">
 | 
			
		||||
                  <span class="name">{{ item.name }}</span>
 | 
			
		||||
@@ -44,7 +58,9 @@
 | 
			
		||||
                <div class="product-info">
 | 
			
		||||
                  <div class="info-line">
 | 
			
		||||
                    <span class="label">商品原价:</span>
 | 
			
		||||
                    <span class="price"><del>¥{{ item.price }}</del></span>
 | 
			
		||||
                    <span class="price"
 | 
			
		||||
                      ><del>¥{{ item.price }}</del></span
 | 
			
		||||
                    >
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="info-line">
 | 
			
		||||
                    <span class="label">优惠价:</span>
 | 
			
		||||
@@ -52,7 +68,9 @@
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="info-line">
 | 
			
		||||
                    <span class="label">有效期:</span>
 | 
			
		||||
                    <span class="expire" v-if="item.days > 0">{{ item.days }}天</span>
 | 
			
		||||
                    <span class="expire" v-if="item.days > 0"
 | 
			
		||||
                      >{{ item.days }}天</span
 | 
			
		||||
                    >
 | 
			
		||||
                    <span class="expire" v-else>长期有效</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
@@ -62,21 +80,41 @@
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
                  <div class="pay-way">
 | 
			
		||||
 | 
			
		||||
                    <span type="primary" v-for="payWay in payWays" @click="pay(item,payWay)" :key="payWay">
 | 
			
		||||
                      <el-button v-if="payWay.pay_type==='alipay'" color="#15A6E8" circle>
 | 
			
		||||
                        <i class="iconfont icon-alipay" ></i>
 | 
			
		||||
                    <span
 | 
			
		||||
                      type="primary"
 | 
			
		||||
                      v-for="payWay in payWays"
 | 
			
		||||
                      @click="pay(item, payWay)"
 | 
			
		||||
                      :key="payWay"
 | 
			
		||||
                    >
 | 
			
		||||
                      <el-button
 | 
			
		||||
                        v-if="payWay.pay_type === 'alipay'"
 | 
			
		||||
                        color="#15A6E8"
 | 
			
		||||
                        circle
 | 
			
		||||
                      >
 | 
			
		||||
                        <i class="iconfont icon-alipay"></i>
 | 
			
		||||
                      </el-button>
 | 
			
		||||
                      <el-button v-else-if="payWay.pay_type==='qqpay'" circle>
 | 
			
		||||
                      <el-button v-else-if="payWay.pay_type === 'qqpay'" circle>
 | 
			
		||||
                        <i class="iconfont icon-qq"></i>
 | 
			
		||||
                      </el-button>
 | 
			
		||||
                      <el-button v-else-if="payWay.pay_type==='paypal'" class="paypal" round>
 | 
			
		||||
                      <el-button
 | 
			
		||||
                        v-else-if="payWay.pay_type === 'paypal'"
 | 
			
		||||
                        class="paypal"
 | 
			
		||||
                        round
 | 
			
		||||
                      >
 | 
			
		||||
                        <i class="iconfont icon-paypal"></i>
 | 
			
		||||
                      </el-button>
 | 
			
		||||
                      <el-button v-else-if="payWay.pay_type==='jdpay'" color="#E1251B" circle>
 | 
			
		||||
                      <el-button
 | 
			
		||||
                        v-else-if="payWay.pay_type === 'jdpay'"
 | 
			
		||||
                        color="#E1251B"
 | 
			
		||||
                        circle
 | 
			
		||||
                      >
 | 
			
		||||
                        <i class="iconfont icon-jd-pay"></i>
 | 
			
		||||
                      </el-button>
 | 
			
		||||
                      <el-button v-else-if="payWay.pay_type==='douyin'" class="douyin" circle>
 | 
			
		||||
                      <el-button
 | 
			
		||||
                        v-else-if="payWay.pay_type === 'douyin'"
 | 
			
		||||
                        class="douyin"
 | 
			
		||||
                        circle
 | 
			
		||||
                      >
 | 
			
		||||
                        <i class="iconfont icon-douyin"></i>
 | 
			
		||||
                      </el-button>
 | 
			
		||||
                      <el-button v-else circle class="wechat" color="#67C23A">
 | 
			
		||||
@@ -93,116 +131,155 @@
 | 
			
		||||
          <h2 class="headline">消费账单</h2>
 | 
			
		||||
 | 
			
		||||
          <div class="user-order">
 | 
			
		||||
            <user-order v-if="isLogin" :key="userOrderKey"/>
 | 
			
		||||
            <user-order v-if="isLogin" :key="userOrderKey" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <password-dialog v-if="isLogin" :show="showPasswordDialog" @hide="showPasswordDialog = false"/>
 | 
			
		||||
      <bind-mobile v-if="isLogin" :show="showBindMobileDialog" @hide="showBindMobileDialog = false"/>
 | 
			
		||||
      <bind-email v-if="isLogin" :show="showBindEmailDialog" @hide="showBindEmailDialog = false"/>
 | 
			
		||||
      <third-login v-if="isLogin" :show="showThirdLoginDialog" @hide="showThirdLoginDialog = false"/>
 | 
			
		||||
      <redeem-verify v-if="isLogin" :show="showRedeemVerifyDialog" @hide="redeemCallback"/>
 | 
			
		||||
 | 
			
		||||
      <password-dialog
 | 
			
		||||
        v-if="isLogin"
 | 
			
		||||
        :show="showPasswordDialog"
 | 
			
		||||
        @hide="showPasswordDialog = false"
 | 
			
		||||
      />
 | 
			
		||||
      <bind-mobile
 | 
			
		||||
        v-if="isLogin"
 | 
			
		||||
        :show="showBindMobileDialog"
 | 
			
		||||
        @hide="showBindMobileDialog = false"
 | 
			
		||||
      />
 | 
			
		||||
      <bind-email
 | 
			
		||||
        v-if="isLogin"
 | 
			
		||||
        :show="showBindEmailDialog"
 | 
			
		||||
        @hide="showBindEmailDialog = false"
 | 
			
		||||
      />
 | 
			
		||||
      <third-login
 | 
			
		||||
        v-if="isLogin"
 | 
			
		||||
        :show="showThirdLoginDialog"
 | 
			
		||||
        @hide="showThirdLoginDialog = false"
 | 
			
		||||
      />
 | 
			
		||||
      <redeem-verify
 | 
			
		||||
        v-if="isLogin"
 | 
			
		||||
        :show="showRedeemVerifyDialog"
 | 
			
		||||
        @hide="redeemCallback"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-dialog v-model="showDialog" :show-close=false :close-on-click-modal="false" hide-footer width="auto" class="pay-dialog">
 | 
			
		||||
    <el-dialog
 | 
			
		||||
      v-model="showDialog"
 | 
			
		||||
      :show-close="false"
 | 
			
		||||
      :close-on-click-modal="false"
 | 
			
		||||
      hide-footer
 | 
			
		||||
      width="auto"
 | 
			
		||||
      class="pay-dialog"
 | 
			
		||||
    >
 | 
			
		||||
      <div v-if="qrImg !== ''">
 | 
			
		||||
        <div class="product-info">请使用微信扫码支付:<span class="price">¥{{price}}</span></div>
 | 
			
		||||
        <div class="product-info">
 | 
			
		||||
          请使用微信扫码支付:<span class="price">¥{{ price }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-image :src="qrImg" fit="cover" />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div style="padding-bottom: 10px; text-align: center">
 | 
			
		||||
        <el-button type="success" @click="payCallback(true)">支付成功</el-button>
 | 
			
		||||
        <el-button type="danger" @click="payCallback(false)">支付失败</el-button>
 | 
			
		||||
        <el-button type="success" @click="payCallback(true)"
 | 
			
		||||
          >支付成功</el-button
 | 
			
		||||
        >
 | 
			
		||||
        <el-button type="danger" @click="payCallback(false)"
 | 
			
		||||
          >支付失败</el-button
 | 
			
		||||
        >
 | 
			
		||||
      </div>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {checkSession, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { checkSession, getSystemInfo } from "@/store/cache";
 | 
			
		||||
import UserProfile from "@/components/UserProfile.vue";
 | 
			
		||||
import PasswordDialog from "@/components/PasswordDialog.vue";
 | 
			
		||||
import BindMobile from "@/components/BindMobile.vue";
 | 
			
		||||
import RedeemVerify from "@/components/RedeemVerify.vue";
 | 
			
		||||
import UserOrder from "@/components/UserOrder.vue";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
import BindEmail from "@/components/BindEmail.vue";
 | 
			
		||||
import ThirdLogin from "@/components/ThirdLogin.vue";
 | 
			
		||||
import QRCode from "qrcode";
 | 
			
		||||
 | 
			
		||||
const list = ref([])
 | 
			
		||||
const vipImg = ref("/images/vip.png")
 | 
			
		||||
const enableReward = ref(false) // 是否启用众筹功能
 | 
			
		||||
const rewardImg = ref('/images/reward.png')
 | 
			
		||||
const showPasswordDialog = ref(false)
 | 
			
		||||
const showBindMobileDialog = ref(false)
 | 
			
		||||
const showBindEmailDialog = ref(false)
 | 
			
		||||
const showRedeemVerifyDialog = ref(false)
 | 
			
		||||
const showThirdLoginDialog = ref(false)
 | 
			
		||||
const user = ref(null)
 | 
			
		||||
const isLogin = ref(false)
 | 
			
		||||
const orderTimeout = ref(1800)
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const loadingText = ref("加载中...")
 | 
			
		||||
const orderPayInfoText = ref("")
 | 
			
		||||
 | 
			
		||||
const payWays = ref([])
 | 
			
		||||
const vipInfoText = ref("")
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
const profileKey = ref(0)
 | 
			
		||||
const userOrderKey = ref(0)
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
const qrImg = ref("")
 | 
			
		||||
const price = ref(0)
 | 
			
		||||
const list = ref([]);
 | 
			
		||||
const vipImg = ref("/images/menu/member.png");
 | 
			
		||||
const enableReward = ref(false); // 是否启用众筹功能
 | 
			
		||||
const rewardImg = ref("/images/reward.png");
 | 
			
		||||
const showPasswordDialog = ref(false);
 | 
			
		||||
const showBindMobileDialog = ref(false);
 | 
			
		||||
const showBindEmailDialog = ref(false);
 | 
			
		||||
const showRedeemVerifyDialog = ref(false);
 | 
			
		||||
const showThirdLoginDialog = ref(false);
 | 
			
		||||
const user = ref(null);
 | 
			
		||||
const isLogin = ref(false);
 | 
			
		||||
const orderTimeout = ref(1800);
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
const loadingText = ref("加载中...");
 | 
			
		||||
const orderPayInfoText = ref("");
 | 
			
		||||
 | 
			
		||||
const payWays = ref([]);
 | 
			
		||||
const vipInfoText = ref("");
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
const profileKey = ref(0);
 | 
			
		||||
const userOrderKey = ref(0);
 | 
			
		||||
const showDialog = ref(false);
 | 
			
		||||
const qrImg = ref("");
 | 
			
		||||
const price = ref(0);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkSession().then(_user => {
 | 
			
		||||
    user.value = _user
 | 
			
		||||
    isLogin.value = true
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then((_user) => {
 | 
			
		||||
      user.value = _user;
 | 
			
		||||
      isLogin.value = true;
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      store.setShowLoginDialog(true);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/product/list").then((res) => {
 | 
			
		||||
    list.value = res.data
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取产品套餐失败:" + e.message)
 | 
			
		||||
  httpGet("/api/product/list")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      list.value = res.data;
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取产品套餐失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    rewardImg.value = res.data['reward_img']
 | 
			
		||||
    enableReward.value = res.data['enabled_reward']
 | 
			
		||||
    orderPayInfoText.value = res.data['order_pay_info_text']
 | 
			
		||||
    if (res.data['order_pay_timeout'] > 0) {
 | 
			
		||||
      orderTimeout.value = res.data['order_pay_timeout']
 | 
			
		||||
  getSystemInfo()
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      rewardImg.value = res.data["reward_img"];
 | 
			
		||||
      enableReward.value = res.data["enabled_reward"];
 | 
			
		||||
      orderPayInfoText.value = res.data["order_pay_info_text"];
 | 
			
		||||
      if (res.data["order_pay_timeout"] > 0) {
 | 
			
		||||
        orderTimeout.value = res.data["order_pay_timeout"];
 | 
			
		||||
      }
 | 
			
		||||
    vipInfoText.value = res.data['vip_info_text']
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
      vipInfoText.value = res.data["vip_info_text"];
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取系统配置失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/payment/payWays").then(res => {
 | 
			
		||||
    payWays.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取支付方式失败:" + e.message)
 | 
			
		||||
  httpGet("/api/payment/payWays")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      payWays.value = res.data;
 | 
			
		||||
    })
 | 
			
		||||
})
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取支付方式失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const pay = (product, payWay) => {
 | 
			
		||||
  if (!isLogin.value) {
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    return
 | 
			
		||||
    store.setShowLoginDialog(true);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  loadingText.value = "正在生成支付订单..."
 | 
			
		||||
  let host = process.env.VUE_APP_API_HOST
 | 
			
		||||
  if (host === '') {
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  loadingText.value = "正在生成支付订单...";
 | 
			
		||||
  let host = process.env.VUE_APP_API_HOST;
 | 
			
		||||
  if (host === "") {
 | 
			
		||||
    host = `${location.protocol}//${location.host}`;
 | 
			
		||||
  }
 | 
			
		||||
  httpPost(`${process.env.VUE_APP_API_HOST}/api/payment/doPay`, {
 | 
			
		||||
@@ -212,45 +289,49 @@ const pay = (product, payWay) => {
 | 
			
		||||
    user_id: user.value.id,
 | 
			
		||||
    host: host,
 | 
			
		||||
    device: "jump"
 | 
			
		||||
  }).then(res => {
 | 
			
		||||
    showDialog.value = true
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    if (payWay.pay_way === 'wechat') {
 | 
			
		||||
      price.value = Number(product.discount)
 | 
			
		||||
      QRCode.toDataURL(res.data, {width: 300, height: 300, margin: 2}, (error, url) => {
 | 
			
		||||
  })
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      showDialog.value = true;
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
      if (payWay.pay_way === "wechat") {
 | 
			
		||||
        price.value = Number(product.discount);
 | 
			
		||||
        QRCode.toDataURL(
 | 
			
		||||
          res.data,
 | 
			
		||||
          { width: 300, height: 300, margin: 2 },
 | 
			
		||||
          (error, url) => {
 | 
			
		||||
            if (error) {
 | 
			
		||||
          console.error(error)
 | 
			
		||||
              console.error(error);
 | 
			
		||||
            } else {
 | 
			
		||||
              qrImg.value = url;
 | 
			
		||||
            }
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      window.open(res.data, '_blank');
 | 
			
		||||
          }
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      ElMessage.error("生成支付订单失败:" + e.message)
 | 
			
		||||
      loading.value = false
 | 
			
		||||
    }, 500)
 | 
			
		||||
        );
 | 
			
		||||
      } else {
 | 
			
		||||
        window.open(res.data, "_blank");
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        ElMessage.error("生成支付订单失败:" + e.message);
 | 
			
		||||
        loading.value = false;
 | 
			
		||||
      }, 500);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const redeemCallback = (success) => {
 | 
			
		||||
  showRedeemVerifyDialog.value = false
 | 
			
		||||
  showRedeemVerifyDialog.value = false;
 | 
			
		||||
  if (success) {
 | 
			
		||||
    profileKey.value += 1
 | 
			
		||||
    profileKey.value += 1;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const payCallback = (success) => {
 | 
			
		||||
  showDialog.value = false
 | 
			
		||||
  showDialog.value = false;
 | 
			
		||||
  if (success) {
 | 
			
		||||
    profileKey.value += 1
 | 
			
		||||
    userOrderKey.value += 1
 | 
			
		||||
    profileKey.value += 1;
 | 
			
		||||
    userOrderKey.value += 1;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,15 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="power-log custom-scroll" v-loading="loading">
 | 
			
		||||
    <div class="inner" :style="{height: listBoxHeight + 'px'}">
 | 
			
		||||
    <!-- :style="{ height: listBoxHeight + 'px' }" -->
 | 
			
		||||
    <div class="inner">
 | 
			
		||||
      <div class="list-box">
 | 
			
		||||
        <div class="handle-box">
 | 
			
		||||
          <el-input v-model="query.model" placeholder="模型" class="handle-input mr10" clearable></el-input>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="query.model"
 | 
			
		||||
            placeholder="模型"
 | 
			
		||||
            class="handle-input mr10"
 | 
			
		||||
            clearable
 | 
			
		||||
          ></el-input>
 | 
			
		||||
          <el-date-picker
 | 
			
		||||
            v-model="query.date"
 | 
			
		||||
            type="daterange"
 | 
			
		||||
@@ -11,117 +17,144 @@
 | 
			
		||||
            end-placeholder="结束日期"
 | 
			
		||||
            format="YYYY-MM-DD"
 | 
			
		||||
            value-format="YYYY-MM-DD"
 | 
			
		||||
              style="margin: 0 10px;width: 200px;"
 | 
			
		||||
            style="margin: 0 10px; width: 200px"
 | 
			
		||||
          />
 | 
			
		||||
          <el-button color="#21aa93" :icon="Search" @click="fetchData">搜索</el-button>
 | 
			
		||||
          <el-button type="primary" :icon="Search" @click="fetchData"
 | 
			
		||||
            >搜索</el-button
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-row v-if="items.length > 0">
 | 
			
		||||
          <el-table :data="items" :row-key="row => row.id" table-layout="auto" border
 | 
			
		||||
                    style="--el-table-border-color:#373C47;
 | 
			
		||||
                --el-table-tr-bg-color:#2D323B;
 | 
			
		||||
                --el-table-row-hover-bg-color:#373C47;
 | 
			
		||||
                --el-table-header-bg-color:#474E5C;
 | 
			
		||||
                --el-table-text-color:#d1d1d1">
 | 
			
		||||
            <el-table-column prop="username" label="用户" width="130px"/>
 | 
			
		||||
            <el-table-column prop="model" label="模型" width="130px"/>
 | 
			
		||||
          <el-table
 | 
			
		||||
            :data="items"
 | 
			
		||||
            :row-key="(row) => row.id"
 | 
			
		||||
            table-layout="auto"
 | 
			
		||||
            border
 | 
			
		||||
          >
 | 
			
		||||
            <el-table-column prop="username" label="用户" width="130px" />
 | 
			
		||||
            <el-table-column prop="model" label="模型" width="130px" />
 | 
			
		||||
            <el-table-column prop="type" label="类型">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <el-tag size="small" :type="tagColors[scope.row.type]">{{ scope.row.type_str }}</el-tag>
 | 
			
		||||
                <el-tag size="small" :type="tagColors[scope.row.type]">{{
 | 
			
		||||
                  scope.row.type_str
 | 
			
		||||
                }}</el-tag>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column label="数额">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <div>
 | 
			
		||||
                  <el-text type="success" v-if="scope.row.mark === 1">+{{ scope.row.amount }}</el-text>
 | 
			
		||||
                  <el-text type="danger" v-if="scope.row.mark === 0">-{{ scope.row.amount }}</el-text>
 | 
			
		||||
                  <el-text type="success" v-if="scope.row.mark === 1"
 | 
			
		||||
                    >+{{ scope.row.amount }}</el-text
 | 
			
		||||
                  >
 | 
			
		||||
                  <el-text type="danger" v-if="scope.row.mark === 0"
 | 
			
		||||
                    >-{{ scope.row.amount }}</el-text
 | 
			
		||||
                  >
 | 
			
		||||
                </div>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column prop="balance" label="余额"/>
 | 
			
		||||
            <el-table-column prop="balance" label="余额" />
 | 
			
		||||
            <el-table-column label="发生时间" width="160px">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
                <span>{{ dateFormat(scope.row["created_at"]) }}</span>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column prop="remark" label="备注"/>
 | 
			
		||||
            <el-table-column prop="remark" label="备注" />
 | 
			
		||||
          </el-table>
 | 
			
		||||
 | 
			
		||||
          <div class="pagination">
 | 
			
		||||
            <el-pagination v-if="total > 0" background
 | 
			
		||||
            <el-pagination
 | 
			
		||||
              v-if="total > 0"
 | 
			
		||||
              background
 | 
			
		||||
              layout="total,prev, pager, next"
 | 
			
		||||
              style="--el-pagination-button-bg-color: rgba(86, 86, 95, 0.2)"
 | 
			
		||||
              :hide-on-single-page="true"
 | 
			
		||||
              v-model:current-page="page"
 | 
			
		||||
              v-model:page-size="pageSize"
 | 
			
		||||
              @current-change="fetchData()"
 | 
			
		||||
                           :total="total"/>
 | 
			
		||||
 | 
			
		||||
              :total="total"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <el-empty :image-size="100" v-else/>
 | 
			
		||||
        <el-empty
 | 
			
		||||
          :image-size="100"
 | 
			
		||||
          v-else
 | 
			
		||||
          :image="nodata"
 | 
			
		||||
          description="暂无数据"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import {Search} from "@element-plus/icons-vue";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {checkSession} from "@/store/cache";
 | 
			
		||||
import nodata from "@/assets/img/no-data.png";
 | 
			
		||||
 | 
			
		||||
const items = ref([])
 | 
			
		||||
const total = ref(0)
 | 
			
		||||
const page = ref(1)
 | 
			
		||||
const pageSize = ref(20)
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const listBoxHeight = window.innerHeight - 87
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { dateFormat } from "@/utils/libs";
 | 
			
		||||
import { Search } from "@element-plus/icons-vue";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { httpPost } from "@/utils/http";
 | 
			
		||||
import { checkSession } from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const items = ref([]);
 | 
			
		||||
const total = ref(0);
 | 
			
		||||
const page = ref(1);
 | 
			
		||||
const pageSize = ref(20);
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
const listBoxHeight = window.innerHeight - 87;
 | 
			
		||||
const query = ref({
 | 
			
		||||
  model: "",
 | 
			
		||||
  date: []
 | 
			
		||||
})
 | 
			
		||||
const tagColors = ref(["primary", "success", "primary", "danger", "info", "warning"])
 | 
			
		||||
});
 | 
			
		||||
const tagColors = ref([
 | 
			
		||||
  "primary",
 | 
			
		||||
  "success",
 | 
			
		||||
  "primary",
 | 
			
		||||
  "danger",
 | 
			
		||||
  "info",
 | 
			
		||||
  "warning"
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
    fetchData()
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      fetchData();
 | 
			
		||||
    })
 | 
			
		||||
  const clipboard = new Clipboard('.copy-order-no');
 | 
			
		||||
  clipboard.on('success', () => {
 | 
			
		||||
    .catch(() => {});
 | 
			
		||||
  const clipboard = new Clipboard(".copy-order-no");
 | 
			
		||||
  clipboard.on("success", () => {
 | 
			
		||||
    ElMessage.success("复制成功!");
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
  clipboard.on("error", () => {
 | 
			
		||||
    ElMessage.error("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 获取数据
 | 
			
		||||
const fetchData = () => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  httpPost('/api/powerLog/list', {
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  httpPost("/api/powerLog/list", {
 | 
			
		||||
    model: query.value.model,
 | 
			
		||||
    date: query.value.date,
 | 
			
		||||
    page: page.value,
 | 
			
		||||
    page_size: pageSize.value
 | 
			
		||||
  }).then((res) => {
 | 
			
		||||
    if (res.data) {
 | 
			
		||||
      items.value = res.data.items
 | 
			
		||||
      total.value = res.data.total
 | 
			
		||||
      page.value = res.data.page
 | 
			
		||||
      pageSize.value = res.data.page_size
 | 
			
		||||
    }
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    loading.value = false
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data) {
 | 
			
		||||
        items.value = res.data.items;
 | 
			
		||||
        total.value = res.data.total;
 | 
			
		||||
        page.value = res.data.page;
 | 
			
		||||
        pageSize.value = res.data.page_size;
 | 
			
		||||
      }
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
      ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@@ -135,7 +168,10 @@ const fetchData = () => {
 | 
			
		||||
    .list-box {
 | 
			
		||||
      overflow-x hidden
 | 
			
		||||
      //overflow-y auto
 | 
			
		||||
 | 
			
		||||
      background: var(--chat-bg);
 | 
			
		||||
      padding: 20px;
 | 
			
		||||
      margin-top: 20px;
 | 
			
		||||
      border-radius: 10px;
 | 
			
		||||
      .handle-box {
 | 
			
		||||
        padding 20px 0
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,93 +1,104 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="page-song" :style="{ height: winHeight + 'px' }">
 | 
			
		||||
    <div class="inner">
 | 
			
		||||
      <h2 class="title">{{song.title}}</h2>
 | 
			
		||||
      <h2 class="title">{{ song.title }}</h2>
 | 
			
		||||
      <div class="row tags" v-if="song.tags">
 | 
			
		||||
        <span>{{song.tags}}</span>
 | 
			
		||||
        <span>{{ song.tags }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="row author">
 | 
			
		||||
        <span>
 | 
			
		||||
          <el-avatar :size="32" :src="song.user?.avatar" />
 | 
			
		||||
        </span>
 | 
			
		||||
        <span class="nickname">{{song.user?.nickname}}</span>
 | 
			
		||||
        <span class="nickname">{{ song.user?.nickname }}</span>
 | 
			
		||||
        <button class="btn btn-icon" @click="play">
 | 
			
		||||
          <i class="iconfont icon-play"></i> {{song.play_times}}
 | 
			
		||||
          <i class="iconfont icon-play"></i> {{ song.play_times }}
 | 
			
		||||
        </button>
 | 
			
		||||
 | 
			
		||||
        <el-tooltip effect="light" content="复制歌曲链接" placement="top">
 | 
			
		||||
          <button class="btn btn-icon copy-link" :data-clipboard-text="getShareURL(song)" >
 | 
			
		||||
        <el-tooltip content="复制歌曲链接" placement="top">
 | 
			
		||||
          <button
 | 
			
		||||
            class="btn btn-icon copy-link"
 | 
			
		||||
            :data-clipboard-text="getShareURL(song)"
 | 
			
		||||
          >
 | 
			
		||||
            <i class="iconfont icon-share1"></i>
 | 
			
		||||
          </button>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="row date">
 | 
			
		||||
        <span>{{dateFormat(song.created_at)}}</span>
 | 
			
		||||
        <span class="version">{{song.raw_data?.major_model_version}}</span>
 | 
			
		||||
        <span>{{ dateFormat(song.created_at) }}</span>
 | 
			
		||||
        <span class="version">{{ song.raw_data?.major_model_version }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <textarea class="prompt" maxlength="2000" rows="18" readonly>{{song.prompt}}</textarea>
 | 
			
		||||
        <textarea class="prompt" maxlength="2000" rows="18" readonly>{{
 | 
			
		||||
          song.prompt
 | 
			
		||||
        }}</textarea>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="music-player" v-if="playList.length > 0">
 | 
			
		||||
      <music-player :songs="playList" ref="playerRef" @play="song.play_times += 1"/>
 | 
			
		||||
      <music-player
 | 
			
		||||
        :songs="playList"
 | 
			
		||||
        ref="playerRef"
 | 
			
		||||
        @play="song.play_times += 1"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, onUnmounted, ref} from "vue"
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import { onMounted, onUnmounted, ref } from "vue";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { httpGet } from "@/utils/http";
 | 
			
		||||
import { showMessageError } from "@/utils/dialog";
 | 
			
		||||
import { dateFormat } from "@/utils/libs";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import MusicPlayer from "@/components/MusicPlayer.vue";
 | 
			
		||||
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const id = router.currentRoute.value.params.id
 | 
			
		||||
const song = ref({title:""})
 | 
			
		||||
const playList = ref([])
 | 
			
		||||
const playerRef = ref(null)
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const id = router.currentRoute.value.params.id;
 | 
			
		||||
const song = ref({ title: "" });
 | 
			
		||||
const playList = ref([]);
 | 
			
		||||
const playerRef = ref(null);
 | 
			
		||||
 | 
			
		||||
httpGet("/api/suno/detail",{song_id:id}).then(res => {
 | 
			
		||||
  song.value = res.data
 | 
			
		||||
  playList.value = [song.value]
 | 
			
		||||
  document.title = song.value?.title+ " | By "+song.value?.user.nickname+" | Suno音乐"
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showMessageError("获取歌曲详情失败:"+e.message)
 | 
			
		||||
})
 | 
			
		||||
httpGet("/api/suno/detail", { song_id: id })
 | 
			
		||||
  .then((res) => {
 | 
			
		||||
    song.value = res.data;
 | 
			
		||||
    playList.value = [song.value];
 | 
			
		||||
    document.title =
 | 
			
		||||
      song.value?.title + " | By " + song.value?.user.nickname + " | Suno音乐";
 | 
			
		||||
  })
 | 
			
		||||
  .catch((e) => {
 | 
			
		||||
    showMessageError("获取歌曲详情失败:" + e.message);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
const clipboard = ref(null)
 | 
			
		||||
const clipboard = ref(null);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  clipboard.value = new Clipboard('.copy-link');
 | 
			
		||||
  clipboard.value.on('success', () => {
 | 
			
		||||
  clipboard.value = new Clipboard(".copy-link");
 | 
			
		||||
  clipboard.value.on("success", () => {
 | 
			
		||||
    ElMessage.success("复制歌曲链接成功!");
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.value.on('error', () => {
 | 
			
		||||
    ElMessage.error('复制失败!');
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
  clipboard.value.on("error", () => {
 | 
			
		||||
    ElMessage.error("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onUnmounted(() => {
 | 
			
		||||
  clipboard.value.destroy()
 | 
			
		||||
})
 | 
			
		||||
  clipboard.value.destroy();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 播放歌曲
 | 
			
		||||
const play = () => {
 | 
			
		||||
  playerRef.value.play()
 | 
			
		||||
}
 | 
			
		||||
  playerRef.value.play();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const winHeight = ref(window.innerHeight-50)
 | 
			
		||||
const winHeight = ref(window.innerHeight - 50);
 | 
			
		||||
const getShareURL = (item) => {
 | 
			
		||||
  return `${location.protocol}//${location.host}/song/${item.id}`
 | 
			
		||||
}
 | 
			
		||||
  return `${location.protocol}//${location.host}/song/${item.id}`;
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,8 @@
 | 
			
		||||
          <template #default="props">
 | 
			
		||||
            <div>
 | 
			
		||||
              <el-table :data="props.row.context" :border="childBorder">
 | 
			
		||||
                <el-table-column label="对话应用" prop="role" width="120"/>
 | 
			
		||||
                <el-table-column label="对话内容" prop="content"/>
 | 
			
		||||
                <el-table-column label="对话应用" prop="role" width="120" />
 | 
			
		||||
                <el-table-column label="对话内容" prop="content" />
 | 
			
		||||
              </el-table>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
@@ -23,24 +23,39 @@
 | 
			
		||||
            </span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column label="应用类型" prop="type_name"/>
 | 
			
		||||
        <el-table-column label="应用标识" prop="key"/>
 | 
			
		||||
        <el-table-column label="绑定模型" prop="model_name"/>
 | 
			
		||||
        <el-table-column label="应用类型" prop="type_name" />
 | 
			
		||||
        <el-table-column label="应用标识" prop="key" />
 | 
			
		||||
        <el-table-column label="绑定模型" prop="model_name" />
 | 
			
		||||
        <el-table-column label="启用状态">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-switch v-model="scope.row['enable']" @change="roleSet('enable',scope.row)"/>
 | 
			
		||||
            <el-switch
 | 
			
		||||
              v-model="scope.row['enable']"
 | 
			
		||||
              @change="roleSet('enable', scope.row)"
 | 
			
		||||
            />
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column label="应用图标" prop="icon">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-image :src="scope.row.icon" style="width: 45px; height: 45px; border-radius: 50%"/>
 | 
			
		||||
            <el-image
 | 
			
		||||
              :src="scope.row.icon"
 | 
			
		||||
              style="width: 45px; height: 45px; border-radius: 50%"
 | 
			
		||||
            />
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column label="打招呼信息" prop="hello_msg"/>
 | 
			
		||||
        <el-table-column label="打招呼信息" prop="hello_msg" />
 | 
			
		||||
        <el-table-column label="操作" width="150">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-button size="small" type="primary" @click="rowEdit(scope.$index, scope.row)">编辑</el-button>
 | 
			
		||||
            <el-popconfirm title="确定要删除当前应用吗?" @confirm="removeRole(scope.row)" :width="200">
 | 
			
		||||
            <el-button
 | 
			
		||||
              size="small"
 | 
			
		||||
              type="primary"
 | 
			
		||||
              @click="rowEdit(scope.$index, scope.row)"
 | 
			
		||||
              >编辑</el-button
 | 
			
		||||
            >
 | 
			
		||||
            <el-popconfirm
 | 
			
		||||
              title="确定要删除当前应用吗?"
 | 
			
		||||
              @confirm="removeRole(scope.row)"
 | 
			
		||||
              :width="200"
 | 
			
		||||
            >
 | 
			
		||||
              <template #reference>
 | 
			
		||||
                <el-button size="small" type="danger">删除</el-button>
 | 
			
		||||
              </template>
 | 
			
		||||
@@ -56,12 +71,15 @@
 | 
			
		||||
      :close-on-click-modal="false"
 | 
			
		||||
      width="50%"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form :model="role" label-width="120px" ref="formRef" label-position="left" :rules="rules">
 | 
			
		||||
      <el-form
 | 
			
		||||
        :model="role"
 | 
			
		||||
        label-width="120px"
 | 
			
		||||
        ref="formRef"
 | 
			
		||||
        label-position="left"
 | 
			
		||||
        :rules="rules"
 | 
			
		||||
      >
 | 
			
		||||
        <el-form-item label="应用名称:" prop="name">
 | 
			
		||||
          <el-input
 | 
			
		||||
              v-model="role.name"
 | 
			
		||||
              autocomplete="off"
 | 
			
		||||
          />
 | 
			
		||||
          <el-input v-model="role.name" autocomplete="off" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="应用分类:" prop="tid">
 | 
			
		||||
          <el-select
 | 
			
		||||
@@ -80,10 +98,7 @@
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="应用标志:" prop="key">
 | 
			
		||||
          <el-input
 | 
			
		||||
              v-model="role.key"
 | 
			
		||||
              autocomplete="off"
 | 
			
		||||
          />
 | 
			
		||||
          <el-input v-model="role.key" autocomplete="off" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="应用图标:" prop="icon">
 | 
			
		||||
@@ -117,10 +132,7 @@
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="打招呼信息:" prop="hello_msg">
 | 
			
		||||
          <el-input
 | 
			
		||||
              v-model="role.hello_msg"
 | 
			
		||||
              autocomplete="off"
 | 
			
		||||
          />
 | 
			
		||||
          <el-input v-model="role.hello_msg" autocomplete="off" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="上下文信息:" prop="context">
 | 
			
		||||
@@ -143,9 +155,13 @@
 | 
			
		||||
                  <div class="context-msg-key">
 | 
			
		||||
                    <span>对话内容</span>
 | 
			
		||||
                    <span class="fr">
 | 
			
		||||
                      <el-button type="primary" @click="addContext" size="small">
 | 
			
		||||
                      <el-button
 | 
			
		||||
                        type="primary"
 | 
			
		||||
                        @click="addContext"
 | 
			
		||||
                        size="small"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-icon>
 | 
			
		||||
                        <Plus/>
 | 
			
		||||
                          <Plus />
 | 
			
		||||
                        </el-icon>
 | 
			
		||||
                        增加一行
 | 
			
		||||
                      </el-button>
 | 
			
		||||
@@ -163,15 +179,30 @@
 | 
			
		||||
                      v-loading="isGenerating"
 | 
			
		||||
                    />
 | 
			
		||||
                    <span class="remove-item">
 | 
			
		||||
                      <el-tooltip effect="dark" content="删除当前行" placement="right">
 | 
			
		||||
                      <el-tooltip
 | 
			
		||||
                        effect="dark"
 | 
			
		||||
                        content="删除当前行"
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                      >
 | 
			
		||||
                        <el-button circle type="danger" size="small">
 | 
			
		||||
                          <el-icon @click="removeContext(scope.$index)"><Delete /></el-icon>
 | 
			
		||||
                          <el-icon @click="removeContext(scope.$index)"
 | 
			
		||||
                            ><Delete
 | 
			
		||||
                          /></el-icon>
 | 
			
		||||
                        </el-button>
 | 
			
		||||
                      </el-tooltip>
 | 
			
		||||
 | 
			
		||||
                      <el-popover placement="right" :width="400" trigger="click">
 | 
			
		||||
                      <el-popover
 | 
			
		||||
                        placement="right"
 | 
			
		||||
                        :width="400"
 | 
			
		||||
                        trigger="click"
 | 
			
		||||
                      >
 | 
			
		||||
                        <template #reference>
 | 
			
		||||
                          <el-button type="primary" circle size="small" class="icon-btn">
 | 
			
		||||
                          <el-button
 | 
			
		||||
                            type="primary"
 | 
			
		||||
                            circle
 | 
			
		||||
                            size="small"
 | 
			
		||||
                            class="icon-btn"
 | 
			
		||||
                          >
 | 
			
		||||
                            <i class="iconfont icon-linggan"></i>
 | 
			
		||||
                          </el-button>
 | 
			
		||||
                        </template>
 | 
			
		||||
@@ -183,8 +214,16 @@
 | 
			
		||||
                          placeholder="请您输入要 AI实现的目标,任务或者需要AI扮演的角色?"
 | 
			
		||||
                        />
 | 
			
		||||
                        <el-row class="text-line">
 | 
			
		||||
                           <el-text class="mx-1" type="info" size="small">使用 AI 生成 System 预设指令</el-text>
 | 
			
		||||
                          <el-button class="generate-btn" size="small" @click="generatePrompt(scope.row)" color="#5865f2" :disabled="isGenerating">
 | 
			
		||||
                          <el-text type="info" size="small"
 | 
			
		||||
                            >使用 AI 生成 System 预设指令</el-text
 | 
			
		||||
                          >
 | 
			
		||||
                          <el-button
 | 
			
		||||
                            class="generate-btn"
 | 
			
		||||
                            size="small"
 | 
			
		||||
                            @click="generatePrompt(scope.row)"
 | 
			
		||||
                            color="#5865f2"
 | 
			
		||||
                            :disabled="isGenerating"
 | 
			
		||||
                          >
 | 
			
		||||
                            <i class="iconfont icon-chuangzuo"></i>
 | 
			
		||||
                            <span>立即生成</span>
 | 
			
		||||
                          </el-button>
 | 
			
		||||
@@ -199,7 +238,7 @@
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="启用状态">
 | 
			
		||||
          <el-switch v-model="role.enable"/>
 | 
			
		||||
          <el-switch v-model="role.enable" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
 | 
			
		||||
@@ -214,62 +253,67 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {Delete, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import {onMounted, reactive, ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {copyObj, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
import {Sortable} from "sortablejs"
 | 
			
		||||
import { Delete, Plus } from "@element-plus/icons-vue";
 | 
			
		||||
import { onMounted, reactive, ref } from "vue";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { copyObj, removeArrayItem } from "@/utils/libs";
 | 
			
		||||
import { Sortable } from "sortablejs";
 | 
			
		||||
import Compressor from "compressorjs";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
import { showMessageError } from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
const parentBorder = ref(true)
 | 
			
		||||
const childBorder = ref(true)
 | 
			
		||||
const tableData = ref([])
 | 
			
		||||
const sortedTableData = ref([])
 | 
			
		||||
const role = ref({context: []})
 | 
			
		||||
const formRef = ref(null)
 | 
			
		||||
const optTitle = ref("")
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const showDialog = ref(false);
 | 
			
		||||
const parentBorder = ref(true);
 | 
			
		||||
const childBorder = ref(true);
 | 
			
		||||
const tableData = ref([]);
 | 
			
		||||
const sortedTableData = ref([]);
 | 
			
		||||
const role = ref({ context: [] });
 | 
			
		||||
const formRef = ref(null);
 | 
			
		||||
const optTitle = ref("");
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
 | 
			
		||||
const rules = reactive({
 | 
			
		||||
  name: [{required: true, message: '请输入用户名', trigger: 'blur',}],
 | 
			
		||||
  key: [{required: true, message: '请输入应用标识', trigger: 'blur',}],
 | 
			
		||||
  icon: [{required: true, message: '请输入应用图标', trigger: 'blur',}],
 | 
			
		||||
  name: [{ required: true, message: "请输入用户名", trigger: "blur" }],
 | 
			
		||||
  key: [{ required: true, message: "请输入应用标识", trigger: "blur" }],
 | 
			
		||||
  icon: [{ required: true, message: "请输入应用图标", trigger: "blur" }],
 | 
			
		||||
  sort: [
 | 
			
		||||
    {required: true, message: '请输入排序数字', trigger: 'blur'},
 | 
			
		||||
    {type: 'number', message: '请输入有效数字'},
 | 
			
		||||
    { required: true, message: "请输入排序数字", trigger: "blur" },
 | 
			
		||||
    { type: "number", message: "请输入有效数字" }
 | 
			
		||||
  ],
 | 
			
		||||
  hello_msg: [{required: true, message: '请输入打招呼信息', trigger: 'change',}]
 | 
			
		||||
})
 | 
			
		||||
  hello_msg: [
 | 
			
		||||
    { required: true, message: "请输入打招呼信息", trigger: "change" }
 | 
			
		||||
  ]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const appTypes = ref([])
 | 
			
		||||
const models = ref([])
 | 
			
		||||
const messageRoles = ref(["system", "user", "assistant"])
 | 
			
		||||
const appTypes = ref([]);
 | 
			
		||||
const models = ref([]);
 | 
			
		||||
const messageRoles = ref(["system", "user", "assistant"]);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  fetchData()
 | 
			
		||||
  fetchData();
 | 
			
		||||
 | 
			
		||||
  // get chat models
 | 
			
		||||
  httpGet('/api/admin/model/list?enable=1').then((res) => {
 | 
			
		||||
    models.value = res.data
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error("获取AI模型数据失败");
 | 
			
		||||
  httpGet("/api/admin/model/list?enable=1")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      models.value = res.data;
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      ElMessage.error("获取AI模型数据失败");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  // get app type
 | 
			
		||||
  httpGet('/api/admin/app/type/list?enable=1').then((res) => {
 | 
			
		||||
    appTypes.value = res.data
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error("获取应用分类数据失败");
 | 
			
		||||
  httpGet("/api/admin/app/type/list?enable=1")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      appTypes.value = res.data;
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      ElMessage.error("获取应用分类数据失败");
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const fetchData = () => {
 | 
			
		||||
  // 获取应用列表
 | 
			
		||||
  httpGet('/api/admin/role/list').then((res) => {
 | 
			
		||||
  httpGet("/api/admin/role/list")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      // 初始化数据
 | 
			
		||||
      // const arr = res.data;
 | 
			
		||||
      // for (let i = 0; i < arr.length; i++) {
 | 
			
		||||
@@ -277,97 +321,112 @@ const fetchData = () => {
 | 
			
		||||
      //     arr[i].model_id = ''
 | 
			
		||||
      //   }
 | 
			
		||||
      // }
 | 
			
		||||
    tableData.value = res.data
 | 
			
		||||
    sortedTableData.value = copyObj(tableData.value)
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error("获取聊天应用失败");
 | 
			
		||||
      tableData.value = res.data;
 | 
			
		||||
      sortedTableData.value = copyObj(tableData.value);
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      ElMessage.error("获取聊天应用失败");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  const drawBodyWrapper = document.querySelector('.el-table__body tbody')
 | 
			
		||||
  const drawBodyWrapper = document.querySelector(".el-table__body tbody");
 | 
			
		||||
  // 初始化拖动排序插件
 | 
			
		||||
  Sortable.create(drawBodyWrapper, {
 | 
			
		||||
    sort: true,
 | 
			
		||||
    animation: 500,
 | 
			
		||||
    onEnd({newIndex, oldIndex, from}) {
 | 
			
		||||
    onEnd({ newIndex, oldIndex, from }) {
 | 
			
		||||
      if (oldIndex === newIndex) {
 | 
			
		||||
        return
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const sortedData = Array.from(from.children).map(row => row.querySelector('.sort').getAttribute('data-id'));
 | 
			
		||||
      const ids = []
 | 
			
		||||
      const sorts = []
 | 
			
		||||
      const sortedData = Array.from(from.children).map((row) =>
 | 
			
		||||
        row.querySelector(".sort").getAttribute("data-id")
 | 
			
		||||
      );
 | 
			
		||||
      const ids = [];
 | 
			
		||||
      const sorts = [];
 | 
			
		||||
      sortedData.forEach((id, index) => {
 | 
			
		||||
        ids.push(parseInt(id))
 | 
			
		||||
        sorts.push(index+1)
 | 
			
		||||
        tableData.value[index].sort_num = index + 1
 | 
			
		||||
      })
 | 
			
		||||
        ids.push(parseInt(id));
 | 
			
		||||
        sorts.push(index + 1);
 | 
			
		||||
        tableData.value[index].sort_num = index + 1;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      httpPost("/api/admin/role/sort", {ids: ids, sorts: sorts}).catch(e => {
 | 
			
		||||
        ElMessage.error("排序失败:" + e.message)
 | 
			
		||||
      })
 | 
			
		||||
      httpPost("/api/admin/role/sort", { ids: ids, sorts: sorts }).catch(
 | 
			
		||||
        (e) => {
 | 
			
		||||
          ElMessage.error("排序失败:" + e.message);
 | 
			
		||||
        }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const roleSet = (filed, row) => {
 | 
			
		||||
  httpPost('/api/admin/role/set', {id: row.id, filed: filed, value: row[filed]}).then(() => {
 | 
			
		||||
    ElMessage.success("操作成功!")
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("操作失败:" + e.message)
 | 
			
		||||
  httpPost("/api/admin/role/set", {
 | 
			
		||||
    id: row.id,
 | 
			
		||||
    filed: filed,
 | 
			
		||||
    value: row[filed]
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("操作成功!");
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("操作失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 编辑
 | 
			
		||||
const curIndex = ref(0)
 | 
			
		||||
const curIndex = ref(0);
 | 
			
		||||
const rowEdit = function (index, row) {
 | 
			
		||||
  optTitle.value = "修改应用"
 | 
			
		||||
  curIndex.value = index
 | 
			
		||||
  role.value = copyObj(row)
 | 
			
		||||
  showDialog.value = true
 | 
			
		||||
}
 | 
			
		||||
  optTitle.value = "修改应用";
 | 
			
		||||
  curIndex.value = index;
 | 
			
		||||
  role.value = copyObj(row);
 | 
			
		||||
  showDialog.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addRole = function () {
 | 
			
		||||
  optTitle.value = "添加新应用"
 | 
			
		||||
  role.value = {context: []}
 | 
			
		||||
  showDialog.value = true
 | 
			
		||||
}
 | 
			
		||||
  optTitle.value = "添加新应用";
 | 
			
		||||
  role.value = { context: [] };
 | 
			
		||||
  showDialog.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const save = function () {
 | 
			
		||||
  formRef.value.validate((valid) => {
 | 
			
		||||
    if (valid) {
 | 
			
		||||
      showDialog.value = false
 | 
			
		||||
      httpPost('/api/admin/role/save', role.value).then(() => {
 | 
			
		||||
        ElMessage.success('操作成功')
 | 
			
		||||
        fetchData()
 | 
			
		||||
      }).catch((e) => {
 | 
			
		||||
        ElMessage.error('操作失败,' + e.message)
 | 
			
		||||
      showDialog.value = false;
 | 
			
		||||
      httpPost("/api/admin/role/save", role.value)
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          ElMessage.success("操作成功");
 | 
			
		||||
          fetchData();
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("操作失败," + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeRole = function (row) {
 | 
			
		||||
  httpGet('/api/admin/role/remove?id=' + row.id).then(() => {
 | 
			
		||||
    ElMessage.success("删除成功!")
 | 
			
		||||
  httpGet("/api/admin/role/remove?id=" + row.id)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("删除成功!");
 | 
			
		||||
      tableData.value = removeArrayItem(tableData.value, row, (v1, v2) => {
 | 
			
		||||
      return v1.id === v2.id
 | 
			
		||||
        return v1.id === v2.id;
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error("删除失败!")
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      ElMessage.error("删除失败!");
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addContext = function () {
 | 
			
		||||
  if (!role.value.context) {
 | 
			
		||||
    role.value.context = []
 | 
			
		||||
    role.value.context = [];
 | 
			
		||||
  }
 | 
			
		||||
  role.value.context.push({role: '', content: ''})
 | 
			
		||||
}
 | 
			
		||||
  role.value.context.push({ role: "", content: "" });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeContext = function (index) {
 | 
			
		||||
  role.value.context.splice(index, 1);
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 图片上传
 | 
			
		||||
const uploadImg = (file) => {
 | 
			
		||||
@@ -376,36 +435,40 @@ const uploadImg = (file) => {
 | 
			
		||||
    quality: 0.6,
 | 
			
		||||
    success(result) {
 | 
			
		||||
      const formData = new FormData();
 | 
			
		||||
      formData.append('file', result, result.name);
 | 
			
		||||
      formData.append("file", result, result.name);
 | 
			
		||||
      // 执行上传操作
 | 
			
		||||
      httpPost('/api/admin/upload', formData).then((res) => {
 | 
			
		||||
        role.value.icon = res.data.url
 | 
			
		||||
        ElMessage.success('上传成功')
 | 
			
		||||
      }).catch((e) => {
 | 
			
		||||
        ElMessage.error('上传失败:' + e.message)
 | 
			
		||||
      httpPost("/api/admin/upload", formData)
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          role.value.icon = res.data.url;
 | 
			
		||||
          ElMessage.success("上传成功");
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("上传失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    },
 | 
			
		||||
    error(e) {
 | 
			
		||||
      ElMessage.error('上传失败:' + e.message)
 | 
			
		||||
    },
 | 
			
		||||
      ElMessage.error("上传失败:" + e.message);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isGenerating = ref(false)
 | 
			
		||||
const metaPrompt = ref("")
 | 
			
		||||
const isGenerating = ref(false);
 | 
			
		||||
const metaPrompt = ref("");
 | 
			
		||||
const generatePrompt = (row) => {
 | 
			
		||||
  if (metaPrompt.value === "") {
 | 
			
		||||
    return showMessageError("请输入元提示词")
 | 
			
		||||
    return showMessageError("请输入元提示词");
 | 
			
		||||
  }
 | 
			
		||||
  isGenerating.value = true
 | 
			
		||||
  httpPost("/api/prompt/meta", {prompt: metaPrompt.value}).then(res => {
 | 
			
		||||
    row.content = res.data
 | 
			
		||||
    isGenerating.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("生成失败:"+e.message)
 | 
			
		||||
    isGenerating.value = false
 | 
			
		||||
  isGenerating.value = true;
 | 
			
		||||
  httpPost("/api/prompt/meta", { prompt: metaPrompt.value })
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      row.content = res.data;
 | 
			
		||||
      isGenerating.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      showMessageError("生成失败:" + e.message);
 | 
			
		||||
      isGenerating.value = false;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,10 +3,18 @@
 | 
			
		||||
    <el-tabs v-model="activeName" @tab-change="handleChange">
 | 
			
		||||
      <el-tab-pane label="对话列表" name="chat" v-loading="data.chat.loading">
 | 
			
		||||
        <div class="handle-box">
 | 
			
		||||
          <el-input v-model.number="data.chat.query.user_id" placeholder="账户ID" class="handle-input mr10"
 | 
			
		||||
                    @keyup="searchChat($event)"></el-input>
 | 
			
		||||
          <el-input v-model="data.chat.query.title" placeholder="对话标题" class="handle-input mr10"
 | 
			
		||||
                    @keyup="searchChat($event)"></el-input>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model.number="data.chat.query.user_id"
 | 
			
		||||
            placeholder="账户ID"
 | 
			
		||||
            class="handle-input mr10"
 | 
			
		||||
            @keyup="searchChat($event)"
 | 
			
		||||
          ></el-input>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="data.chat.query.title"
 | 
			
		||||
            placeholder="对话标题"
 | 
			
		||||
            class="handle-input mr10"
 | 
			
		||||
            @keyup="searchChat($event)"
 | 
			
		||||
          ></el-input>
 | 
			
		||||
          <el-date-picker
 | 
			
		||||
            v-model="data.chat.query.created_at"
 | 
			
		||||
            type="daterange"
 | 
			
		||||
@@ -14,18 +22,29 @@
 | 
			
		||||
            end-placeholder="结束日期"
 | 
			
		||||
            format="YYYY-MM-DD"
 | 
			
		||||
            value-format="YYYY-MM-DD"
 | 
			
		||||
              style="margin-right: 10px;width: 200px; position: relative;top:3px;"
 | 
			
		||||
            style="
 | 
			
		||||
              margin-right: 10px;
 | 
			
		||||
              width: 200px;
 | 
			
		||||
              position: relative;
 | 
			
		||||
              top: 3px;
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
          <el-button type="primary" :icon="Search" @click="fetchChatData">搜索</el-button>
 | 
			
		||||
          <el-button type="primary" :icon="Search" @click="fetchChatData"
 | 
			
		||||
            >搜索</el-button
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-table :data="data.chat.items" :row-key="row => row.id" table-layout="auto">
 | 
			
		||||
            <el-table-column prop="user_id" label="账户ID"/>
 | 
			
		||||
            <el-table-column prop="username" label="账户"/>
 | 
			
		||||
          <el-table
 | 
			
		||||
            :data="data.chat.items"
 | 
			
		||||
            :row-key="(row) => row.id"
 | 
			
		||||
            table-layout="auto"
 | 
			
		||||
          >
 | 
			
		||||
            <el-table-column prop="user_id" label="账户ID" />
 | 
			
		||||
            <el-table-column prop="username" label="账户" />
 | 
			
		||||
            <el-table-column label="图标">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <el-avatar :size="30" :src="scope.row.role.icon"/>
 | 
			
		||||
                <el-avatar :size="30" :src="scope.row.role.icon" />
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column label="角色">
 | 
			
		||||
@@ -33,21 +52,29 @@
 | 
			
		||||
                <span>{{ scope.row.role.name }}</span>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column prop="model" label="模型"/>
 | 
			
		||||
            <el-table-column prop="title" label="标题"/>
 | 
			
		||||
            <el-table-column prop="msg_num" label="消息数量"/>
 | 
			
		||||
            <el-table-column prop="token" label="消耗算力"/>
 | 
			
		||||
            <el-table-column prop="model" label="模型" />
 | 
			
		||||
            <el-table-column prop="title" label="标题" />
 | 
			
		||||
            <el-table-column prop="msg_num" label="消息数量" />
 | 
			
		||||
            <el-table-column prop="token" label="消耗算力" />
 | 
			
		||||
 | 
			
		||||
            <el-table-column label="创建时间">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
                <span>{{ dateFormat(scope.row["created_at"]) }}</span>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <el-table-column label="操作" width="180">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <el-button size="small" type="primary" @click="showMessages(scope.row)">查看</el-button>
 | 
			
		||||
                <el-popconfirm title="确定要删除当前记录吗?" @confirm="removeChat(scope.row)">
 | 
			
		||||
                <el-button
 | 
			
		||||
                  size="small"
 | 
			
		||||
                  type="primary"
 | 
			
		||||
                  @click="showMessages(scope.row)"
 | 
			
		||||
                  >查看</el-button
 | 
			
		||||
                >
 | 
			
		||||
                <el-popconfirm
 | 
			
		||||
                  title="确定要删除当前记录吗?"
 | 
			
		||||
                  @confirm="removeChat(scope.row)"
 | 
			
		||||
                >
 | 
			
		||||
                  <template #reference>
 | 
			
		||||
                    <el-button size="small" type="danger">删除</el-button>
 | 
			
		||||
                  </template>
 | 
			
		||||
@@ -58,24 +85,38 @@
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
        <div class="pagination">
 | 
			
		||||
          <el-pagination v-if="data.chat.total > 0" background
 | 
			
		||||
          <el-pagination
 | 
			
		||||
            v-if="data.chat.total > 0"
 | 
			
		||||
            background
 | 
			
		||||
            layout="total,prev, pager, next"
 | 
			
		||||
            :hide-on-single-page="true"
 | 
			
		||||
            v-model:current-page="data.chat.page"
 | 
			
		||||
            v-model:page-size="data.chat.pageSize"
 | 
			
		||||
            @current-change="fetchChatData()"
 | 
			
		||||
                         :total="data.chat.total"/>
 | 
			
		||||
 | 
			
		||||
            :total="data.chat.total"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
      <el-tab-pane label="消息记录" name="message">
 | 
			
		||||
        <div class="handle-box">
 | 
			
		||||
          <el-input v-model.number="data.message.query.user_id" placeholder="账户ID" class="handle-input mr10"
 | 
			
		||||
                    @keyup="searchMessage($event)"></el-input>
 | 
			
		||||
          <el-input v-model="data.message.query.content" placeholder="消息内容" class="handle-input mr10"
 | 
			
		||||
                    @keyup="searchMessage($event)"></el-input>
 | 
			
		||||
          <el-input v-model="data.message.query.model" placeholder="模型" class="handle-input mr10"
 | 
			
		||||
                    @keyup="searchMessage($event)"></el-input>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model.number="data.message.query.user_id"
 | 
			
		||||
            placeholder="账户ID"
 | 
			
		||||
            class="handle-input mr10"
 | 
			
		||||
            @keyup="searchMessage($event)"
 | 
			
		||||
          ></el-input>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="data.message.query.content"
 | 
			
		||||
            placeholder="消息内容"
 | 
			
		||||
            class="handle-input mr10"
 | 
			
		||||
            @keyup="searchMessage($event)"
 | 
			
		||||
          ></el-input>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="data.message.query.model"
 | 
			
		||||
            placeholder="模型"
 | 
			
		||||
            class="handle-input mr10"
 | 
			
		||||
            @keyup="searchMessage($event)"
 | 
			
		||||
          ></el-input>
 | 
			
		||||
          <el-date-picker
 | 
			
		||||
            v-model="data.message.query.created_at"
 | 
			
		||||
            type="daterange"
 | 
			
		||||
@@ -83,42 +124,65 @@
 | 
			
		||||
            end-placeholder="结束日期"
 | 
			
		||||
            format="YYYY-MM-DD"
 | 
			
		||||
            value-format="YYYY-MM-DD"
 | 
			
		||||
              style="margin-right: 10px;width: 200px; position: relative;top:3px;"
 | 
			
		||||
            style="
 | 
			
		||||
              margin-right: 10px;
 | 
			
		||||
              width: 200px;
 | 
			
		||||
              position: relative;
 | 
			
		||||
              top: 3px;
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
          <el-button type="primary" :icon="Search" @click="fetchMessageData">搜索</el-button>
 | 
			
		||||
          <el-button type="primary" :icon="Search" @click="fetchMessageData"
 | 
			
		||||
            >搜索</el-button
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-table :data="data.message.items" :row-key="row => row.id" table-layout="auto">
 | 
			
		||||
            <el-table-column prop="user_id" label="账户ID"/>
 | 
			
		||||
            <el-table-column prop="username" label="账户"/>
 | 
			
		||||
          <el-table
 | 
			
		||||
            :data="data.message.items"
 | 
			
		||||
            :row-key="(row) => row.id"
 | 
			
		||||
            table-layout="auto"
 | 
			
		||||
          >
 | 
			
		||||
            <el-table-column prop="user_id" label="账户ID" />
 | 
			
		||||
            <el-table-column prop="username" label="账户" />
 | 
			
		||||
            <el-table-column label="角色">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <el-avatar :size="30" :src="scope.row.icon"/>
 | 
			
		||||
                <el-avatar :size="30" :src="scope.row.icon" />
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
            <el-table-column prop="model" label="模型"/>
 | 
			
		||||
            <el-table-column prop="model" label="模型" />
 | 
			
		||||
 | 
			
		||||
            <el-table-column label="消息内容">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <el-text style="width: 200px" truncated @click="showContent(scope.row.content)">
 | 
			
		||||
                <el-text
 | 
			
		||||
                  style="width: 200px"
 | 
			
		||||
                  truncated
 | 
			
		||||
                  @click="showContent(scope.row.content)"
 | 
			
		||||
                >
 | 
			
		||||
                  {{ scope.row.content }}
 | 
			
		||||
                </el-text>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <el-table-column prop="token" label="算力"/>
 | 
			
		||||
            <el-table-column prop="token" label="算力" />
 | 
			
		||||
 | 
			
		||||
            <el-table-column label="创建时间">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
                <span>{{ dateFormat(scope.row["created_at"]) }}</span>
 | 
			
		||||
              </template>
 | 
			
		||||
            </el-table-column>
 | 
			
		||||
 | 
			
		||||
            <el-table-column label="操作" width="180">
 | 
			
		||||
              <template #default="scope">
 | 
			
		||||
                <el-button size="small" type="primary" @click="showContent(scope.row.content)">查看</el-button>
 | 
			
		||||
                <el-popconfirm title="确定要删除当前记录吗?" @confirm="removeMessage(scope.row)">
 | 
			
		||||
                <el-button
 | 
			
		||||
                  size="small"
 | 
			
		||||
                  type="primary"
 | 
			
		||||
                  @click="showContent(scope.row.content)"
 | 
			
		||||
                  >查看</el-button
 | 
			
		||||
                >
 | 
			
		||||
                <el-popconfirm
 | 
			
		||||
                  title="确定要删除当前记录吗?"
 | 
			
		||||
                  @confirm="removeMessage(scope.row)"
 | 
			
		||||
                >
 | 
			
		||||
                  <template #reference>
 | 
			
		||||
                    <el-button size="small" type="danger">删除</el-button>
 | 
			
		||||
                  </template>
 | 
			
		||||
@@ -129,24 +193,25 @@
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
        <div class="pagination">
 | 
			
		||||
          <el-pagination v-if="data.message.total > 0" background
 | 
			
		||||
          <el-pagination
 | 
			
		||||
            v-if="data.message.total > 0"
 | 
			
		||||
            background
 | 
			
		||||
            layout="total,prev, pager, next"
 | 
			
		||||
            :hide-on-single-page="true"
 | 
			
		||||
            v-model:current-page="data.message.page"
 | 
			
		||||
            v-model:page-size="data.message.pageSize"
 | 
			
		||||
            @current-change="fetchMessageData()"
 | 
			
		||||
                         :total="data.message.total"/>
 | 
			
		||||
 | 
			
		||||
            :total="data.message.total"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
    </el-tabs>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
      v-model="showContentDialog"
 | 
			
		||||
      title="消息详情"
 | 
			
		||||
      class="chat-dialog"
 | 
			
		||||
        style="--el-dialog-width:60%"
 | 
			
		||||
      style="--el-dialog-width: 60%"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="chat-detail">
 | 
			
		||||
        <div class="chat-line" v-html="dialogContent"></div>
 | 
			
		||||
@@ -157,135 +222,144 @@
 | 
			
		||||
      v-model="showChatItemDialog"
 | 
			
		||||
      title="对话详情"
 | 
			
		||||
      class="chat-dialog"
 | 
			
		||||
        style="--el-dialog-width:60%"
 | 
			
		||||
      style="--el-dialog-width: 60%"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="chat-box chat-page">
 | 
			
		||||
        <div v-for="item in messages" :key="item.id">
 | 
			
		||||
          <chat-prompt
 | 
			
		||||
              v-if="item.type==='prompt'"
 | 
			
		||||
              :data="item"/>
 | 
			
		||||
          <chat-reply v-else-if="item.type==='reply'"
 | 
			
		||||
          <chat-prompt v-if="item.type === 'prompt'" :data="item" />
 | 
			
		||||
          <chat-reply
 | 
			
		||||
            v-else-if="item.type === 'reply'"
 | 
			
		||||
            :read-only="true"
 | 
			
		||||
                      :data="item"/>
 | 
			
		||||
            :data="item"
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
      </div><!-- end chat box -->
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- end chat box -->
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat, processContent} from "@/utils/libs";
 | 
			
		||||
import {Search} from "@element-plus/icons-vue";
 | 
			
		||||
import 'highlight.js/styles/a11y-dark.css'
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { dateFormat, processContent } from "@/utils/libs";
 | 
			
		||||
import { Search } from "@element-plus/icons-vue";
 | 
			
		||||
import "highlight.js/styles/a11y-dark.css";
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
import ChatPrompt from "@/components/ChatPrompt.vue";
 | 
			
		||||
import ChatReply from "@/components/ChatReply.vue";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const data = ref({
 | 
			
		||||
  "chat": {
 | 
			
		||||
  chat: {
 | 
			
		||||
    items: [],
 | 
			
		||||
    query: {title: "", created_at: [], page: 1, page_size: 15},
 | 
			
		||||
    query: { title: "", created_at: [], page: 1, page_size: 15 },
 | 
			
		||||
    total: 0,
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 15,
 | 
			
		||||
    loading: true
 | 
			
		||||
  },
 | 
			
		||||
  "message": {
 | 
			
		||||
  message: {
 | 
			
		||||
    items: [],
 | 
			
		||||
    query: {title: "", created_at: [], page: 1, page_size: 15},
 | 
			
		||||
    query: { title: "", created_at: [], page: 1, page_size: 15 },
 | 
			
		||||
    total: 0,
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 15,
 | 
			
		||||
    loading: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const activeName = ref("chat")
 | 
			
		||||
});
 | 
			
		||||
const activeName = ref("chat");
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  fetchChatData()
 | 
			
		||||
})
 | 
			
		||||
  fetchChatData();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handleChange = (tab) => {
 | 
			
		||||
  if (tab === "chat") {
 | 
			
		||||
    fetchChatData()
 | 
			
		||||
    fetchChatData();
 | 
			
		||||
  } else if (tab === "message") {
 | 
			
		||||
    fetchMessageData()
 | 
			
		||||
    fetchMessageData();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 搜索对话
 | 
			
		||||
const searchChat = (evt) => {
 | 
			
		||||
  if (evt.keyCode === 13) {
 | 
			
		||||
    fetchChatData()
 | 
			
		||||
    fetchChatData();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 搜索消息
 | 
			
		||||
const searchMessage = (evt) => {
 | 
			
		||||
  if (evt.keyCode === 13) {
 | 
			
		||||
    fetchMessageData()
 | 
			
		||||
    fetchMessageData();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 获取数据
 | 
			
		||||
const fetchChatData = () => {
 | 
			
		||||
  const d = data.value.chat
 | 
			
		||||
  d.query.page = d.page
 | 
			
		||||
  d.query.page_size = d.pageSize
 | 
			
		||||
  httpPost('/api/admin/chat/list', d.query).then((res) => {
 | 
			
		||||
  const d = data.value.chat;
 | 
			
		||||
  d.query.page = d.page;
 | 
			
		||||
  d.query.page_size = d.pageSize;
 | 
			
		||||
  httpPost("/api/admin/chat/list", d.query)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data) {
 | 
			
		||||
      d.items = res.data.items
 | 
			
		||||
      d.total = res.data.total
 | 
			
		||||
      d.page = res.data.page
 | 
			
		||||
      d.pageSize = res.data.page_size
 | 
			
		||||
        d.items = res.data.items;
 | 
			
		||||
        d.total = res.data.total;
 | 
			
		||||
        d.page = res.data.page;
 | 
			
		||||
        d.pageSize = res.data.page_size;
 | 
			
		||||
      }
 | 
			
		||||
    d.loading = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
      d.loading = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const fetchMessageData = () => {
 | 
			
		||||
  const d = data.value.message
 | 
			
		||||
  d.query.page = d.page
 | 
			
		||||
  d.query.page_size = d.pageSize
 | 
			
		||||
  httpPost('/api/admin/chat/message', d.query).then((res) => {
 | 
			
		||||
  const d = data.value.message;
 | 
			
		||||
  d.query.page = d.page;
 | 
			
		||||
  d.query.page_size = d.pageSize;
 | 
			
		||||
  httpPost("/api/admin/chat/message", d.query)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data) {
 | 
			
		||||
      d.items = res.data.items
 | 
			
		||||
      d.total = res.data.total
 | 
			
		||||
      d.page = res.data.page
 | 
			
		||||
      d.pageSize = res.data.page_size
 | 
			
		||||
        d.items = res.data.items;
 | 
			
		||||
        d.total = res.data.total;
 | 
			
		||||
        d.page = res.data.page;
 | 
			
		||||
        d.pageSize = res.data.page_size;
 | 
			
		||||
      }
 | 
			
		||||
    d.loading = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
      d.loading = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeChat = function (row) {
 | 
			
		||||
  httpGet('/api/admin/chat/remove?chat_id=' + row.chat_id).then(() => {
 | 
			
		||||
    ElMessage.success("删除成功!")
 | 
			
		||||
    fetchChatData()
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error("删除失败:" + e.message)
 | 
			
		||||
  httpGet("/api/admin/chat/remove?chat_id=" + row.chat_id)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("删除成功!");
 | 
			
		||||
      fetchChatData();
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("删除失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeMessage = function (row) {
 | 
			
		||||
  httpGet('/api/admin/chat/message/remove?id=' + row.id).then(() => {
 | 
			
		||||
    ElMessage.success("删除成功!")
 | 
			
		||||
    fetchMessageData()
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error("删除失败:" + e.message)
 | 
			
		||||
  httpGet("/api/admin/chat/message/remove?id=" + row.id)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("删除成功!");
 | 
			
		||||
      fetchMessageData();
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("删除失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const mathjaxPlugin = require('markdown-it-mathjax3')
 | 
			
		||||
const md = require('markdown-it')({
 | 
			
		||||
const mathjaxPlugin = require("markdown-it-mathjax3");
 | 
			
		||||
const md = require("markdown-it")({
 | 
			
		||||
  breaks: true,
 | 
			
		||||
  html: true,
 | 
			
		||||
  linkify: true,
 | 
			
		||||
@@ -293,41 +367,43 @@ const md = require('markdown-it')({
 | 
			
		||||
  highlight: function (str, lang) {
 | 
			
		||||
    if (lang && hl.getLanguage(lang)) {
 | 
			
		||||
      // 处理代码高亮
 | 
			
		||||
      const preCode = hl.highlight(lang, str, true).value
 | 
			
		||||
      const preCode = hl.highlight(lang, str, true).value;
 | 
			
		||||
      // 将代码包裹在 pre 中
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 处理代码高亮
 | 
			
		||||
    const preCode = md.utils.escapeHtml(str)
 | 
			
		||||
    const preCode = md.utils.escapeHtml(str);
 | 
			
		||||
    // 将代码包裹在 pre 中
 | 
			
		||||
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`
 | 
			
		||||
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code></pre>`;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
md.use(mathjaxPlugin)
 | 
			
		||||
md.use(mathjaxPlugin);
 | 
			
		||||
 | 
			
		||||
const showContentDialog = ref(false)
 | 
			
		||||
const dialogContent = ref("")
 | 
			
		||||
const showContentDialog = ref(false);
 | 
			
		||||
const dialogContent = ref("");
 | 
			
		||||
const showContent = (content) => {
 | 
			
		||||
  showContentDialog.value = true
 | 
			
		||||
  dialogContent.value = md.render(processContent(content))
 | 
			
		||||
}
 | 
			
		||||
  showContentDialog.value = true;
 | 
			
		||||
  dialogContent.value = md.render(processContent(content));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const showChatItemDialog = ref(false)
 | 
			
		||||
const messages = ref([])
 | 
			
		||||
const showChatItemDialog = ref(false);
 | 
			
		||||
const messages = ref([]);
 | 
			
		||||
const showMessages = (row) => {
 | 
			
		||||
  showChatItemDialog.value = true
 | 
			
		||||
  messages.value = []
 | 
			
		||||
  httpGet('/api/admin/chat/history?chat_id=' + row.chat_id).then(res => {
 | 
			
		||||
    const data = res.data
 | 
			
		||||
  showChatItemDialog.value = true;
 | 
			
		||||
  messages.value = [];
 | 
			
		||||
  httpGet("/api/admin/chat/history?chat_id=" + row.chat_id)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      const data = res.data;
 | 
			
		||||
      for (let i = 0; i < data.length; i++) {
 | 
			
		||||
        messages.value.push(data[i]);
 | 
			
		||||
      }
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    // TODO: 显示重新加载按钮
 | 
			
		||||
    ElMessage.error('加载聊天记录失败:' + e.message);
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      // TODO: 显示重新加载按钮
 | 
			
		||||
      ElMessage.error("加载聊天记录失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -93,6 +93,7 @@
 | 
			
		||||
            <el-pagination v-if="data.mj.total > 0" background
 | 
			
		||||
                           layout="total,prev, pager, next"
 | 
			
		||||
                           :hide-on-single-page="true"
 | 
			
		||||
                           
 | 
			
		||||
                           v-model:current-page="data.mj.page"
 | 
			
		||||
                           v-model:page-size="data.mj.pageSize"
 | 
			
		||||
                           @current-change="fetchMjData()"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +1,47 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container user-list" v-loading="loading">
 | 
			
		||||
    <div class="handle-box">
 | 
			
		||||
      <el-input v-model="query.username" placeholder="账号" class="handle-input mr10"></el-input>
 | 
			
		||||
      <el-button type="primary" :icon="Search" @click="handleSearch">搜索</el-button>
 | 
			
		||||
      <el-button type="success" :icon="Plus" @click="addUser">新增用户</el-button>
 | 
			
		||||
      <el-button type="danger" :icon="Delete" @click="multipleDelete">删除</el-button>
 | 
			
		||||
      <el-input
 | 
			
		||||
        v-model="query.username"
 | 
			
		||||
        placeholder="账号"
 | 
			
		||||
        class="handle-input mr10"
 | 
			
		||||
      ></el-input>
 | 
			
		||||
      <el-button type="primary" :icon="Search" @click="handleSearch"
 | 
			
		||||
        >搜索</el-button
 | 
			
		||||
      >
 | 
			
		||||
      <el-button type="success" :icon="Plus" @click="addUser"
 | 
			
		||||
        >新增用户</el-button
 | 
			
		||||
      >
 | 
			
		||||
      <el-button type="danger" :icon="Delete" @click="multipleDelete"
 | 
			
		||||
        >删除</el-button
 | 
			
		||||
      >
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <el-table :data="users.items" border class="table" :row-key="row => row.id"
 | 
			
		||||
                @selection-change="handleSelectionChange" table-layout="auto">
 | 
			
		||||
      <el-table
 | 
			
		||||
        :data="users.items"
 | 
			
		||||
        border
 | 
			
		||||
        class="table"
 | 
			
		||||
        :row-key="(row) => row.id"
 | 
			
		||||
        @selection-change="handleSelectionChange"
 | 
			
		||||
        table-layout="auto"
 | 
			
		||||
      >
 | 
			
		||||
        <el-table-column type="selection" width="38"></el-table-column>
 | 
			
		||||
        <el-table-column prop="id" label="ID"/>
 | 
			
		||||
        <el-table-column prop="id" label="ID" />
 | 
			
		||||
        <el-table-column label="账号">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ scope.row.username }}</span>
 | 
			
		||||
            <el-image v-if="scope.row.vip" :src="vipImg" style="height: 20px;position: relative; top:5px; left: 5px"/>
 | 
			
		||||
            <el-image
 | 
			
		||||
              v-if="scope.row.vip"
 | 
			
		||||
              :src="vipImg"
 | 
			
		||||
              style="height: 20px; position: relative; top: 5px; left: 5px"
 | 
			
		||||
            />
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column prop="mobile" label="手机"/>
 | 
			
		||||
        <el-table-column prop="email" label="邮箱"/>
 | 
			
		||||
        <el-table-column prop="nickname" label="昵称"/>
 | 
			
		||||
        <el-table-column prop="power" label="剩余算力"/>
 | 
			
		||||
        <el-table-column prop="mobile" label="手机" />
 | 
			
		||||
        <el-table-column prop="email" label="邮箱" />
 | 
			
		||||
        <el-table-column prop="nickname" label="昵称" />
 | 
			
		||||
        <el-table-column prop="power" label="剩余算力" />
 | 
			
		||||
        <el-table-column label="状态" width="80">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-tag v-if="scope.row.status" type="success">正常</el-tag>
 | 
			
		||||
@@ -30,30 +50,48 @@
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
        <el-table-column label="过期时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span v-if="scope.row['expired_time']">{{ scope.row['expired_time'] }}</span>
 | 
			
		||||
            <span v-if="scope.row['expired_time']">{{
 | 
			
		||||
              scope.row["expired_time"]
 | 
			
		||||
            }}</span>
 | 
			
		||||
            <el-tag v-else>长期有效</el-tag>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column label="注册时间">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
            <span>{{ dateFormat(scope.row["created_at"]) }}</span>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
 | 
			
		||||
        <el-table-column fixed="right" label="操作" width="200">
 | 
			
		||||
          <template #default="scope">
 | 
			
		||||
            <el-button-group class="ml-4">
 | 
			
		||||
              <el-button size="small" type="primary" @click="userEdit(scope.row)">编辑</el-button>
 | 
			
		||||
              <el-button size="small" type="danger" @click="removeUser(scope.row)">删除</el-button>
 | 
			
		||||
              <el-button size="small" type="success" @click="resetPass(scope.row)">重置密码</el-button>
 | 
			
		||||
              <el-button
 | 
			
		||||
                size="small"
 | 
			
		||||
                type="primary"
 | 
			
		||||
                @click="userEdit(scope.row)"
 | 
			
		||||
                >编辑</el-button
 | 
			
		||||
              >
 | 
			
		||||
              <el-button
 | 
			
		||||
                size="small"
 | 
			
		||||
                type="danger"
 | 
			
		||||
                @click="removeUser(scope.row)"
 | 
			
		||||
                >删除</el-button
 | 
			
		||||
              >
 | 
			
		||||
              <el-button
 | 
			
		||||
                size="small"
 | 
			
		||||
                type="success"
 | 
			
		||||
                @click="resetPass(scope.row)"
 | 
			
		||||
                >重置密码</el-button
 | 
			
		||||
              >
 | 
			
		||||
            </el-button-group>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-table-column>
 | 
			
		||||
      </el-table>
 | 
			
		||||
 | 
			
		||||
      <div class="pagination">
 | 
			
		||||
        <el-pagination v-if="users.total > 0"
 | 
			
		||||
        <el-pagination
 | 
			
		||||
          v-if="users.total > 0"
 | 
			
		||||
          background
 | 
			
		||||
          layout="total, prev, pager, next"
 | 
			
		||||
          v-model:current-page="users.page"
 | 
			
		||||
@@ -61,7 +99,6 @@
 | 
			
		||||
          :total="users.total"
 | 
			
		||||
          @current-change="fetchUserList(users.page, users.page_size)"
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
      </div>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
@@ -71,21 +108,34 @@
 | 
			
		||||
      :close-on-click-modal="false"
 | 
			
		||||
      width="50%"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form :model="user" label-width="100px" ref="userEditFormRef" :rules="rules">
 | 
			
		||||
      <el-form
 | 
			
		||||
        :model="user"
 | 
			
		||||
        label-width="100px"
 | 
			
		||||
        ref="userEditFormRef"
 | 
			
		||||
        :rules="rules"
 | 
			
		||||
      >
 | 
			
		||||
        <el-form-item label="账号:" prop="username">
 | 
			
		||||
          <el-input v-model="user.username" autocomplete="off"/>
 | 
			
		||||
          <el-input v-model="user.username" autocomplete="off" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="手机:" prop="mobile">
 | 
			
		||||
          <el-input v-model="user.mobile" autocomplete="off"/>
 | 
			
		||||
          <el-input v-model="user.mobile" autocomplete="off" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="邮箱:" prop="email">
 | 
			
		||||
          <el-input v-model="user.email" autocomplete="off"/>
 | 
			
		||||
          <el-input v-model="user.email" autocomplete="off" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item v-if="add" label="密码:" prop="password">
 | 
			
		||||
          <el-input v-model="user.password" autocomplete="off" placeholder="8-16位"/>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="user.password"
 | 
			
		||||
            autocomplete="off"
 | 
			
		||||
            placeholder="8-16位"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="剩余算力:" prop="power">
 | 
			
		||||
          <el-input v-model.number="user.power" autocomplete="off" placeholder="0"/>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model.number="user.power"
 | 
			
		||||
            autocomplete="off"
 | 
			
		||||
            placeholder="0"
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="有效期:" prop="expired_time">
 | 
			
		||||
@@ -131,13 +181,12 @@
 | 
			
		||||
          </el-select>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="启用状态">
 | 
			
		||||
          <el-switch v-model="user.status"/>
 | 
			
		||||
          <el-switch v-model="user.status" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="开通VIP">
 | 
			
		||||
          <el-switch v-model="user.vip"/>
 | 
			
		||||
          <el-switch v-model="user.vip" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
 | 
			
		||||
@@ -149,18 +198,19 @@
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="showResetPassDialog"
 | 
			
		||||
        title="重置密码"
 | 
			
		||||
        width="50%"
 | 
			
		||||
    >
 | 
			
		||||
    <el-dialog v-model="showResetPassDialog" title="重置密码" width="50%">
 | 
			
		||||
      <el-form label-width="100px" ref="userEditFormRef">
 | 
			
		||||
        <el-form-item label="账户:">
 | 
			
		||||
          <el-input v-model="pass.username" autocomplete="off" readonly disabled/>
 | 
			
		||||
          <el-input
 | 
			
		||||
            v-model="pass.username"
 | 
			
		||||
            autocomplete="off"
 | 
			
		||||
            readonly
 | 
			
		||||
            disabled
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 | 
			
		||||
        <el-form-item label="新密码:">
 | 
			
		||||
          <el-input v-model="pass.password" autocomplete="off"/>
 | 
			
		||||
          <el-input v-model="pass.password" autocomplete="off" />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
 | 
			
		||||
@@ -169,198 +219,215 @@
 | 
			
		||||
          <el-button type="primary" @click="doResetPass">提交</el-button>
 | 
			
		||||
        </span>
 | 
			
		||||
      </template>
 | 
			
		||||
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, reactive, ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import {dateFormat, disabledDate} from "@/utils/libs";
 | 
			
		||||
import {Delete, Plus, Search} from "@element-plus/icons-vue";
 | 
			
		||||
import { onMounted, reactive, ref } from "vue";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage, ElMessageBox } from "element-plus";
 | 
			
		||||
import { dateFormat, disabledDate } from "@/utils/libs";
 | 
			
		||||
import { Delete, Plus, Search } from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const users = ref({page: 1, page_size: 15, items: []})
 | 
			
		||||
const query = ref({username: '', page: 1, page_size: 15})
 | 
			
		||||
const users = ref({ page: 1, page_size: 15, items: [] });
 | 
			
		||||
const query = ref({ username: "", page: 1, page_size: 15 });
 | 
			
		||||
 | 
			
		||||
const title = ref('添加用户')
 | 
			
		||||
const vipImg = ref("/images/vip.png")
 | 
			
		||||
const add = ref(true)
 | 
			
		||||
const user = ref({chat_roles: [], chat_models: []})
 | 
			
		||||
const pass = ref({username: '', password: '', id: 0})
 | 
			
		||||
const roles = ref([])
 | 
			
		||||
const models = ref([])
 | 
			
		||||
const showUserEditDialog = ref(false)
 | 
			
		||||
const showResetPassDialog = ref(false)
 | 
			
		||||
const title = ref("添加用户");
 | 
			
		||||
const vipImg = ref("/images/menu/member.png");
 | 
			
		||||
const add = ref(true);
 | 
			
		||||
const user = ref({ chat_roles: [], chat_models: [] });
 | 
			
		||||
const pass = ref({ username: "", password: "", id: 0 });
 | 
			
		||||
const roles = ref([]);
 | 
			
		||||
const models = ref([]);
 | 
			
		||||
const showUserEditDialog = ref(false);
 | 
			
		||||
const showResetPassDialog = ref(false);
 | 
			
		||||
const rules = reactive({
 | 
			
		||||
  username: [{required: true, message: '请输入账号', trigger: 'blur',}],
 | 
			
		||||
  username: [{ required: true, message: "请输入账号", trigger: "blur" }],
 | 
			
		||||
  password: [
 | 
			
		||||
    {
 | 
			
		||||
      required: true,
 | 
			
		||||
      validator: (rule, value) => {
 | 
			
		||||
        return !(value.length > 16 || value.length < 8);
 | 
			
		||||
 | 
			
		||||
      }, message: '密码必须为8-16',
 | 
			
		||||
      trigger: 'blur'
 | 
			
		||||
      },
 | 
			
		||||
      message: "密码必须为8-16",
 | 
			
		||||
      trigger: "blur"
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  calls: [
 | 
			
		||||
    {required: true, message: '请输入提问次数'},
 | 
			
		||||
    {type: 'number', message: '请输入有效数字'},
 | 
			
		||||
    { required: true, message: "请输入提问次数" },
 | 
			
		||||
    { type: "number", message: "请输入有效数字" }
 | 
			
		||||
  ],
 | 
			
		||||
  chat_roles: [{required: true, message: '请选择聊天角色', trigger: 'change'}],
 | 
			
		||||
  chat_models: [{required: true, message: '请选择AI模型', trigger: 'change'}],
 | 
			
		||||
})
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
  chat_roles: [
 | 
			
		||||
    { required: true, message: "请选择聊天角色", trigger: "change" }
 | 
			
		||||
  ],
 | 
			
		||||
  chat_models: [{ required: true, message: "请选择AI模型", trigger: "change" }]
 | 
			
		||||
});
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
 | 
			
		||||
const userEditFormRef = ref(null)
 | 
			
		||||
const userEditFormRef = ref(null);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  fetchUserList(users.value.page, users.value.page_size)
 | 
			
		||||
  fetchUserList(users.value.page, users.value.page_size);
 | 
			
		||||
  // 获取角色列表
 | 
			
		||||
  httpGet('/api/admin/role/list').then((res) => {
 | 
			
		||||
  httpGet("/api/admin/role/list")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      roles.value = res.data;
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      ElMessage.error("获取聊天角色失败");
 | 
			
		||||
  })
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  httpGet('/api/admin/model/list').then(res => {
 | 
			
		||||
    models.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取模型失败:" + e.message)
 | 
			
		||||
  httpGet("/api/admin/model/list")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      models.value = res.data;
 | 
			
		||||
    })
 | 
			
		||||
})
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取模型失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const fetchUserList = function (page, pageSize) {
 | 
			
		||||
  query.value.page = page
 | 
			
		||||
  query.value.page_size = pageSize
 | 
			
		||||
  httpGet('/api/admin/user/list', query.value).then((res) => {
 | 
			
		||||
  query.value.page = page;
 | 
			
		||||
  query.value.page_size = pageSize;
 | 
			
		||||
  httpGet("/api/admin/user/list", query.value)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data) {
 | 
			
		||||
        // 初始化数据
 | 
			
		||||
        const arr = res.data.items;
 | 
			
		||||
        for (let i = 0; i < arr.length; i++) {
 | 
			
		||||
        arr[i].expired_time = dateFormat(arr[i].expired_time)
 | 
			
		||||
          arr[i].expired_time = dateFormat(arr[i].expired_time);
 | 
			
		||||
        }
 | 
			
		||||
      users.value.items = arr
 | 
			
		||||
      users.value.total = res.data.total
 | 
			
		||||
      users.value.page = res.data.page
 | 
			
		||||
      user.value.page_size = res.data.page_size
 | 
			
		||||
        users.value.items = arr;
 | 
			
		||||
        users.value.total = res.data.total;
 | 
			
		||||
        users.value.page = res.data.page;
 | 
			
		||||
        user.value.page_size = res.data.page_size;
 | 
			
		||||
      }
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error('加载用户列表失败')
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      ElMessage.error("加载用户列表失败");
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleSearch = () => {
 | 
			
		||||
  fetchUserList(users.value.page, users.value.page_size)
 | 
			
		||||
}
 | 
			
		||||
  fetchUserList(users.value.page, users.value.page_size);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 删除用户
 | 
			
		||||
const removeUser = function (user) {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会永久删除用户信息和聊天记录,确认操作吗?',
 | 
			
		||||
      '警告',
 | 
			
		||||
    "此操作将会永久删除用户信息和聊天记录,确认操作吗?",
 | 
			
		||||
    "警告",
 | 
			
		||||
    {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      confirmButtonText: "确定",
 | 
			
		||||
      cancelButtonText: "取消",
 | 
			
		||||
      type: "warning"
 | 
			
		||||
    }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    httpGet('/api/admin/user/remove', {id: user.id}).then(() => {
 | 
			
		||||
      ElMessage.success('操作成功!')
 | 
			
		||||
      fetchUserList(users.value.page, users.value.page_size)
 | 
			
		||||
    }).catch((e) => {
 | 
			
		||||
      ElMessage.error('操作失败,' + e.message)
 | 
			
		||||
  )
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      httpGet("/api/admin/user/remove", { id: user.id })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          ElMessage.success("操作成功!");
 | 
			
		||||
          fetchUserList(users.value.page, users.value.page_size);
 | 
			
		||||
        })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.info('操作被取消')
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("操作失败," + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      ElMessage.info("操作被取消");
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const userEdit = function (row) {
 | 
			
		||||
  user.value = row
 | 
			
		||||
  title.value = '编辑用户'
 | 
			
		||||
  showUserEditDialog.value = true
 | 
			
		||||
  add.value = false
 | 
			
		||||
}
 | 
			
		||||
  user.value = row;
 | 
			
		||||
  title.value = "编辑用户";
 | 
			
		||||
  showUserEditDialog.value = true;
 | 
			
		||||
  add.value = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addUser = () => {
 | 
			
		||||
  user.value = {chat_id: 0, chat_roles: [], chat_models: []}
 | 
			
		||||
  title.value = '添加用户'
 | 
			
		||||
  showUserEditDialog.value = true
 | 
			
		||||
  add.value = true
 | 
			
		||||
}
 | 
			
		||||
  user.value = { chat_id: 0, chat_roles: [], chat_models: [] };
 | 
			
		||||
  title.value = "添加用户";
 | 
			
		||||
  showUserEditDialog.value = true;
 | 
			
		||||
  add.value = true;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const saveUser = function () {
 | 
			
		||||
  userEditFormRef.value.validate((valid) => {
 | 
			
		||||
    if (valid) {
 | 
			
		||||
      showUserEditDialog.value = false
 | 
			
		||||
      console.log(user.value)
 | 
			
		||||
      httpPost('/api/admin/user/save', user.value).then((res) => {
 | 
			
		||||
        ElMessage.success('操作成功!')
 | 
			
		||||
      showUserEditDialog.value = false;
 | 
			
		||||
      console.log(user.value);
 | 
			
		||||
      httpPost("/api/admin/user/save", user.value)
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          ElMessage.success("操作成功!");
 | 
			
		||||
          if (add.value) {
 | 
			
		||||
          users.value.items.push(res.data)
 | 
			
		||||
            users.value.items.push(res.data);
 | 
			
		||||
          }
 | 
			
		||||
      }).catch((e) => {
 | 
			
		||||
        ElMessage.error('操作失败,' + e.message)
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("操作失败," + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
      return false
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const userIds = ref([])
 | 
			
		||||
const userIds = ref([]);
 | 
			
		||||
const handleSelectionChange = function (rows) {
 | 
			
		||||
  userIds.value = []
 | 
			
		||||
  userIds.value = [];
 | 
			
		||||
  rows.forEach((row) => {
 | 
			
		||||
    userIds.value.push(row.id)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    userIds.value.push(row.id);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const multipleDelete = function () {
 | 
			
		||||
  ElMessageBox.confirm(
 | 
			
		||||
      '此操作将会永久删除用户信息和聊天记录,确认操作吗?',
 | 
			
		||||
      '警告',
 | 
			
		||||
    "此操作将会永久删除用户信息和聊天记录,确认操作吗?",
 | 
			
		||||
    "警告",
 | 
			
		||||
    {
 | 
			
		||||
        confirmButtonText: '确定',
 | 
			
		||||
        cancelButtonText: '取消',
 | 
			
		||||
        type: 'warning',
 | 
			
		||||
      confirmButtonText: "确定",
 | 
			
		||||
      cancelButtonText: "取消",
 | 
			
		||||
      type: "warning"
 | 
			
		||||
    }
 | 
			
		||||
  ).then(() => {
 | 
			
		||||
    loading.value = true
 | 
			
		||||
    httpGet('/api/admin/user/remove', {ids: userIds.value}).then(() => {
 | 
			
		||||
      ElMessage.success('操作成功!')
 | 
			
		||||
      fetchUserList(users.value.page, users.value.page_size)
 | 
			
		||||
      loading.value = false
 | 
			
		||||
    }).catch((e) => {
 | 
			
		||||
      ElMessage.error('操作失败,' + e.message)
 | 
			
		||||
      loading.value = false
 | 
			
		||||
  )
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      loading.value = true;
 | 
			
		||||
      httpGet("/api/admin/user/remove", { ids: userIds.value })
 | 
			
		||||
        .then(() => {
 | 
			
		||||
          ElMessage.success("操作成功!");
 | 
			
		||||
          fetchUserList(users.value.page, users.value.page_size);
 | 
			
		||||
          loading.value = false;
 | 
			
		||||
        })
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.info('操作被取消')
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("操作失败," + e.message);
 | 
			
		||||
          loading.value = false;
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      ElMessage.info("操作被取消");
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const resetPass = (row) => {
 | 
			
		||||
  showResetPassDialog.value = true
 | 
			
		||||
  pass.value.id = row.id
 | 
			
		||||
  pass.value.username = row.username
 | 
			
		||||
}
 | 
			
		||||
  showResetPassDialog.value = true;
 | 
			
		||||
  pass.value.id = row.id;
 | 
			
		||||
  pass.value.username = row.username;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const doResetPass = () => {
 | 
			
		||||
  httpPost('/api/admin/user/resetPass', pass.value).then(() => {
 | 
			
		||||
    ElMessage.success('操作成功!')
 | 
			
		||||
    showResetPassDialog.value = false
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error('操作失败,' + e.message)
 | 
			
		||||
  httpPost("/api/admin/user/resetPass", pass.value)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("操作成功!");
 | 
			
		||||
      showResetPassDialog.value = false;
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("操作失败," + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||