merge code for v4.1.8
@@ -6,6 +6,6 @@ VUE_APP_ADMIN_USER=admin
 | 
			
		||||
VUE_APP_ADMIN_PASS=admin123
 | 
			
		||||
VUE_APP_KEY_PREFIX=GeekAI_DEV_
 | 
			
		||||
VUE_APP_TITLE="Geek-AI 创作系统"
 | 
			
		||||
VUE_APP_VERSION=v4.1.7
 | 
			
		||||
VUE_APP_VERSION=v4.1.8
 | 
			
		||||
VUE_APP_DOCS_URL=https://docs.geekai.me
 | 
			
		||||
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,6 @@ VUE_APP_API_HOST=
 | 
			
		||||
VUE_APP_WS_HOST=
 | 
			
		||||
VUE_APP_KEY_PREFIX=GeekAI_
 | 
			
		||||
VUE_APP_TITLE="Geek-AI 创作系统"
 | 
			
		||||
VUE_APP_VERSION=v4.1.7
 | 
			
		||||
VUE_APP_VERSION=v4.1.8
 | 
			
		||||
VUE_APP_DOCS_URL=https://docs.geekai.me
 | 
			
		||||
VUE_APP_GIT_URL=https://github.com/yangjian102621/geekai
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19499
									
								
								web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -8,8 +8,9 @@
 | 
			
		||||
    "lint": "vue-cli-service lint"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@element-plus/icons-vue": "^2.1.0",
 | 
			
		||||
    "@element-plus/icons-vue": "^2.3.1",
 | 
			
		||||
    "@openai/realtime-api-beta": "github:openai/openai-realtime-api-beta",
 | 
			
		||||
    "animate.css": "^4.1.1",
 | 
			
		||||
    "axios": "^0.27.2",
 | 
			
		||||
    "clipboard": "^2.0.11",
 | 
			
		||||
    "compressorjs": "^1.2.1",
 | 
			
		||||
@@ -21,6 +22,7 @@
 | 
			
		||||
    "json-bigint": "^1.0.0",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "markdown-it": "^13.0.1",
 | 
			
		||||
	"markdown-it-emoji": "^2.0.0",
 | 
			
		||||
    "markdown-it-mathjax3": "^4.3.2",
 | 
			
		||||
    "markmap-common": "^0.16.0",
 | 
			
		||||
    "markmap-lib": "^0.16.1",
 | 
			
		||||
@@ -44,10 +46,13 @@
 | 
			
		||||
    "@vue/cli-plugin-babel": "~5.0.0",
 | 
			
		||||
    "@vue/cli-plugin-eslint": "~5.0.0",
 | 
			
		||||
    "@vue/cli-service": "~5.0.0",
 | 
			
		||||
    "autoprefixer": "^10.4.20",
 | 
			
		||||
    "eslint": "^7.32.0",
 | 
			
		||||
    "eslint-plugin-vue": "^8.0.3",
 | 
			
		||||
    "postcss": "^8.4.49",
 | 
			
		||||
    "stylus": "^0.58.1",
 | 
			
		||||
    "stylus-loader": "^7.0.0",
 | 
			
		||||
    "tailwindcss": "^3.4.17",
 | 
			
		||||
    "webpack": "^5.90.3"
 | 
			
		||||
  },
 | 
			
		||||
  "eslintConfig": {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								web/postcss.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,6 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  plugins: {
 | 
			
		||||
    tailwindcss: {},
 | 
			
		||||
    autoprefixer: {},
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								web/public/images/avatar/default.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 17 KiB  | 
| 
		 Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 28 KiB  | 
| 
		 Before Width: | Height: | Size: 337 KiB After Width: | Height: | Size: 76 KiB  | 
| 
		 Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 3.4 KiB  | 
| 
		 Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 3.2 KiB  | 
| 
		 Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 3.2 KiB  | 
| 
		 Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.4 KiB  | 
| 
		 Before Width: | Height: | Size: 802 B After Width: | Height: | Size: 2.3 KiB  | 
| 
		 Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 3.6 KiB  | 
| 
		 Before Width: | Height: | Size: 939 B After Width: | Height: | Size: 3.5 KiB  | 
| 
		 Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 2.5 KiB  | 
| 
		 Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 4.8 KiB  | 
| 
		 Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 4.4 KiB  | 
| 
		 Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 4.2 KiB  | 
| 
		 Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 3.9 KiB  | 
| 
		 Before Width: | Height: | Size: 836 B After Width: | Height: | Size: 2.7 KiB  | 
| 
		 Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.0 KiB  | 
@@ -1,18 +1,19 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="zh-cn">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
			
		||||
    <meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
 | 
			
		||||
<html lang="zh-cn" data-theme="light">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 | 
			
		||||
    <meta
 | 
			
		||||
      name="viewport"
 | 
			
		||||
      content="width=device-width,initial-scale=1.0,user-scalable=no"
 | 
			
		||||
    />
 | 
			
		||||
    <title>Geek-AI 创作助手</title>
 | 
			
		||||
</head>
 | 
			
		||||
  </head>
 | 
			
		||||
 | 
			
		||||
<body>
 | 
			
		||||
<noscript>
 | 
			
		||||
    <strong>请开启JavaScript支持</strong>
 | 
			
		||||
</noscript>
 | 
			
		||||
<div id="app"></div>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
  <body>
 | 
			
		||||
    <noscript>
 | 
			
		||||
      <strong>请开启JavaScript支持</strong>
 | 
			
		||||
    </noscript>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										123
									
								
								web/src/App.vue
									
									
									
									
									
								
							
							
						
						@@ -1,29 +1,29 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-config-provider>
 | 
			
		||||
    <router-view/>
 | 
			
		||||
    <router-view />
 | 
			
		||||
  </el-config-provider>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ElConfigProvider} from 'element-plus';
 | 
			
		||||
import {onMounted, ref, watch} from "vue";
 | 
			
		||||
import {checkSession, getClientId, getSystemInfo} from "@/store/cache";
 | 
			
		||||
import {isChrome, isMobile} from "@/utils/libs";
 | 
			
		||||
import {showMessageInfo} from "@/utils/dialog";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import {getUserToken} from "@/store/session";
 | 
			
		||||
import { ElConfigProvider } from "element-plus";
 | 
			
		||||
import { onMounted, ref, watch } from "vue";
 | 
			
		||||
import { checkSession, getClientId, getSystemInfo } from "@/store/cache";
 | 
			
		||||
import { isChrome, isMobile } from "@/utils/libs";
 | 
			
		||||
import { showMessageInfo } from "@/utils/dialog";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
import { getUserToken } from "@/store/session";
 | 
			
		||||
 | 
			
		||||
const debounce = (fn, delay) => {
 | 
			
		||||
  let timer
 | 
			
		||||
  let timer;
 | 
			
		||||
  return (...args) => {
 | 
			
		||||
    if (timer) {
 | 
			
		||||
      clearTimeout(timer)
 | 
			
		||||
      clearTimeout(timer);
 | 
			
		||||
    }
 | 
			
		||||
    timer = setTimeout(() => {
 | 
			
		||||
      fn(...args)
 | 
			
		||||
    }, delay)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
      fn(...args);
 | 
			
		||||
    }, delay);
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const _ResizeObserver = window.ResizeObserver;
 | 
			
		||||
window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
 | 
			
		||||
@@ -31,63 +31,69 @@ window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
 | 
			
		||||
    callback = debounce(callback, 200);
 | 
			
		||||
    super(callback);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  // 获取系统参数
 | 
			
		||||
  getSystemInfo().then((res) => {
 | 
			
		||||
    const link = document.createElement('link')
 | 
			
		||||
    link.rel = 'shortcut icon'
 | 
			
		||||
    link.href = res.data.logo
 | 
			
		||||
    document.head.appendChild(link)
 | 
			
		||||
  })
 | 
			
		||||
    const link = document.createElement("link");
 | 
			
		||||
    link.rel = "shortcut icon";
 | 
			
		||||
    link.href = res.data.logo;
 | 
			
		||||
    document.head.appendChild(link);
 | 
			
		||||
  });
 | 
			
		||||
  if (!isChrome() && !isMobile()) {
 | 
			
		||||
    showMessageInfo("建议使用 Chrome 浏览器以获得最佳体验。")
 | 
			
		||||
    showMessageInfo("建议使用 Chrome 浏览器以获得最佳体验。");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
    store.setIsLogin(true)
 | 
			
		||||
  }).catch(()=>{})
 | 
			
		||||
})
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      store.setIsLogin(true);
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {});
 | 
			
		||||
 | 
			
		||||
watch(() => store.isLogin, (val) => {
 | 
			
		||||
  if (val) {
 | 
			
		||||
    connect()
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
  // 设置主题
 | 
			
		||||
  document.documentElement.setAttribute("data-theme", store.theme);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const handler = ref(0)
 | 
			
		||||
// 初始化 websocket 连接
 | 
			
		||||
const connect = () => {
 | 
			
		||||
  let host = process.env.VUE_APP_WS_HOST
 | 
			
		||||
  if (host === '') {
 | 
			
		||||
    if (location.protocol === 'https:') {
 | 
			
		||||
      host = 'wss://' + location.host;
 | 
			
		||||
    } else {
 | 
			
		||||
      host = 'ws://' + location.host;
 | 
			
		||||
watch(
 | 
			
		||||
  () => store.isLogin,
 | 
			
		||||
  (val) => {
 | 
			
		||||
    if (val) {
 | 
			
		||||
      connect();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const clientId = getClientId()
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/ws?client_id=${clientId}`,["token",getUserToken()]);
 | 
			
		||||
  _socket.addEventListener('open', () => {
 | 
			
		||||
    console.log('WebSocket 已连接')
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const handler = ref(0);
 | 
			
		||||
// 初始化 websocket 连接
 | 
			
		||||
const connect = () => {
 | 
			
		||||
  let host = process.env.VUE_APP_WS_HOST;
 | 
			
		||||
  if (host === "") {
 | 
			
		||||
    if (location.protocol === "https:") {
 | 
			
		||||
      host = "wss://" + location.host;
 | 
			
		||||
    } else {
 | 
			
		||||
      host = "ws://" + location.host;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const clientId = getClientId();
 | 
			
		||||
  const _socket = new WebSocket(host + `/api/ws?client_id=${clientId}`, ["token", getUserToken()]);
 | 
			
		||||
  _socket.addEventListener("open", () => {
 | 
			
		||||
    console.log("WebSocket 已连接");
 | 
			
		||||
    handler.value = setInterval(() => {
 | 
			
		||||
      if (_socket.readyState === WebSocket.OPEN) {
 | 
			
		||||
        _socket.send(JSON.stringify({"type":"ping"}))
 | 
			
		||||
        _socket.send(JSON.stringify({ type: "ping" }));
 | 
			
		||||
      }
 | 
			
		||||
    },5000)
 | 
			
		||||
  })
 | 
			
		||||
  _socket.addEventListener('close', () => {
 | 
			
		||||
    clearInterval(handler.value)
 | 
			
		||||
    connect()
 | 
			
		||||
    }, 5000);
 | 
			
		||||
  });
 | 
			
		||||
  store.setSocket(_socket)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  _socket.addEventListener("close", () => {
 | 
			
		||||
    clearInterval(handler.value);
 | 
			
		||||
    connect();
 | 
			
		||||
  });
 | 
			
		||||
  store.setSocket(_socket);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
html, body {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
@@ -102,6 +108,14 @@ html, body {
 | 
			
		||||
  text-rendering: optimizeLegibility;
 | 
			
		||||
 | 
			
		||||
  --primary-color: #21aa93
 | 
			
		||||
 | 
			
		||||
  h1 { font-size: 2em; } /* 通常是 2em */
 | 
			
		||||
  h2 { font-size: 1.5em; } /* 通常是 1.5em */
 | 
			
		||||
  h3 { font-size: 1.17em; } /* 通常是 1.17em */
 | 
			
		||||
  h4 { font-size: 1em; } /* 通常是 1em */
 | 
			
		||||
  h5 { font-size: 0.83em; } /* 通常是 0.83em */
 | 
			
		||||
  h6 { font-size: 0.67em; } /* 通常是 0.67em */
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-overlay-dialog {
 | 
			
		||||
@@ -137,4 +151,5 @@ html, body {
 | 
			
		||||
  color #07C160
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@import '@/assets/iconfont/iconfont.css'
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,74 @@
 | 
			
		||||
.el-form-item__content {
 | 
			
		||||
  .tip-input {
 | 
			
		||||
    display flex
 | 
			
		||||
    width 100%
 | 
			
		||||
.form {
 | 
			
		||||
  .el-form-item__label {
 | 
			
		||||
    .label-title {
 | 
			
		||||
      display flex
 | 
			
		||||
      align-items center
 | 
			
		||||
 | 
			
		||||
    .el-input, .el-select, .el-switch {
 | 
			
		||||
      margin-right 10px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .info {
 | 
			
		||||
      margin-top 2px
 | 
			
		||||
      .el-icon {
 | 
			
		||||
        margin-left 5px
 | 
			
		||||
        cursor pointer
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  .el-form-item__content {
 | 
			
		||||
    width 100%
 | 
			
		||||
 | 
			
		||||
    .uploader-icon {
 | 
			
		||||
      font-size 24px
 | 
			
		||||
      position relative
 | 
			
		||||
      top 3px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .tip-input-line {
 | 
			
		||||
      .tip {
 | 
			
		||||
        margin-top 10px
 | 
			
		||||
        color #c1c1c1
 | 
			
		||||
        font-size 12px;
 | 
			
		||||
        line-height 1.5;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-input {
 | 
			
		||||
    width 100%
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .text {
 | 
			
		||||
    font-size 14px
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .active-info {
 | 
			
		||||
    line-height 1.5
 | 
			
		||||
    padding 10px 0 30px 0
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-descriptions {
 | 
			
		||||
    margin-bottom 20px
 | 
			
		||||
 | 
			
		||||
    .el-icon {
 | 
			
		||||
      font-size 18px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .selected {
 | 
			
		||||
      color #0bc15f
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .closed {
 | 
			
		||||
      color #da0d54
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .text {
 | 
			
		||||
      margin-left 10px
 | 
			
		||||
      font-size 12px
 | 
			
		||||
      color #999999
 | 
			
		||||
      position: relative;
 | 
			
		||||
      top -5px
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-alert {
 | 
			
		||||
    margin-bottom 15px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
@@ -94,9 +113,10 @@
 | 
			
		||||
              -webkit-box-orient: vertical;
 | 
			
		||||
              -webkit-line-clamp: 2;
 | 
			
		||||
              word-break: break-all;
 | 
			
		||||
              height 34px
 | 
			
		||||
              height 50px
 | 
			
		||||
              font-size: .875rem;
 | 
			
		||||
              color #999999
 | 
			
		||||
              color var(--text-fb)
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,37 +1,33 @@
 | 
			
		||||
$sideBgColor = #252526;
 | 
			
		||||
$borderColor = #4676d0;
 | 
			
		||||
 | 
			
		||||
#app {
 | 
			
		||||
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
  .chat-page {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
 | 
			
		||||
    :deep (.el-message-box__message){
 | 
			
		||||
      font-size: 18px !important
 | 
			
		||||
    }
 | 
			
		||||
    .newChat{
 | 
			
		||||
      margin-bottom: 10px  
 | 
			
		||||
    }
 | 
			
		||||
    // left side
 | 
			
		||||
 | 
			
		||||
    .el-container{
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      }
 | 
			
		||||
    .el-aside {
 | 
			
		||||
      //background-color: $sideBgColor;
 | 
			
		||||
      padding 10px
 | 
			
		||||
      width var(--el-aside-width, 320px)
 | 
			
		||||
 | 
			
		||||
      .media-page {
 | 
			
		||||
        display: flex
 | 
			
		||||
        flex-flow: column
 | 
			
		||||
        //background-color: $sideBgColor
 | 
			
		||||
        border-radius 10px
 | 
			
		||||
        padding 10px 0
 | 
			
		||||
 | 
			
		||||
        .search-box {
 | 
			
		||||
          flex-wrap: wrap
 | 
			
		||||
          padding: 10px 0;
 | 
			
		||||
 | 
			
		||||
          .search-input {
 | 
			
		||||
            --el-input-bg-color: #363535
 | 
			
		||||
            --el-input-border-color: #464545
 | 
			
		||||
            --el-input-focus-border-color: #47fff1
 | 
			
		||||
            --el-input-hover-border-color: #2DA39A
 | 
			
		||||
            box-shadow: none
 | 
			
		||||
          }
 | 
			
		||||
          margin-bottom: 10px
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 隐藏滚动条
 | 
			
		||||
@@ -51,14 +47,14 @@ $borderColor = #4676d0;
 | 
			
		||||
            width: 100%
 | 
			
		||||
            justify-content: flex-start
 | 
			
		||||
            padding: 8px 12px
 | 
			
		||||
            //border-bottom: 1px solid #3c3c3c
 | 
			
		||||
            //border: 1px solid #3c3c3c
 | 
			
		||||
            cursor: pointer
 | 
			
		||||
            border: 1px solid #3c3c3c
 | 
			
		||||
            border: 1px solid var(--theme-bg-color)
 | 
			
		||||
            margin-bottom 6px
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
 | 
			
		||||
            &:hover {
 | 
			
		||||
              background-color #343540
 | 
			
		||||
              border: 1px solid var(--border-active);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .avatar {
 | 
			
		||||
@@ -78,7 +74,7 @@ $borderColor = #4676d0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .chat-title {
 | 
			
		||||
              color: #c1c1c1
 | 
			
		||||
              color: var(--el-text-color-regular);
 | 
			
		||||
              padding: 5px 10px;
 | 
			
		||||
              max-width 220px;
 | 
			
		||||
              font-size 14px;
 | 
			
		||||
@@ -92,10 +88,11 @@ $borderColor = #4676d0;
 | 
			
		||||
              position: absolute;
 | 
			
		||||
              right: 2px;
 | 
			
		||||
              top: 16px;
 | 
			
		||||
              color #ffffff
 | 
			
		||||
              color var(--text-fb)
 | 
			
		||||
              
 | 
			
		||||
 | 
			
		||||
              .el-dropdown-link {
 | 
			
		||||
                color #ffffff
 | 
			
		||||
                color var(--text-fb)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .el-icon {
 | 
			
		||||
@@ -105,8 +102,9 @@ $borderColor = #4676d0;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .chat-list-item.active {
 | 
			
		||||
            background-color: #343540;
 | 
			
		||||
            border-color #21aa93
 | 
			
		||||
            background-color :var(--theme-bg);
 | 
			
		||||
            box-shadow: 0 3px 9px rgba(112, 144, 176, 0.12);
 | 
			
		||||
            border: 1px solid var(--border-active);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -116,7 +114,7 @@ $borderColor = #4676d0;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        padding-top 12px
 | 
			
		||||
        border-top 1px solid #3c3c3c;
 | 
			
		||||
        // border-top 0.5px solid var(--el-border-color);
 | 
			
		||||
 | 
			
		||||
        .iconfont {
 | 
			
		||||
          margin-right 5px
 | 
			
		||||
@@ -133,15 +131,15 @@ $borderColor = #4676d0;
 | 
			
		||||
        min-width: 0;
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        background-color: var(--el-bg-color)
 | 
			
		||||
        color var(--el-text-color-primary)
 | 
			
		||||
 | 
			
		||||
        color var(--text-fb)
 | 
			
		||||
  
 | 
			
		||||
        .chat-config {
 | 
			
		||||
          height 30px
 | 
			
		||||
          height 50px
 | 
			
		||||
          padding 10px 30px
 | 
			
		||||
          display flex
 | 
			
		||||
          justify-content center
 | 
			
		||||
          justify-items center
 | 
			
		||||
          border-bottom 1px solid #d9d9e3
 | 
			
		||||
          // border-bottom 1px solid var(--el-border-color);
 | 
			
		||||
 | 
			
		||||
          .role-select-label {
 | 
			
		||||
            color #ffffff
 | 
			
		||||
@@ -157,18 +155,22 @@ $borderColor = #4676d0;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .setting {
 | 
			
		||||
            padding 5px
 | 
			
		||||
            // padding 5px
 | 
			
		||||
            border-radius 5px
 | 
			
		||||
            cursor pointer
 | 
			
		||||
            background-color #f2f2f2
 | 
			
		||||
            margin-right 10px
 | 
			
		||||
            width: 26px;
 | 
			
		||||
            height: 26px;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            line-height: 26px;
 | 
			
		||||
            // background-color #f2f2f2
 | 
			
		||||
            // margin-right 10px
 | 
			
		||||
            .iconfont {
 | 
			
		||||
              font-size 18px
 | 
			
		||||
              color #19c37d
 | 
			
		||||
              font-size 16px
 | 
			
		||||
              color var(--el-color-primary)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            &:hover {
 | 
			
		||||
              background-color #D5FAD3
 | 
			
		||||
              background-color var(--text--hover)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@@ -183,7 +185,8 @@ $borderColor = #4676d0;
 | 
			
		||||
          overflow: hidden;
 | 
			
		||||
          width: 100%;
 | 
			
		||||
          position relative
 | 
			
		||||
 | 
			
		||||
          background: var(--chat-bg)
 | 
			
		||||
  
 | 
			
		||||
          ::-webkit-scrollbar {
 | 
			
		||||
            width: 12px /* 滚动条宽度 */
 | 
			
		||||
            background #F1F1F1
 | 
			
		||||
@@ -205,6 +208,8 @@ $borderColor = #4676d0;
 | 
			
		||||
          .chat-box {
 | 
			
		||||
            overflow-y: auto;
 | 
			
		||||
            //border-bottom: 1px solid #4f4f4f
 | 
			
		||||
  scrollbar-width: none; /* 隐藏滚动条(Firefox) */
 | 
			
		||||
  -ms-overflow-style: none; /* 隐藏滚动条(IE、Edge) */
 | 
			
		||||
 | 
			
		||||
            // 变量定义
 | 
			
		||||
            --content-font-size: 16px;
 | 
			
		||||
@@ -217,8 +222,12 @@ $borderColor = #4676d0;
 | 
			
		||||
              font-size: 14px;
 | 
			
		||||
              display: flex;
 | 
			
		||||
              align-items: flex-start;
 | 
			
		||||
              
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            ::-webkit-scrollbar {
 | 
			
		||||
  display: none; /* 隐藏滚动条(Webkit 浏览器) */
 | 
			
		||||
}
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .input-box {
 | 
			
		||||
@@ -228,10 +237,11 @@ $borderColor = #4676d0;
 | 
			
		||||
 | 
			
		||||
            .input-box-inner {
 | 
			
		||||
              display flex
 | 
			
		||||
              background-color: #ffffff
 | 
			
		||||
              background-color:var(--chat-bg);
 | 
			
		||||
 | 
			
		||||
              justify-content: center;
 | 
			
		||||
              align-items: center;
 | 
			
		||||
              box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
 | 
			
		||||
              // box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
 | 
			
		||||
              padding 0 15px;
 | 
			
		||||
 | 
			
		||||
              .tool-item {
 | 
			
		||||
@@ -243,7 +253,7 @@ $borderColor = #4676d0;
 | 
			
		||||
                justify-items center
 | 
			
		||||
                padding 6px
 | 
			
		||||
                cursor pointer
 | 
			
		||||
                background #F2F2F2
 | 
			
		||||
                // background #F2F2F2
 | 
			
		||||
 | 
			
		||||
                &:hover {
 | 
			
		||||
                  background #D5FAD3
 | 
			
		||||
@@ -274,13 +284,17 @@ $borderColor = #4676d0;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                .input-border {
 | 
			
		||||
                  display flex
 | 
			
		||||
                  // display flex
 | 
			
		||||
                  width 100%
 | 
			
		||||
                  overflow hidden
 | 
			
		||||
                  border: 2px solid #21AA93
 | 
			
		||||
                  border: 2px solid var( --theme-border-primary)
 | 
			
		||||
                  border-radius 10px
 | 
			
		||||
                  padding 10px
 | 
			
		||||
                  background-color #F4F4F4
 | 
			
		||||
                  // background-color #F4F4F4
 | 
			
		||||
 | 
			
		||||
                  &:hover{
 | 
			
		||||
                    border-color var(--theme-border-hover)
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  .input-inner {
 | 
			
		||||
                    display flex
 | 
			
		||||
@@ -296,6 +310,7 @@ $borderColor = #4676d0;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    .prompt-input {
 | 
			
		||||
                      min-height: 58px;
 | 
			
		||||
                      width 100%
 | 
			
		||||
                      line-height: 24px
 | 
			
		||||
                      border none
 | 
			
		||||
@@ -312,12 +327,35 @@ $borderColor = #4676d0;
 | 
			
		||||
                  .send-btn {
 | 
			
		||||
                    width 32px
 | 
			
		||||
                    margin-left 10px
 | 
			
		||||
                    
 | 
			
		||||
                    .el-button {
 | 
			
		||||
                      padding 8px 5px;
 | 
			
		||||
                      border-radius 6px;
 | 
			
		||||
                      font-size 20px;
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                  .little-btns{
 | 
			
		||||
                    display: flex;
 | 
			
		||||
                    justify-content: flex-end;
 | 
			
		||||
                    align-items: center;
 | 
			
		||||
                    gap: 8px;
 | 
			
		||||
                    
 | 
			
		||||
                    .iconfont{
 | 
			
		||||
                        font-size: 19px;
 | 
			
		||||
                        cursor pointer
 | 
			
		||||
                        background-color: var(--chat-content-bg);
 | 
			
		||||
                        padding: 5px;
 | 
			
		||||
                        border-radius: 6px;
 | 
			
		||||
                      }
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  .add-new{
 | 
			
		||||
                    .el-icon{
 | 
			
		||||
                      font-size: 20px;
 | 
			
		||||
                      color: #754ff6;
 | 
			
		||||
                    }
 | 
			
		||||
                    cursor:pointer
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
@@ -380,7 +418,8 @@ $borderColor = #4676d0;
 | 
			
		||||
      width 40px
 | 
			
		||||
      height 40px
 | 
			
		||||
      border-radius 100%
 | 
			
		||||
      background-color #ffffff
 | 
			
		||||
      background-color:var(--chat-content-bg);
 | 
			
		||||
       color:var(--theme-text-color-primary);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -417,9 +456,13 @@ $borderColor = #4676d0;
 | 
			
		||||
        line-height 1.8
 | 
			
		||||
        font-size 16px
 | 
			
		||||
        overflow auto
 | 
			
		||||
        height 100%
 | 
			
		||||
        height: 70vh
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
    }
 | 
			
		||||
    .dialog-footer{
 | 
			
		||||
        margin-right: 22px;
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -436,4 +479,5 @@ $borderColor = #4676d0;
 | 
			
		||||
  .el-icon {
 | 
			
		||||
    margin-left 5px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										153
									
								
								web/src/assets/css/common.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,153 @@
 | 
			
		||||
:root{
 | 
			
		||||
  --sm-txt:rgba(163, 174, 208, 1);
 | 
			
		||||
  --text-secondary: #8a939d;
 | 
			
		||||
  --el-color-primary: rgb(107, 80, 225);
 | 
			
		||||
  --van-primary-color:rgb(107, 80, 225);
 | 
			
		||||
  --theme-textcolor-normal:#b0a0f8;
 | 
			
		||||
  --el-border-radius-base: 5px;
 | 
			
		||||
  --el-color-primary-light-5:rgb(107, 85, 255);
 | 
			
		||||
  --el-color-primary-light-3:rgb(78, 51, 254);
 | 
			
		||||
  --theme-btn-color:rgba(117, 81, 255, 1)
 | 
			
		||||
  --common-text-color:#6e4ef9;
 | 
			
		||||
  --el-component-size: 36px;
 | 
			
		||||
--el-color-primary-dark-2:rgb(169 152 247);
 | 
			
		||||
--el-button-active-border-color:rgb(169 152 247);
 | 
			
		||||
--el-color-success-light-9:#EAFFFC;
 | 
			
		||||
--el-color-success-light-8:#A7F0D9;
 | 
			
		||||
  --el-message-text-color:#0ECD8B;
 | 
			
		||||
  --el-color-success:#0ECD8B;
 | 
			
		||||
 | 
			
		||||
  --text-fff:#fff
 | 
			
		||||
  --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
 | 
			
		||||
  --a-link-color: #6e8eff
 | 
			
		||||
  --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: calc(var(--el-component-size) / 2);
 | 
			
		||||
  --el-dialog-border-radius: 10px
 | 
			
		||||
}
 | 
			
		||||
.login-box{
 | 
			
		||||
  --el-component-size: 48px;
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
.btn-go{
 | 
			
		||||
  background:  var(--btnColor);
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  padding: 5px 10px;
 | 
			
		||||
  &:hover{
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.btn-normal{
 | 
			
		||||
  background:  var(--theme-btn-color);
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
  padding: 5px 10px;
 | 
			
		||||
  &:hover{
 | 
			
		||||
    color: #fff;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.flex{
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
.flex-center{
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
.flex-between{
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
}
 | 
			
		||||
.theme-color-primary{
 | 
			
		||||
  color: var(--el-color-primary);
 | 
			
		||||
}
 | 
			
		||||
.text-color-primary{
 | 
			
		||||
  color:var(--text-color-primary)
 | 
			
		||||
}
 | 
			
		||||
.w100{
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.el-input__wrapper{
 | 
			
		||||
  background: var( --card-bg)
 | 
			
		||||
}
 | 
			
		||||
.el-dialog__title{
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  line-height: 28px;
 | 
			
		||||
}
 | 
			
		||||
.el-button--primary{
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-button {
 | 
			
		||||
  height auto
 | 
			
		||||
}
 | 
			
		||||
/* 设置滚动条的宽度 */
 | 
			
		||||
::-webkit-scrollbar {
 | 
			
		||||
  width: 12px; /* 垂直滚动条宽度 */
 | 
			
		||||
  height: 12px; /* 水平滚动条高度 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 滚动条的轨道背景颜色 */
 | 
			
		||||
 | 
			
		||||
::-webkit-scrollbar-track {
 | 
			
		||||
  background: #f1f1f1 !important; /* 滚动条轨道颜色 */
 | 
			
		||||
  border-radius: 6px; /* 可选:轨道圆角 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 滚动条滑块的颜色 */
 | 
			
		||||
::-webkit-scrollbar-thumb {
 | 
			
		||||
  background: #888 !important; /* 滑块颜色 */
 | 
			
		||||
  border-radius: 6px; /* 可选:滑块圆角 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 鼠标悬停在滑块上的颜色 */
 | 
			
		||||
::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
  background: #555; /* 悬停时的滑块颜色 */
 | 
			
		||||
}
 | 
			
		||||
//.el-message-box
 | 
			
		||||
.el-message-box{
 | 
			
		||||
  --el-messagebox-border-radius: 10px
 | 
			
		||||
}
 | 
			
		||||
.el-message-box__container{
 | 
			
		||||
  //border-top: 1px solid #dbd3f4;
 | 
			
		||||
  padding-top: 7px;
 | 
			
		||||
  .el-message-box__message{
 | 
			
		||||
    --text-color:var(--theme-text-color-primary)
 | 
			
		||||
    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
 | 
			
		||||
}
 | 
			
		||||
.box-card{
 | 
			
		||||
  padding: 20px;
 | 
			
		||||
  background-color: var(--chat-bg);
 | 
			
		||||
  border-radius: 8px;
 | 
			
		||||
}
 | 
			
		||||
.el-table th.el-table__cell {
 | 
			
		||||
    background-color:  var(--chat-bg)
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,4 +23,15 @@
 | 
			
		||||
  ::-webkit-scrollbar-thumb:hover {
 | 
			
		||||
    background-color: #666666;
 | 
			
		||||
  }
 | 
			
		||||
  overflow: auto; /* 保持滚动功能 */
 | 
			
		||||
  scrollbar-width: none; /* 隐藏滚动条(Firefox) */
 | 
			
		||||
  -ms-overflow-style: none; /* 隐藏滚动条(IE、Edge) */
 | 
			
		||||
  ::-webkit-scrollbar {
 | 
			
		||||
    display: none; /* 隐藏滚动条(Webkit 浏览器) */
 | 
			
		||||
  }
 | 
			
		||||
  &.showScrollbar {
 | 
			
		||||
    ::-webkit-scrollbar {
 | 
			
		||||
      display: none; /* 隐藏滚动条(Webkit 浏览器) */
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								web/src/assets/css/font.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: "OPlusSans3-Regular";
 | 
			
		||||
  src: url("../fonts/OPlusSans3-Regular.ttf") format("truetype");
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
}
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: "OPlusSans3-Medium";
 | 
			
		||||
  src: url("../fonts/OPlusSans3-Medium.ttf") format("truetype");
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
  font-style: normal;
 | 
			
		||||
}
 | 
			
		||||
$font-regular = "OPlusSans3-Regular", "PingFangSC-Regular", "Roboto", "sans-serif";
 | 
			
		||||
$font-medium = "OPlusSans3-Medium", "PingFangSC-Medium", "Roboto", "sans-serif";
 | 
			
		||||
 | 
			
		||||
.font-regular {
 | 
			
		||||
  font-family: $font-regular;
 | 
			
		||||
  font-weight: normal;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.font-medium {
 | 
			
		||||
  font-family: $font-medium;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,166 +1,240 @@
 | 
			
		||||
.home {
 | 
			
		||||
.layout{
 | 
			
		||||
  display: flex;
 | 
			
		||||
  height 100vh
 | 
			
		||||
  width 100%
 | 
			
		||||
  flex-flow column
 | 
			
		||||
 | 
			
		||||
  .header {
 | 
			
		||||
    display flex
 | 
			
		||||
    justify-content space-between
 | 
			
		||||
    height 50px
 | 
			
		||||
    line-height 50px
 | 
			
		||||
    background-color #1E1F22
 | 
			
		||||
    padding-right 20px
 | 
			
		||||
 | 
			
		||||
    .banner {
 | 
			
		||||
      display flex
 | 
			
		||||
 | 
			
		||||
      .logo {
 | 
			
		||||
        display flex
 | 
			
		||||
        padding 5px
 | 
			
		||||
        cursor pointer
 | 
			
		||||
 | 
			
		||||
        .el-image {
 | 
			
		||||
          width 48px
 | 
			
		||||
          height 48px
 | 
			
		||||
          border-radius 50%
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .title {
 | 
			
		||||
        display: flex;
 | 
			
		||||
        color: #ffffff;
 | 
			
		||||
        font-size: 20px;
 | 
			
		||||
        padding 0 10px
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .navbar {
 | 
			
		||||
      display flex
 | 
			
		||||
      flex-flow row
 | 
			
		||||
 | 
			
		||||
      .link-button {
 | 
			
		||||
        margin-right 15px
 | 
			
		||||
        color #e1e1e1
 | 
			
		||||
        padding 0 10px
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background-color #414141
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .iconfont {
 | 
			
		||||
          font-size 24px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .user-info {
 | 
			
		||||
        width 100%
 | 
			
		||||
        padding 5px 0;
 | 
			
		||||
 | 
			
		||||
        .el-dropdown-link {
 | 
			
		||||
          width 100%;
 | 
			
		||||
          cursor: pointer
 | 
			
		||||
          display flex
 | 
			
		||||
 | 
			
		||||
          .el-image {
 | 
			
		||||
            width: 36px;
 | 
			
		||||
            height: 36px;
 | 
			
		||||
            border-radius: 50%
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .el-icon {
 | 
			
		||||
            color: #cccccc;
 | 
			
		||||
            line-height 24px;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
      }
 | 
			
		||||
  position: relative;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  .big-top-title{
 | 
			
		||||
    padding-top: 10px;
 | 
			
		||||
  }
 | 
			
		||||
  .top-collapse{
 | 
			
		||||
    padding-top: 10px
 | 
			
		||||
    img{
 | 
			
		||||
      width 24px !important
 | 
			
		||||
      height: 24px !important
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .main {
 | 
			
		||||
    width 100%
 | 
			
		||||
    display flex
 | 
			
		||||
    flex-flow row
 | 
			
		||||
 | 
			
		||||
    .navigator {
 | 
			
		||||
      display flex
 | 
			
		||||
      flex-flow column
 | 
			
		||||
      width 60px
 | 
			
		||||
      padding 10px 1px
 | 
			
		||||
      border-right: 1px solid #3c3c3c
 | 
			
		||||
      background-color: #1E1F22
 | 
			
		||||
 | 
			
		||||
      .nav-items {
 | 
			
		||||
        margin-top: 10px;
 | 
			
		||||
        padding 0 5px
 | 
			
		||||
 | 
			
		||||
        li {
 | 
			
		||||
          margin-bottom 15px
 | 
			
		||||
          display flex
 | 
			
		||||
          flex-flow column
 | 
			
		||||
 | 
			
		||||
          a {
 | 
			
		||||
            color #DADBDC
 | 
			
		||||
            border-radius 10px
 | 
			
		||||
            width 48px
 | 
			
		||||
            height 48px
 | 
			
		||||
            display flex
 | 
			
		||||
            justify-content center
 | 
			
		||||
            align-items center
 | 
			
		||||
            cursor pointer
 | 
			
		||||
            background-color #414348
 | 
			
		||||
 | 
			
		||||
            .el-image {
 | 
			
		||||
              border-radius 10px
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .iconfont {
 | 
			
		||||
              font-size 20px
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          a:hover, a.active {
 | 
			
		||||
            color #47fff1
 | 
			
		||||
            background-color #0F7A71
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .title {
 | 
			
		||||
            font-size: 12px
 | 
			
		||||
            padding-top: 6px
 | 
			
		||||
            color: #e5e7eb;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            white-space: nowrap;      /* 防止文本换行 */
 | 
			
		||||
            overflow: hidden;         /* 隐藏溢出内容 */
 | 
			
		||||
            text-overflow: unset;  /* 使用省略号表示溢出内容 */
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .active {
 | 
			
		||||
            color #47fff1
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
  .tab-box{
 | 
			
		||||
    align-items: center
 | 
			
		||||
    background-color: var(--card-bg)
 | 
			
		||||
    // height: 100%
 | 
			
		||||
    // position: fixed;
 | 
			
		||||
    height: 100vh;
 | 
			
		||||
    .title{
 | 
			
		||||
      font-size: 28px
 | 
			
		||||
      height: 40px
 | 
			
		||||
      width 120px
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      word-wrap break-all;
 | 
			
		||||
      overflow hidden
 | 
			
		||||
      font-weight: 700
 | 
			
		||||
      color:var(--text-theme-color)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .content {
 | 
			
		||||
      width: 100%
 | 
			
		||||
      overflow auto
 | 
			
		||||
      box-sizing: border-box
 | 
			
		||||
      background-color #282c34
 | 
			
		||||
    img{
 | 
			
		||||
      height: 44px
 | 
			
		||||
      object-fit: cover
 | 
			
		||||
      border-radius: 50%
 | 
			
		||||
      border: 2px solid #754ff6;
 | 
			
		||||
      background: #fff
 | 
			
		||||
    }
 | 
			
		||||
    .marr{
 | 
			
		||||
      margin-right: 4px;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
.flex-center-col{
 | 
			
		||||
  display flex
 | 
			
		||||
  align-items center
 | 
			
		||||
  flex-direction column
 | 
			
		||||
 | 
			
		||||
  .iconfont {
 | 
			
		||||
    font-size 22px
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .icon-expand {
 | 
			
		||||
    font-size 24px
 | 
			
		||||
    margin-bottom 10px
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    color var(--text-color)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .icon-colspan {
 | 
			
		||||
    font-size 18px
 | 
			
		||||
    margin-left 3px
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    color var(--text-color)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
.menu-list-collapse{
 | 
			
		||||
  .flex-center-col{
 | 
			
		||||
    flex-direction: row;
 | 
			
		||||
  }
 | 
			
		||||
  .menu-list-item{
 | 
			
		||||
    
 | 
			
		||||
    height: 38px;
 | 
			
		||||
    line-height: 38px;
 | 
			
		||||
 | 
			
		||||
    .iconfont {
 | 
			
		||||
      font-size 16px
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
  }
 | 
			
		||||
  .menu-list-item:hover,
 | 
			
		||||
  .active{
 | 
			
		||||
    background: rgba(79, 89, 102, .122);
 | 
			
		||||
 | 
			
		||||
    border-radius: 8px;
 | 
			
		||||
    .el-icon{
 | 
			
		||||
      background: transparent !important;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .menu-title{
 | 
			
		||||
    font-size: 15px !important;
 | 
			
		||||
    margin-bottom: 0 !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.openicon{
 | 
			
		||||
  font-size: 40px;
 | 
			
		||||
  color: #754ff6;
 | 
			
		||||
}
 | 
			
		||||
.menuIcon{
 | 
			
		||||
  .openicon{
 | 
			
		||||
  font-size: 28px;
 | 
			
		||||
  color: #754ff6;
 | 
			
		||||
}
 | 
			
		||||
  }
 | 
			
		||||
.menu-list{
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
  .svg-icon{
 | 
			
		||||
    svg{
 | 
			
		||||
      width: 30px;
 | 
			
		||||
      height: 30px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .menu-list-item{
 | 
			
		||||
    // margin-bottom: 10px;
 | 
			
		||||
    margin: 0 8px 8px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    font-weight: 550;
 | 
			
		||||
    &:hover{
 | 
			
		||||
     .el-icon{
 | 
			
		||||
        background: rgba(79, 89, 102, .122);
 | 
			
		||||
      }
 | 
			
		||||
     
 | 
			
		||||
    }
 | 
			
		||||
    .el-icon{
 | 
			
		||||
      width: 24px;
 | 
			
		||||
      height: 24px;
 | 
			
		||||
      padding: 4px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      border-radius: 50%;
 | 
			
		||||
      font-size: 20px;
 | 
			
		||||
      // img{
 | 
			
		||||
      //   width: 24px;
 | 
			
		||||
      //   height: 24px;
 | 
			
		||||
      // }
 | 
			
		||||
      
 | 
			
		||||
    }
 | 
			
		||||
    &.active{
 | 
			
		||||
      color: var(--text-color);
 | 
			
		||||
      font-weight: 700;
 | 
			
		||||
      filter: none !important;
 | 
			
		||||
      .el-icon{
 | 
			
		||||
        background: rgba(79, 89, 102, .122);
 | 
			
		||||
        filter: invert(100%);
 | 
			
		||||
      } 
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    
 | 
			
		||||
  }
 | 
			
		||||
  .bot{
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    bottom: 6px;
 | 
			
		||||
 
 | 
			
		||||
    
 | 
			
		||||
    }
 | 
			
		||||
  .bot-line{
 | 
			
		||||
    
 | 
			
		||||
    width : 100%;
 | 
			
		||||
    height: 1px;
 | 
			
		||||
    background: var(--line-box)
 | 
			
		||||
    margin: 20px 0 10px 0;
 | 
			
		||||
    }
 | 
			
		||||
  .menu-title{
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    margin-bottom: 6px;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
  }
 | 
			
		||||
  .icon-house,
 | 
			
		||||
  .icon-github{
 | 
			
		||||
      font-size: 20px;
 | 
			
		||||
      color: #754ff6;
 | 
			
		||||
      cursor pointer
 | 
			
		||||
  }
 | 
			
		||||
  .menu-bot-item{
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: space-around;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
    a{
 | 
			
		||||
      // margin-right: 46px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
::v-deep(.theme-box){
 | 
			
		||||
  position: relative !important;
 | 
			
		||||
  right: initial;
 | 
			
		||||
  bottom: initial;
 | 
			
		||||
  width: 20px;
 | 
			
		||||
  height: 20px;
 | 
			
		||||
  line-height: 20px;
 | 
			
		||||
  .iconfont{
 | 
			
		||||
    font-size: 15px !important;}
 | 
			
		||||
}
 | 
			
		||||
.right-main{
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  // background: #f5f7fd;
 | 
			
		||||
  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;
 | 
			
		||||
    right: 8px;
 | 
			
		||||
    z-index : 999;
 | 
			
		||||
    top:0;
 | 
			
		||||
    // width 100%;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
  }
 | 
			
		||||
  .btn-go{
 | 
			
		||||
    background: #754ff6;
 | 
			
		||||
    margin: 10px 10px 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.el-popper {
 | 
			
		||||
  .more-menus {
 | 
			
		||||
    li {
 | 
			
		||||
      padding 10px 15px
 | 
			
		||||
      cursor pointer
 | 
			
		||||
      border-radius 5px
 | 
			
		||||
      margin 5px 0
 | 
			
		||||
      padding: 0px 15px;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      border-radius: 5px;
 | 
			
		||||
      margin: 5px 0;
 | 
			
		||||
      height: 38px;
 | 
			
		||||
      line-height: 38px;
 | 
			
		||||
 | 
			
		||||
      .el-image {
 | 
			
		||||
        position: relative
 | 
			
		||||
@@ -169,26 +243,49 @@
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        background-color #f1f1f1
 | 
			
		||||
        background: rgba(79, 89, 102, 0.1);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    li.active {
 | 
			
		||||
      background-color #f1f1f1
 | 
			
		||||
      background: rgba(79, 89, 102, 0.1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .user-info-menu {
 | 
			
		||||
    li {
 | 
			
		||||
      a {
 | 
			
		||||
        width 100%
 | 
			
		||||
        justify-content left
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          text-decoration none !important
 | 
			
		||||
          color var(--el-primary-text-color)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
  .setting-menus{
 | 
			
		||||
    .title{
 | 
			
		||||
      color: #222226;
 | 
			
		||||
    }
 | 
			
		||||
    .el-icon,
 | 
			
		||||
    .iconfont{
 | 
			
		||||
      font-size: 18px
 | 
			
		||||
      margin-right: 6px
 | 
			
		||||
    }
 | 
			
		||||
    color: #222226;
 | 
			
		||||
  }
 | 
			
		||||
  .username{
 | 
			
		||||
    display: -webkit-box;          
 | 
			
		||||
    -webkit-box-orient: vertical;    
 | 
			
		||||
    overflow: hidden;               
 | 
			
		||||
    -webkit-line-clamp: 1;           
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
.rightHeightMax{
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  max-height: 100vh;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
.rightHeight{
 | 
			
		||||
  height: calc(100vh - 42px);
 | 
			
		||||
  max-height: calc(100vh - 42px);
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .content{
 | 
			
		||||
    padding-top: 42px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.content{
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  overflow: scroll;
 | 
			
		||||
}
 | 
			
		||||
@@ -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,23 @@
 | 
			
		||||
        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 +91,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 +139,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
 | 
			
		||||
      //padding 0 10px 10px 10px
 | 
			
		||||
      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 #fff
 | 
			
		||||
 | 
			
		||||
                            &: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,23 @@
 | 
			
		||||
        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 +107,7 @@
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-textarea {
 | 
			
		||||
          --el-input-focus-border-color: #47FFF1;
 | 
			
		||||
          // --el-input-focus-border-color: #47FFF1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-textarea__inner {
 | 
			
		||||
@@ -142,7 +141,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);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
@@ -52,6 +52,8 @@
 | 
			
		||||
 | 
			
		||||
      .list-item {
 | 
			
		||||
 | 
			
		||||
        display flex
 | 
			
		||||
 | 
			
		||||
        .image {
 | 
			
		||||
          overflow hidden
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +70,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 +79,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,11 +1,14 @@
 | 
			
		||||
.index-page {
 | 
			
		||||
  margin: 0
 | 
			
		||||
  overflow hidden
 | 
			
		||||
  color #ffffff
 | 
			
		||||
  color var(--text-color)
 | 
			
		||||
  display flex
 | 
			
		||||
  justify-content center
 | 
			
		||||
  align-items baseline
 | 
			
		||||
  padding-top 150px
 | 
			
		||||
  align-items center
 | 
			
		||||
  background: var(--theme-bg) !important
 | 
			
		||||
  flex-flow column
 | 
			
		||||
  height: 100vh
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
 | 
			
		||||
  .color-bg {
 | 
			
		||||
    position absolute
 | 
			
		||||
@@ -30,33 +33,47 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .menu-box {
 | 
			
		||||
    position absolute
 | 
			
		||||
    top 0
 | 
			
		||||
    width 100%
 | 
			
		||||
    display flex
 | 
			
		||||
    height 80px
 | 
			
		||||
    align-items center
 | 
			
		||||
 | 
			
		||||
    .el-menu {
 | 
			
		||||
      padding 0 30px
 | 
			
		||||
      width 100%
 | 
			
		||||
      display flex
 | 
			
		||||
      justify-content space-between
 | 
			
		||||
      align-items center
 | 
			
		||||
      background none
 | 
			
		||||
      border none
 | 
			
		||||
 | 
			
		||||
      .menu-item {
 | 
			
		||||
        display flex
 | 
			
		||||
        padding 20px 0
 | 
			
		||||
        // padding 20px 0
 | 
			
		||||
        height 40px
 | 
			
		||||
        align-items center
 | 
			
		||||
 | 
			
		||||
        color #ffffff
 | 
			
		||||
        color var(--text-color);
 | 
			
		||||
        .iconfont{
 | 
			
		||||
          color var(--text-color);
 | 
			
		||||
          font-size: 28px;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        .icon-book{
 | 
			
		||||
          margin-right: 6px;
 | 
			
		||||
        }
 | 
			
		||||
        .title {
 | 
			
		||||
          font-size 24px
 | 
			
		||||
          color var(--text-color);
 | 
			
		||||
          font-size: 24px;
 | 
			
		||||
          padding 10px 10px 0 10px
 | 
			
		||||
          font-weight: 700;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .logo {
 | 
			
		||||
          height 50px
 | 
			
		||||
          height 60px
 | 
			
		||||
          border-radius 50%
 | 
			
		||||
          background: #fff
 | 
			
		||||
          border: 2px solid #754ff6
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .el-button {
 | 
			
		||||
@@ -112,6 +129,34 @@
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      .nav-item-box{
 | 
			
		||||
        width: 100%;
 | 
			
		||||
        border-radius: 8px;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        transition:  all 0.2s cubic-bezier(0.645,0.045,0.355,1);
 | 
			
		||||
        aspect-ratio: 1.1028 / 1;
 | 
			
		||||
        background: var( --card-bg)
 | 
			
		||||
       display: flex;
 | 
			
		||||
        flex-direction: column;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: center;
 | 
			
		||||
        min-width: 160px
 | 
			
		||||
        // min-height: 190px;
 | 
			
		||||
        i{
 | 
			
		||||
          display: inline-block
 | 
			
		||||
          min-width: 48px;
 | 
			
		||||
          width: 48px;
 | 
			
		||||
          height: 48px;
 | 
			
		||||
          font-size: 38px
 | 
			
		||||
          border-radius: 24px;
 | 
			
		||||
          color: var(--normal-color)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &:hover{
 | 
			
		||||
          box-shadow: 0 4px 14px 0 rgba(17, 13, 83, .18);
 | 
			
		||||
          transform: translateY(-8px);}
 | 
			
		||||
        
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -121,4 +166,49 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.cursor-ani {
 | 
			
		||||
   position: relative;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
.cursor-ani::after {
 | 
			
		||||
   content: '';
 | 
			
		||||
   position: absolute;
 | 
			
		||||
   width: 1px;
 | 
			
		||||
   height: 28px;
 | 
			
		||||
   background: #333;
 | 
			
		||||
   transform: translateX(3px) translateY(3px);
 | 
			
		||||
   animation: cursor-blinks 0.8s infinite forwards;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@keyframes cursor-blinks {
 | 
			
		||||
   from {
 | 
			
		||||
     opacity: 0;
 | 
			
		||||
   }
 | 
			
		||||
 
 | 
			
		||||
   to {
 | 
			
		||||
     opacity: 1;
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
.cursor-ani {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  min-height: 34px;
 | 
			
		||||
  font-size: 1.5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.msg-text span {
 | 
			
		||||
  transition: color 0.3s ease; /* 平滑的颜色过渡 */
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
.logo-box{
 | 
			
		||||
  width: 60px
 | 
			
		||||
  height: 60px
 | 
			
		||||
  background: #fff
 | 
			
		||||
  border-radius: 50%
 | 
			
		||||
  img{
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    object-fit: cover;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,117 +1,62 @@
 | 
			
		||||
.bg {
 | 
			
		||||
  position fixed
 | 
			
		||||
  left 0
 | 
			
		||||
  right 0
 | 
			
		||||
  top 0
 | 
			
		||||
  bottom 0
 | 
			
		||||
  background-color #313237
 | 
			
		||||
  background-image url("~@/assets/img/login-bg.jpg")
 | 
			
		||||
  background-size cover
 | 
			
		||||
  background-position center
 | 
			
		||||
  background-repeat repeat-y
 | 
			
		||||
  //filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
 | 
			
		||||
.loginPage{
 | 
			
		||||
  background: var(--card-bg) !important
 | 
			
		||||
  background-color: var(---card-bg) !important
 | 
			
		||||
 | 
			
		||||
.form-title{
 | 
			
		||||
  color:var( --text-theme-color)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main {
 | 
			
		||||
  .contain {
 | 
			
		||||
    position fixed
 | 
			
		||||
    left 50%
 | 
			
		||||
    top 40%
 | 
			
		||||
    width 90%
 | 
			
		||||
    max-width 400px;
 | 
			
		||||
    transform translate(-50%, -50%)
 | 
			
		||||
    padding 20px 10px;
 | 
			
		||||
    color #ffffff
 | 
			
		||||
    border-radius 10px;
 | 
			
		||||
.left{
 | 
			
		||||
  width: 50%;
 | 
			
		||||
 
 | 
			
		||||
  .login-box{
 | 
			
		||||
    width: 410px;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    min-height: calc(100vh - 48px);
 | 
			
		||||
   
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
  .wechatLog{
 | 
			
		||||
    width: 410px;
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    line-height: 50px;
 | 
			
		||||
    text-align: center
 | 
			
		||||
    background: var( --sign-bg)
 | 
			
		||||
    a{
 | 
			
		||||
      color: var(--text-theme-color)
 | 
			
		||||
 | 
			
		||||
    .logo {
 | 
			
		||||
      text-align center
 | 
			
		||||
 | 
			
		||||
      .el-image {
 | 
			
		||||
        width 120px;
 | 
			
		||||
        cursor pointer
 | 
			
		||||
        border-radius 50%
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .header {
 | 
			
		||||
      width 100%
 | 
			
		||||
      margin-bottom 24px
 | 
			
		||||
      font-size 24px
 | 
			
		||||
      color $white_v1
 | 
			
		||||
      letter-space 2px
 | 
			
		||||
      text-align center
 | 
			
		||||
      padding-top 10px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .content {
 | 
			
		||||
      width 100%
 | 
			
		||||
      height: auto
 | 
			
		||||
      border-radius 3px
 | 
			
		||||
 | 
			
		||||
      .block {
 | 
			
		||||
        margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
        .el-input__inner {
 | 
			
		||||
          border 1px solid $gray-v6 !important
 | 
			
		||||
 | 
			
		||||
          .el-icon-user, .el-icon-lock {
 | 
			
		||||
            font-size 20px
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .btn-row {
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
 | 
			
		||||
        .login-btn {
 | 
			
		||||
          width 100%
 | 
			
		||||
          font-size 16px
 | 
			
		||||
          letter-spacing 2px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .text-line {
 | 
			
		||||
        justify-content center
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
        font-size 14px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .opt {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        .el-col {
 | 
			
		||||
          text-align center
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .divider {
 | 
			
		||||
        border-top: 2px solid #c1c1c1;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .clogin {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        display flex
 | 
			
		||||
        justify-content center
 | 
			
		||||
 | 
			
		||||
        .iconfont {
 | 
			
		||||
          font-size 20px
 | 
			
		||||
          background: #E9F1F6;
 | 
			
		||||
          padding: 8px;
 | 
			
		||||
          border-radius: 50%
 | 
			
		||||
          cursor pointer
 | 
			
		||||
        }
 | 
			
		||||
        .iconfont.icon-wechat {
 | 
			
		||||
          color #0bc15f
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    font-size: 14px;
 | 
			
		||||
    margin-bottom: 26px
 | 
			
		||||
    border-radius: 16px;
 | 
			
		||||
    .icon-wechat{
 | 
			
		||||
      color: #0bc15f
 | 
			
		||||
      margin-right: 9px
 | 
			
		||||
      font-size: 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .footer {
 | 
			
		||||
    color #ffffff;
 | 
			
		||||
 | 
			
		||||
    .container {
 | 
			
		||||
      padding 20px;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
  .text-color-primary{
 | 
			
		||||
    cursor :pointer
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
.login-btn {
 | 
			
		||||
  width :100%
 | 
			
		||||
  height: 40px;
 | 
			
		||||
  border-radius: 16px;
 | 
			
		||||
}
 | 
			
		||||
 .code-input{
 | 
			
		||||
    width: 306px;
 | 
			
		||||
    margin-right: 9px;
 | 
			
		||||
  }
 | 
			
		||||
:deep(.el-tabs__item.is-active), :deep(.el-tabs__item:hover) {
 | 
			
		||||
  color: var(--common-text-color) !important
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:deep(.el-tabs__item) {
 | 
			
		||||
  color: var(--text-theme-color)
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -145,11 +147,10 @@
 | 
			
		||||
        padding 10px 15px
 | 
			
		||||
        border-radius 10px
 | 
			
		||||
        cursor pointer
 | 
			
		||||
        margin-bottom 10px
 | 
			
		||||
        margin-bottom 20px
 | 
			
		||||
        background: var(--chat-bg);
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background-color #2A2525
 | 
			
		||||
        }
 | 
			
		||||
       
 | 
			
		||||
 | 
			
		||||
        .left {
 | 
			
		||||
          .container {
 | 
			
		||||
@@ -189,7 +190,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
 | 
			
		||||
@@ -214,7 +216,7 @@
 | 
			
		||||
          padding 0 20px
 | 
			
		||||
 | 
			
		||||
          .prompt,.failed {
 | 
			
		||||
            padding 6px 0
 | 
			
		||||
            padding 0
 | 
			
		||||
            font-size 16px
 | 
			
		||||
            max-height 80px
 | 
			
		||||
            line-height 28px
 | 
			
		||||
@@ -222,7 +224,8 @@
 | 
			
		||||
            text-overflow ellipsis
 | 
			
		||||
          }
 | 
			
		||||
          .prompt {
 | 
			
		||||
            color rgb(250 247 245)
 | 
			
		||||
            color var( --text-fb)
 | 
			
		||||
            cursor: text
 | 
			
		||||
          }
 | 
			
		||||
          .failed {
 | 
			
		||||
            color #E4696B
 | 
			
		||||
@@ -248,7 +251,6 @@
 | 
			
		||||
 | 
			
		||||
              .text {
 | 
			
		||||
                margin-right 10px
 | 
			
		||||
                color #e1e1e1
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -256,11 +258,12 @@
 | 
			
		||||
              background none
 | 
			
		||||
              padding 6px
 | 
			
		||||
              transition background 0.6s ease 0s
 | 
			
		||||
              color #726E6C
 | 
			
		||||
              color #919191
 | 
			
		||||
 | 
			
		||||
              &:hover {
 | 
			
		||||
                background #5f5958
 | 
			
		||||
                color #e1e1e1
 | 
			
		||||
                // background #5f5958
 | 
			
		||||
                // color #e1e1e1
 | 
			
		||||
                color:var(--el-color-primary)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .downloading {
 | 
			
		||||
@@ -274,7 +277,7 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .pagination {
 | 
			
		||||
      padding 10px 20px
 | 
			
		||||
      margin-top 20px
 | 
			
		||||
      display flex
 | 
			
		||||
      justify-content center
 | 
			
		||||
    }
 | 
			
		||||
@@ -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,15 @@
 | 
			
		||||
 | 
			
		||||
  .btn {
 | 
			
		||||
    margin-right 10px
 | 
			
		||||
    background-color #363030
 | 
			
		||||
    border none
 | 
			
		||||
    border-radius 5px
 | 
			
		||||
    padding 5px 10px
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    color: var(--theme-text-color-primary)
 | 
			
		||||
    background-color var(--btn-bg)
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color #5F5958
 | 
			
		||||
      opacity: 0.7
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,8 @@ body {
 | 
			
		||||
      .container {
 | 
			
		||||
        padding: 15px 20px 30px 20px;
 | 
			
		||||
        background: #ffffff;
 | 
			
		||||
        margin-bottom 80px
 | 
			
		||||
        margin-bottom 20px
 | 
			
		||||
        max-width 100%
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .crumbs {
 | 
			
		||||
@@ -170,24 +171,68 @@ body {
 | 
			
		||||
  .content-collapse {
 | 
			
		||||
    left: 65px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-table {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    
 | 
			
		||||
    .el-table__body-header {
 | 
			
		||||
      height 40px
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.w-100 {
 | 
			
		||||
  width 100%
 | 
			
		||||
}
 | 
			
		||||
.mr-1 {
 | 
			
		||||
  margin-right 5px
 | 
			
		||||
  margin-right 0.5rem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.mr-2 {
 | 
			
		||||
  margin-right 10px
 | 
			
		||||
  margin-right 1rem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ml-1 {
 | 
			
		||||
  margin-left 5px
 | 
			
		||||
  margin-left 0.5rem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ml-2 {
 | 
			
		||||
  margin-left 10px
 | 
			
		||||
  margin-left 1rem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.d-flex {
 | 
			
		||||
  display flex !important
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.justify-center {
 | 
			
		||||
  justify-content center
 | 
			
		||||
}
 | 
			
		||||
.justify-between {
 | 
			
		||||
  justify-content space-between
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.justify-end {
 | 
			
		||||
  justify-content flex-end
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-center {
 | 
			
		||||
  align-items center
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.p-1 {
 | 
			
		||||
  padding 0.5rem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.p-2 {
 | 
			
		||||
  padding 1rem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.m-1 {
 | 
			
		||||
  margin 0.5rem
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.m-2 {
 | 
			
		||||
  margin 1rem
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {
 | 
			
		||||
@@ -128,10 +130,13 @@
 | 
			
		||||
          position: absolute
 | 
			
		||||
          bottom: 10px
 | 
			
		||||
          right: 20px
 | 
			
		||||
          display: flex;
 | 
			
		||||
 | 
			
		||||
          .mm-toolbar {
 | 
			
		||||
            line-height: 36px;
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow row
 | 
			
		||||
            margin-left: 10px;
 | 
			
		||||
 | 
			
		||||
            .mm-toolbar-brand {
 | 
			
		||||
              display none
 | 
			
		||||
@@ -139,7 +144,7 @@
 | 
			
		||||
 | 
			
		||||
            .mm-toolbar-item {
 | 
			
		||||
              cursor pointer
 | 
			
		||||
              color var(--el-color-white)
 | 
			
		||||
              color var( --text-fb)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@@ -135,7 +136,8 @@
 | 
			
		||||
 | 
			
		||||
              .el-button {
 | 
			
		||||
                margin 10px 5px 0 5px
 | 
			
		||||
                padding 0
 | 
			
		||||
                height 32px
 | 
			
		||||
                filter: none;
 | 
			
		||||
 | 
			
		||||
                .icon-alipay,.icon-wechat-pay {
 | 
			
		||||
                  color #ffffff
 | 
			
		||||
@@ -145,7 +147,7 @@
 | 
			
		||||
                  font-size 24px
 | 
			
		||||
                }
 | 
			
		||||
                .icon-jd-pay {
 | 
			
		||||
                  color #ffffff
 | 
			
		||||
                  color var(--text-theme-color)
 | 
			
		||||
                  font-size 24px
 | 
			
		||||
                }
 | 
			
		||||
                .icon-douyin {
 | 
			
		||||
@@ -161,8 +163,10 @@
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          &: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像素 */
 | 
			
		||||
            box-shadow: 0 0 10px var(--shadow-color);
 | 
			
		||||
            background-color: var(--hover-deep-color)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -79,12 +79,12 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.van-theme-dark {
 | 
			
		||||
  .mobile-chat {
 | 
			
		||||
    .chat-list-wrapper {
 | 
			
		||||
      background #232425;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// .van-theme-dark {
 | 
			
		||||
//   .mobile-chat {
 | 
			
		||||
//     .chat-list-wrapper {
 | 
			
		||||
//       background #232425;
 | 
			
		||||
//     }
 | 
			
		||||
//   }
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
@import "model-select.styl"
 | 
			
		||||
@@ -183,6 +183,7 @@
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow column
 | 
			
		||||
            justify-content center
 | 
			
		||||
            height 200px
 | 
			
		||||
 | 
			
		||||
            .title {
 | 
			
		||||
              margin-bottom 20px
 | 
			
		||||
 
 | 
			
		||||
@@ -4,15 +4,20 @@
 | 
			
		||||
    width 100%
 | 
			
		||||
    display flex
 | 
			
		||||
    flex-flow row
 | 
			
		||||
 | 
			
		||||
    .image-slot {
 | 
			
		||||
      color var(--theme-text-color-primary)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .job-item {
 | 
			
		||||
    margin-right 10px
 | 
			
		||||
    width 200px
 | 
			
		||||
    height 200px
 | 
			
		||||
    overflow hidden
 | 
			
		||||
    padding 2px
 | 
			
		||||
    background-color #555555
 | 
			
		||||
    background-color var( --gray-btn-bg)
 | 
			
		||||
 | 
			
		||||
    .job-item-inner {
 | 
			
		||||
      position relative
 | 
			
		||||
@@ -31,7 +36,7 @@
 | 
			
		||||
 | 
			
		||||
        span {
 | 
			
		||||
          font-size 20px
 | 
			
		||||
          color #ffffff
 | 
			
		||||
          color var(--theme-text-color-primary)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
.el-overlay-dialog {
 | 
			
		||||
  .el-dialog {
 | 
			
		||||
    background-color #1a1b1e
 | 
			
		||||
    // background-color #1a1b1e
 | 
			
		||||
 | 
			
		||||
    .el-dialog__header {
 | 
			
		||||
      .el-dialog__title {
 | 
			
		||||
@@ -33,14 +33,15 @@
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .task-info {
 | 
			
		||||
          background-color #25262b
 | 
			
		||||
          // background-color #25262b
 | 
			
		||||
          padding 1rem 1.5rem
 | 
			
		||||
         
 | 
			
		||||
 | 
			
		||||
          .info-line {
 | 
			
		||||
            width 100%
 | 
			
		||||
 | 
			
		||||
            .prompt {
 | 
			
		||||
              background-color #35363b
 | 
			
		||||
              // background-color #35363b
 | 
			
		||||
              padding 10px
 | 
			
		||||
              color #999999
 | 
			
		||||
              overflow auto
 | 
			
		||||
@@ -64,16 +65,16 @@
 | 
			
		||||
              label {
 | 
			
		||||
                display flex
 | 
			
		||||
                width 100px
 | 
			
		||||
                color #a5a5a5
 | 
			
		||||
                color :var(--text-fb)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .item-value {
 | 
			
		||||
                display flex
 | 
			
		||||
                width 100%
 | 
			
		||||
                background-color #35363b
 | 
			
		||||
                // background-color #35363b
 | 
			
		||||
                padding 2px 5px
 | 
			
		||||
                border-radius 5px
 | 
			
		||||
                color #F5F5F5
 | 
			
		||||
                color: var(--text-theme-color);
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,16 @@
 | 
			
		||||
.page-suno {
 | 
			
		||||
  display flex
 | 
			
		||||
  height 100%
 | 
			
		||||
  background-color #0E0808
 | 
			
		||||
  // background-color #0E0808
 | 
			
		||||
  overflow auto
 | 
			
		||||
 | 
			
		||||
  .item-group{
 | 
			
		||||
    scrollbar-width: auto !important; /* 恢复滚动条(Firefox) */
 | 
			
		||||
  -ms-overflow-style: auto !important; /* 恢复滚动条(IE、Edge) */
 | 
			
		||||
  ::-webkit-scrollbar {
 | 
			
		||||
    display: block !important; 
 | 
			
		||||
  }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
  .left-bar {
 | 
			
		||||
    max-width 340px
 | 
			
		||||
    min-width 340px
 | 
			
		||||
@@ -13,6 +20,7 @@
 | 
			
		||||
      display flex
 | 
			
		||||
      flex-flow row
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
      align-items center
 | 
			
		||||
 | 
			
		||||
      .upload-music {
 | 
			
		||||
        .iconfont {
 | 
			
		||||
@@ -24,7 +32,7 @@
 | 
			
		||||
 | 
			
		||||
    .params {
 | 
			
		||||
      padding 20px 0
 | 
			
		||||
      color rgb(250 247 245)
 | 
			
		||||
      color: var(--text-theme-color);
 | 
			
		||||
      position relative
 | 
			
		||||
 | 
			
		||||
      .pure-music {
 | 
			
		||||
@@ -77,6 +85,7 @@
 | 
			
		||||
            opacity: 0.9;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        .song {
 | 
			
		||||
          display flex
 | 
			
		||||
@@ -137,6 +146,8 @@
 | 
			
		||||
          bottom 10px
 | 
			
		||||
          font-size 12px
 | 
			
		||||
          padding 2px 5px
 | 
			
		||||
          background-color var(--sm-btn-bg)
 | 
			
		||||
          color: #fff
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -144,8 +155,14 @@
 | 
			
		||||
        position relative
 | 
			
		||||
        overflow-x auto
 | 
			
		||||
        overflow-y hidden
 | 
			
		||||
        scrollbar-width: auto !important; /* 恢复滚动条(Firefox) */
 | 
			
		||||
        -ms-overflow-style: auto !important; /* 恢复滚动条(IE、Edge) */
 | 
			
		||||
        width 100%
 | 
			
		||||
 | 
			
		||||
        ::-webkit-scrollbar {
 | 
			
		||||
          display: block !important; 
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .inner {
 | 
			
		||||
          display flex
 | 
			
		||||
          flex-flow row
 | 
			
		||||
@@ -154,12 +171,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,9 +190,11 @@
 | 
			
		||||
    width 100%
 | 
			
		||||
    color rgb(250 247 245)
 | 
			
		||||
    overflow auto
 | 
			
		||||
    background: var(--chat-bg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    .list-box {
 | 
			
		||||
      padding 0 0 0 20px
 | 
			
		||||
      padding 20px
 | 
			
		||||
      .item {
 | 
			
		||||
        display flex
 | 
			
		||||
        flex-flow row
 | 
			
		||||
@@ -180,7 +203,7 @@
 | 
			
		||||
        margin-bottom 10px
 | 
			
		||||
 | 
			
		||||
        &:hover {
 | 
			
		||||
          background-color #2A2525
 | 
			
		||||
         background: rgba(188,149,236,0.08)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .left {
 | 
			
		||||
@@ -247,18 +270,18 @@
 | 
			
		||||
            font-weight 700
 | 
			
		||||
 | 
			
		||||
            a {
 | 
			
		||||
              color rgb(250 247 245)
 | 
			
		||||
              color var( --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 +294,7 @@
 | 
			
		||||
 | 
			
		||||
          .tags {
 | 
			
		||||
            font-size 14px
 | 
			
		||||
            color #d1d1d1
 | 
			
		||||
            color var(--text-fb)
 | 
			
		||||
            padding 3px 0
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
@@ -279,11 +302,13 @@
 | 
			
		||||
        .right {
 | 
			
		||||
          min-width 350px;
 | 
			
		||||
          font-size 14px
 | 
			
		||||
          padding 0 15px
 | 
			
		||||
          padding 0 0 0 15px
 | 
			
		||||
          display flex
 | 
			
		||||
          justify-content right
 | 
			
		||||
 | 
			
		||||
          .tools {
 | 
			
		||||
            display flex
 | 
			
		||||
            justify-content left
 | 
			
		||||
            justify-content right
 | 
			
		||||
            align-items center
 | 
			
		||||
            flex-flow row
 | 
			
		||||
            height 90px
 | 
			
		||||
@@ -291,20 +316,21 @@
 | 
			
		||||
            .btn-publish {
 | 
			
		||||
              padding 2px 10px
 | 
			
		||||
 | 
			
		||||
              .text {
 | 
			
		||||
                margin-right 10px
 | 
			
		||||
              }
 | 
			
		||||
              // .text {
 | 
			
		||||
              //   margin-right 10px
 | 
			
		||||
              // }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            .btn-icon {
 | 
			
		||||
              background none
 | 
			
		||||
              padding 6px
 | 
			
		||||
              transition background 0.6s ease 0s
 | 
			
		||||
              color #726E6C
 | 
			
		||||
              color #919191
 | 
			
		||||
 | 
			
		||||
              &:hover {
 | 
			
		||||
                background #5f5958
 | 
			
		||||
                color #e1e1e1
 | 
			
		||||
                // background #5f5958
 | 
			
		||||
                // color #e1e1e1
 | 
			
		||||
                color:var(--el-color-primary)
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              .downloading {
 | 
			
		||||
@@ -356,7 +382,7 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .pagination {
 | 
			
		||||
      padding 10px 20px
 | 
			
		||||
      margin-top 20px
 | 
			
		||||
      display flex
 | 
			
		||||
      justify-content center
 | 
			
		||||
    }
 | 
			
		||||
@@ -372,14 +398,26 @@
 | 
			
		||||
 | 
			
		||||
  .btn {
 | 
			
		||||
    margin-right 10px
 | 
			
		||||
    background-color #363030
 | 
			
		||||
    color: var((--theme-text-color-primary))
 | 
			
		||||
    border none
 | 
			
		||||
    border-radius 5px
 | 
			
		||||
    padding 5px 10px
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    background: var(--btn-bg)
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color #5F5958
 | 
			
		||||
      opacity :0.8
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.submit-btn {
 | 
			
		||||
  display flex
 | 
			
		||||
  align-items: center
 | 
			
		||||
  margin: 20px 0
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  .el-button {
 | 
			
		||||
    width 200px
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								web/src/assets/css/tailwind.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
@tailwind base;
 | 
			
		||||
@tailwind components;
 | 
			
		||||
@tailwind utilities;
 | 
			
		||||
							
								
								
									
										88
									
								
								web/src/assets/css/theme-dark.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,88 @@
 | 
			
		||||
 | 
			
		||||
@import 'font.styl'
 | 
			
		||||
:root[data-theme="dark"]{
 | 
			
		||||
  --text-fb:#fff;
 | 
			
		||||
  --text-color: rgba(255, 255, 255, 1) !important; // 主要的文本颜色
 | 
			
		||||
  --normal-color: rgba(163, 174, 208, 1); // 普通颜色
 | 
			
		||||
  --el-text-color-primary: #fff;
 | 
			
		||||
  p, h1, h2, h3, h4, h5, h6, article {
 | 
			
		||||
    // color: var(--text-color) !important;
 | 
			
		||||
     font-family: $font-regular;
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
  html,
 | 
			
		||||
  body,
 | 
			
		||||
  #app,
 | 
			
		||||
  .wrapper {
 | 
			
		||||
    background: rgb(13, 20, 53) 
 | 
			
		||||
    background-color: rgb(13, 20, 53) 
 | 
			
		||||
    font-family: $font-regular;
 | 
			
		||||
  }
 | 
			
		||||
  --btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
 | 
			
		||||
  --border-active:rgba(255, 255, 255, 0.1);
 | 
			
		||||
  --card-bg:#252d58;
 | 
			
		||||
  --chat-bg:#1f243f
 | 
			
		||||
  --chat-wel-bg:#2d2f38;
 | 
			
		||||
  --card-bg-table: rgba(17, 28, 68, 1);
 | 
			
		||||
  --theme-bg:rgb(13, 20, 53);
 | 
			
		||||
  --theme-bg-color: rgb(13, 20, 53);
 | 
			
		||||
  --theme-bg-all:rgb(13, 20, 53);
 | 
			
		||||
  --sign-bg: rgba(27, 37, 75, 1);
 | 
			
		||||
  --text-theme-color: #fff;
 | 
			
		||||
  --text-color-primary: #d1c7ff;
 | 
			
		||||
  --theme-text-color-secondary: #a3aed0;
 | 
			
		||||
  --theme-text-color-primary: #fff;
 | 
			
		||||
  --theme-text-primary: #f3f3f3;
 | 
			
		||||
  --line-box:rgba(255, 255, 255, 0.1);
 | 
			
		||||
  --el-bg-color:#141a36;
 | 
			
		||||
  --el-fill-color-blank: rgba(17, 28, 68, 1);
 | 
			
		||||
  --el-fill-color-light: rgba(86, 86, 95, .2);
 | 
			
		||||
  --el-color-primary-light-9:rgba(86, 86, 95, .2);
 | 
			
		||||
  --el-text-color-regular: rgba(163, 174, 208, 1)
 | 
			
		||||
  --el-border-color:rgb(79, 80, 85);//黑白切换
 | 
			
		||||
  --el-bg-color-overlay: rgba(17, 28, 68, 1);
 | 
			
		||||
  --el-border-color-light: rgba(255, 255, 255, 0.2);
 | 
			
		||||
  --chat-content-bg:rgba(86, 86, 95, .2);
 | 
			
		||||
  --chat-content-bg-list:rgba(86, 86, 95, .2);
 | 
			
		||||
  --hover-deep-color:#30323c;
 | 
			
		||||
   //layout 
 | 
			
		||||
  .more-menus li.moreTitle,
 | 
			
		||||
  .twoTittle .title,
 | 
			
		||||
  .setting-menus span.title,
 | 
			
		||||
  .setting-menus li .el-icon,
 | 
			
		||||
  .setting-menus li .iconfont,
 | 
			
		||||
  .layout .tab-box .menu-list-item{
 | 
			
		||||
    filter: invert(100%);
 | 
			
		||||
  }
 | 
			
		||||
  .more-menus span.title{
 | 
			
		||||
    color:#000;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 操作按钮
 | 
			
		||||
  --btn-bg: rgba(86, 86, 95, .5);
 | 
			
		||||
 | 
			
		||||
  .el-table {
 | 
			
		||||
    // 表格表头背景
 | 
			
		||||
    --el-fill-color-darker: rgba(100, 100, 100, .5);
 | 
			
		||||
    --el-border-color-darker: #73767a;
 | 
			
		||||
    --el-table-border-color: rgba(100, 100, 100, .5);
 | 
			
		||||
    --el-table-row-hover-bg-color: rgba(16, 21, 43, .8);
 | 
			
		||||
    --el-table-current-row-bg-color: rgba(16, 21, 43, .8);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 加载动画
 | 
			
		||||
  --el-mask-color: rgba(255, 255, 255, 0.5);
 | 
			
		||||
  --van-toast-background: rgba(255, 255, 255, 0.3);
 | 
			
		||||
 | 
			
		||||
  --code-bg-color: #424242;
 | 
			
		||||
  --code-text-color: #fff;
 | 
			
		||||
 | 
			
		||||
  // vant 主题样式
 | 
			
		||||
  --van-cell-background: #141a36;
 | 
			
		||||
  --van-cell-background-light: #242a46;
 | 
			
		||||
  --van-button-default-background: #141a36;
 | 
			
		||||
  --van-background: #141a36;
 | 
			
		||||
  --van-tabbar-background: #141a36;
 | 
			
		||||
  --van-nav-bar-background: #1B244A;
 | 
			
		||||
  --van-dropdown-menu-background: #141a36;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								web/src/assets/css/theme-light.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,53 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@import 'font.styl'
 | 
			
		||||
:root[data-theme="light"] {
 | 
			
		||||
  --text-fb:#000;
 | 
			
		||||
  --text-color: #5b62ce; // 主要的文本颜色
 | 
			
		||||
  --normal-color: rgba(43, 54, 116, 1); // 普通颜色
 | 
			
		||||
  p, h1, h2, h3, h4, h5, h6, article {
 | 
			
		||||
    font-family: $font-regular;
 | 
			
		||||
  }
 | 
			
		||||
  html,
 | 
			
		||||
  body,
 | 
			
		||||
  #app,
 | 
			
		||||
  .wrapper {
 | 
			
		||||
    font-family: $font-regular;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  --btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
 | 
			
		||||
  --border-active:rgba(134, 140, 255, 1);
 | 
			
		||||
  --code-btnColor: linear-gradient(88deg, #af61f0 1.44%, #5b62ce);
 | 
			
		||||
  --card-bg:#fff;
 | 
			
		||||
  --chat-bg:#fff;
 | 
			
		||||
  --theme-bg:linear-gradient(88deg, #fff3f3 1.44%, #e7e8ff);
 | 
			
		||||
  --theme-bg-all:#f5f7fd;
 | 
			
		||||
  --theme-bg-color: #f5f7fd;
 | 
			
		||||
  --sign-bg: rgba(244, 247, 254, 1);
 | 
			
		||||
  --text-theme-color: rgba(43, 54, 116, 1)
 | 
			
		||||
  --text-color-primary: rgba(67, 24, 255, 1);
 | 
			
		||||
  --line-box:rgba(79, 89, 102, 0.122);
 | 
			
		||||
  --theme-text-color-primary: #000;
 | 
			
		||||
  --theme-text-primary: #000;
 | 
			
		||||
  --theme-text-color-secondary: #666;
 | 
			
		||||
  --chat-content-bg:#f5f7fc;
 | 
			
		||||
  --chat-list-bg: #0302020a;
 | 
			
		||||
  --chat-content-bg-list:#fff;
 | 
			
		||||
  --chat-wel-bg:rgba(247, 247, 248, 1);
 | 
			
		||||
  --hover-deep-color:#fff;
 | 
			
		||||
  --el-bg-color-overlay: #fff;
 | 
			
		||||
  --el-bg-color:#fff;
 | 
			
		||||
  --el-fill-color-blank: #fff;
 | 
			
		||||
  --el-pagination-button-bg-color: rgba(86,86,95,0.2);
 | 
			
		||||
 | 
			
		||||
  // 操作按钮
 | 
			
		||||
  --btn-bg: rgba(100, 100, 100, .1);
 | 
			
		||||
 | 
			
		||||
  // 加载动画
 | 
			
		||||
  --el-mask-color: rgba(100, 100, 100, 0.2);
 | 
			
		||||
  // code 标签背景
 | 
			
		||||
  --code-bg-color: #ececec;
 | 
			
		||||
  --code-text-color: var(--el-color-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -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/fonts/OPlusSans3-Medium.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								web/src/assets/fonts/OPlusSans3-Regular.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -1,8 +1,8 @@
 | 
			
		||||
@font-face {
 | 
			
		||||
  font-family: "iconfont"; /* Project id 4125778 */
 | 
			
		||||
  src: url('iconfont.woff2?t=1731289567907') format('woff2'),
 | 
			
		||||
       url('iconfont.woff?t=1731289567907') format('woff'),
 | 
			
		||||
       url('iconfont.ttf?t=1731289567907') format('truetype');
 | 
			
		||||
  src: url('iconfont.woff2?t=1734934068681') format('woff2'),
 | 
			
		||||
       url('iconfont.woff?t=1734934068681') format('woff'),
 | 
			
		||||
       url('iconfont.ttf?t=1734934068681') format('truetype');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.iconfont {
 | 
			
		||||
@@ -13,6 +13,154 @@
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-redeem:before {
 | 
			
		||||
  content: "\e61a";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-login:before {
 | 
			
		||||
  content: "\e636";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-present:before {
 | 
			
		||||
  content: "\e648";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-icon-warning:before {
 | 
			
		||||
  content: "\e671";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-help:before {
 | 
			
		||||
  content: "\e64a";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-success:before {
 | 
			
		||||
  content: "\e61e";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-error:before {
 | 
			
		||||
  content: "\e64e";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-house:before {
 | 
			
		||||
  content: "\e619";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-vip4:before {
 | 
			
		||||
  content: "\e684";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-vip1:before {
 | 
			
		||||
  content: "\f90b";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-vip2:before {
 | 
			
		||||
  content: "\fabb";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-vip3:before {
 | 
			
		||||
  content: "\10135";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-conversation:before {
 | 
			
		||||
  content: "\e617";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-arrow-down:before {
 | 
			
		||||
  content: "\e615";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-arrow-up:before {
 | 
			
		||||
  content: "\e616";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-refresh:before {
 | 
			
		||||
  content: "\e90c";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-refresh-bold:before {
 | 
			
		||||
  content: "\e614";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-copy:before {
 | 
			
		||||
  content: "\e720";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-new-chat:before {
 | 
			
		||||
  content: "\e613";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-expand:before {
 | 
			
		||||
  content: "\e7a0";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-colspan:before {
 | 
			
		||||
  content: "\e79e";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-question:before {
 | 
			
		||||
  content: "\e8e9";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-AIduihua_jihuo:before {
 | 
			
		||||
  content: "\e6bb";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-MidJourney:before {
 | 
			
		||||
  content: "\e60e";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-stable-diffusion:before {
 | 
			
		||||
  content: "\e60f";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-info:before {
 | 
			
		||||
  content: "\e6a0";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-more-horizontal:before {
 | 
			
		||||
  content: "\e60d";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-xinghao:before {
 | 
			
		||||
  content: "\e8d6";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-plus:before {
 | 
			
		||||
  content: "\e61f";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-plus-circle:before {
 | 
			
		||||
  content: "\e822";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-taiyang:before {
 | 
			
		||||
  content: "\e60b";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-yueliang:before {
 | 
			
		||||
  content: "\e679";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-prev-page:before {
 | 
			
		||||
  content: "\e8ef";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-next-page:before {
 | 
			
		||||
  content: "\e8f0";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-search:before {
 | 
			
		||||
  content: "\e618";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-sub_menu:before {
 | 
			
		||||
  content: "\e75e";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-google:before {
 | 
			
		||||
  content: "\ea0c";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.icon-linggan:before {
 | 
			
		||||
  content: "\e641";
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,265 @@
 | 
			
		||||
  "css_prefix_text": "icon-",
 | 
			
		||||
  "description": "",
 | 
			
		||||
  "glyphs": [
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "3624396",
 | 
			
		||||
      "name": "兑换码",
 | 
			
		||||
      "font_class": "redeem",
 | 
			
		||||
      "unicode": "e61a",
 | 
			
		||||
      "unicode_decimal": 58906
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "8760110",
 | 
			
		||||
      "name": "login",
 | 
			
		||||
      "font_class": "login",
 | 
			
		||||
      "unicode": "e636",
 | 
			
		||||
      "unicode_decimal": 58934
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "29565277",
 | 
			
		||||
      "name": "礼物",
 | 
			
		||||
      "font_class": "present",
 | 
			
		||||
      "unicode": "e648",
 | 
			
		||||
      "unicode_decimal": 58952
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "13519527",
 | 
			
		||||
      "name": "警告",
 | 
			
		||||
      "font_class": "icon-warning",
 | 
			
		||||
      "unicode": "e671",
 | 
			
		||||
      "unicode_decimal": 58993
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "145466",
 | 
			
		||||
      "name": "帮助",
 | 
			
		||||
      "font_class": "help",
 | 
			
		||||
      "unicode": "e64a",
 | 
			
		||||
      "unicode_decimal": 58954
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1951950",
 | 
			
		||||
      "name": "成功",
 | 
			
		||||
      "font_class": "success",
 | 
			
		||||
      "unicode": "e61e",
 | 
			
		||||
      "unicode_decimal": 58910
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "6204756",
 | 
			
		||||
      "name": "失败",
 | 
			
		||||
      "font_class": "error",
 | 
			
		||||
      "unicode": "e64e",
 | 
			
		||||
      "unicode_decimal": 58958
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "3916695",
 | 
			
		||||
      "name": "首页",
 | 
			
		||||
      "font_class": "house",
 | 
			
		||||
      "unicode": "e619",
 | 
			
		||||
      "unicode_decimal": 58905
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "10583159",
 | 
			
		||||
      "name": "会员",
 | 
			
		||||
      "font_class": "vip4",
 | 
			
		||||
      "unicode": "e684",
 | 
			
		||||
      "unicode_decimal": 59012
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "23942994",
 | 
			
		||||
      "name": "会员",
 | 
			
		||||
      "font_class": "vip1",
 | 
			
		||||
      "unicode": "f90b",
 | 
			
		||||
      "unicode_decimal": 63755
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "24111538",
 | 
			
		||||
      "name": "会员",
 | 
			
		||||
      "font_class": "vip2",
 | 
			
		||||
      "unicode": "fabb",
 | 
			
		||||
      "unicode_decimal": 64187
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "37305926",
 | 
			
		||||
      "name": "会员VIP",
 | 
			
		||||
      "font_class": "vip3",
 | 
			
		||||
      "unicode": "10135",
 | 
			
		||||
      "unicode_decimal": 65845
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "41134022",
 | 
			
		||||
      "name": "新会话",
 | 
			
		||||
      "font_class": "conversation",
 | 
			
		||||
      "unicode": "e617",
 | 
			
		||||
      "unicode_decimal": 58903
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "17411805",
 | 
			
		||||
      "name": "Arrow Down",
 | 
			
		||||
      "font_class": "arrow-down",
 | 
			
		||||
      "unicode": "e615",
 | 
			
		||||
      "unicode_decimal": 58901
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "17411857",
 | 
			
		||||
      "name": "Arrow Up",
 | 
			
		||||
      "font_class": "arrow-up",
 | 
			
		||||
      "unicode": "e616",
 | 
			
		||||
      "unicode_decimal": 58902
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "7736305",
 | 
			
		||||
      "name": "refresh",
 | 
			
		||||
      "font_class": "refresh",
 | 
			
		||||
      "unicode": "e90c",
 | 
			
		||||
      "unicode_decimal": 59660
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1391302",
 | 
			
		||||
      "name": "Refresh",
 | 
			
		||||
      "font_class": "refresh-bold",
 | 
			
		||||
      "unicode": "e614",
 | 
			
		||||
      "unicode_decimal": 58900
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "19418384",
 | 
			
		||||
      "name": "copy",
 | 
			
		||||
      "font_class": "copy",
 | 
			
		||||
      "unicode": "e720",
 | 
			
		||||
      "unicode_decimal": 59168
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "20584689",
 | 
			
		||||
      "name": "New Chat",
 | 
			
		||||
      "font_class": "new-chat",
 | 
			
		||||
      "unicode": "e613",
 | 
			
		||||
      "unicode_decimal": 58899
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "23995596",
 | 
			
		||||
      "name": "收起展开-展开",
 | 
			
		||||
      "font_class": "expand",
 | 
			
		||||
      "unicode": "e7a0",
 | 
			
		||||
      "unicode_decimal": 59296
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "23995626",
 | 
			
		||||
      "name": "收起展开-收起",
 | 
			
		||||
      "font_class": "colspan",
 | 
			
		||||
      "unicode": "e79e",
 | 
			
		||||
      "unicode_decimal": 59294
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1727527",
 | 
			
		||||
      "name": "306问号-线性圆框",
 | 
			
		||||
      "font_class": "question",
 | 
			
		||||
      "unicode": "e8e9",
 | 
			
		||||
      "unicode_decimal": 59625
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "35446270",
 | 
			
		||||
      "name": "AI对话_激活",
 | 
			
		||||
      "font_class": "AIduihua_jihuo",
 | 
			
		||||
      "unicode": "e6bb",
 | 
			
		||||
      "unicode_decimal": 59067
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "39584617",
 | 
			
		||||
      "name": "MidJourney-copy",
 | 
			
		||||
      "font_class": "MidJourney",
 | 
			
		||||
      "unicode": "e60e",
 | 
			
		||||
      "unicode_decimal": 58894
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "42109955",
 | 
			
		||||
      "name": "stable-diffusion",
 | 
			
		||||
      "font_class": "stable-diffusion",
 | 
			
		||||
      "unicode": "e60f",
 | 
			
		||||
      "unicode_decimal": 58895
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1227734",
 | 
			
		||||
      "name": "info",
 | 
			
		||||
      "font_class": "info",
 | 
			
		||||
      "unicode": "e6a0",
 | 
			
		||||
      "unicode_decimal": 59040
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "159969",
 | 
			
		||||
      "name": "more",
 | 
			
		||||
      "font_class": "more-horizontal",
 | 
			
		||||
      "unicode": "e60d",
 | 
			
		||||
      "unicode_decimal": 58893
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "8434022",
 | 
			
		||||
      "name": "星号",
 | 
			
		||||
      "font_class": "xinghao",
 | 
			
		||||
      "unicode": "e8d6",
 | 
			
		||||
      "unicode_decimal": 59606
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "831577",
 | 
			
		||||
      "name": "plus",
 | 
			
		||||
      "font_class": "plus",
 | 
			
		||||
      "unicode": "e61f",
 | 
			
		||||
      "unicode_decimal": 58911
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "6151285",
 | 
			
		||||
      "name": "plus-circle",
 | 
			
		||||
      "font_class": "plus-circle",
 | 
			
		||||
      "unicode": "e822",
 | 
			
		||||
      "unicode_decimal": 59426
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "15056491",
 | 
			
		||||
      "name": "太阳",
 | 
			
		||||
      "font_class": "taiyang",
 | 
			
		||||
      "unicode": "e60b",
 | 
			
		||||
      "unicode_decimal": 58891
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "40094190",
 | 
			
		||||
      "name": "月亮-copy",
 | 
			
		||||
      "font_class": "yueliang",
 | 
			
		||||
      "unicode": "e679",
 | 
			
		||||
      "unicode_decimal": 59001
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1727538",
 | 
			
		||||
      "name": "上一页",
 | 
			
		||||
      "font_class": "prev-page",
 | 
			
		||||
      "unicode": "e8ef",
 | 
			
		||||
      "unicode_decimal": 59631
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "1727540",
 | 
			
		||||
      "name": "下一页",
 | 
			
		||||
      "font_class": "next-page",
 | 
			
		||||
      "unicode": "e8f0",
 | 
			
		||||
      "unicode_decimal": 59632
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "2488134",
 | 
			
		||||
      "name": "搜索",
 | 
			
		||||
      "font_class": "search",
 | 
			
		||||
      "unicode": "e618",
 | 
			
		||||
      "unicode_decimal": 58904
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "9845558",
 | 
			
		||||
      "name": "sub_menu",
 | 
			
		||||
      "font_class": "sub_menu",
 | 
			
		||||
      "unicode": "e75e",
 | 
			
		||||
      "unicode_decimal": 59230
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "11983544",
 | 
			
		||||
      "name": "google",
 | 
			
		||||
      "font_class": "google",
 | 
			
		||||
      "unicode": "ea0c",
 | 
			
		||||
      "unicode_decimal": 59916
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "icon_id": "15330210",
 | 
			
		||||
      "name": "创意灵感",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								web/src/assets/img/371731933156_.pic.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 64 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								web/src/assets/img/avatar.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 17 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								web/src/assets/img/login-bg.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 824 KiB  | 
| 
		 Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								web/src/assets/img/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 75 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								web/src/assets/img/no-data.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 39 KiB  | 
							
								
								
									
										60
									
								
								web/src/components/AccountBg.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,60 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="right flex-center">
 | 
			
		||||
    <div class="logo">
 | 
			
		||||
      <img src="@/assets/img/logo.png" alt="" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>welcome</div>
 | 
			
		||||
    <footer-bar />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import FooterBar from "@/components/FooterBar.vue";
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.right{
 | 
			
		||||
  font-size: 40px
 | 
			
		||||
  font-weight: bold
 | 
			
		||||
  color:#fff
 | 
			
		||||
  flex-direction: column
 | 
			
		||||
  background-image url("~@/assets/img/login-bg.png")
 | 
			
		||||
  background-size cover
 | 
			
		||||
  background-position center
 | 
			
		||||
  width: 50%;
 | 
			
		||||
  min-height: 100vh
 | 
			
		||||
  max-height: 100vh
 | 
			
		||||
  background-repeat: no-repeat;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
  :deep(.foot-container){
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    bottom: 20px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background: none;
 | 
			
		||||
    color: var(--sm-txt);
 | 
			
		||||
    font-size: 12px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
 | 
			
		||||
    .footer{
 | 
			
		||||
      a,
 | 
			
		||||
      span{
 | 
			
		||||
        color: var(--text-fff)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.logo{
 | 
			
		||||
  margin-bottom: 26px;
 | 
			
		||||
  width: 200px
 | 
			
		||||
  height: 200px
 | 
			
		||||
  background: #fff
 | 
			
		||||
  border-radius: 50%
 | 
			
		||||
  img{
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    object-fit: cover;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										101
									
								
								web/src/components/AccountTop.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,101 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <ThemeChange />
 | 
			
		||||
    <div
 | 
			
		||||
      @click="goBack"
 | 
			
		||||
      class="flex back animate__animated animate__pulse animate__infinite"
 | 
			
		||||
    >
 | 
			
		||||
      <el-icon><ArrowLeftBold /></el-icon
 | 
			
		||||
      >{{ title === "注册" ? "首页" : "返回" }}
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="title">{{ title }}</div>
 | 
			
		||||
    <div class="smTitle" v-if="title !== '重置密码'">
 | 
			
		||||
      {{ title === "登录" ? "没有账号?" : "已有账号?"
 | 
			
		||||
      }}<span @click="goPageFun" class="text-color-primary sign"
 | 
			
		||||
        >赶紧{{ title === "登录" ? "注册" : "登录" }}</span
 | 
			
		||||
      >
 | 
			
		||||
    </div>
 | 
			
		||||
    <slot></slot>
 | 
			
		||||
    <div class="flex orline" v-if="title !== '重置密码'">
 | 
			
		||||
      <div class="lineor"></div>
 | 
			
		||||
      <span>或</span>
 | 
			
		||||
      <div class="lineor"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ArrowLeftBold } from "@element-plus/icons-vue";
 | 
			
		||||
import ThemeChange from "@/components/ThemeChange.vue";
 | 
			
		||||
import { defineProps } from "vue";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: "登录"
 | 
			
		||||
  },
 | 
			
		||||
  smTitle: { type: String, default: "没有账号?" },
 | 
			
		||||
  goPage: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: "/register"
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const goBack = () => {
 | 
			
		||||
  if (props.title === "注册") {
 | 
			
		||||
    router.push("/");
 | 
			
		||||
  } else {
 | 
			
		||||
    router.go(-1);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
const goPageFun = () => {
 | 
			
		||||
  if (props.title === "登录") {
 | 
			
		||||
    router.push("/register");
 | 
			
		||||
  } else {
 | 
			
		||||
    router.push("/login");
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.back{
 | 
			
		||||
   color:var(--sm-txt)
 | 
			
		||||
   font-size: 14px;
 | 
			
		||||
   margin-bottom: 140px
 | 
			
		||||
   margin-top: 18px
 | 
			
		||||
   cursor: pointer
 | 
			
		||||
   .el-icon{
 | 
			
		||||
     margin-right: 6px
 | 
			
		||||
   }
 | 
			
		||||
 }
 | 
			
		||||
 .title{
 | 
			
		||||
   font-size: 36px
 | 
			
		||||
   margin-bottom: 16px
 | 
			
		||||
   color: var(--text-color)
 | 
			
		||||
 | 
			
		||||
 }
 | 
			
		||||
 .smTitle{
 | 
			
		||||
    color: var(--text-color)
 | 
			
		||||
   font-size: 14px;
 | 
			
		||||
   margin-bottom: 36px
 | 
			
		||||
 }
 | 
			
		||||
 .sign{
 | 
			
		||||
    text-decoration: underline;
 | 
			
		||||
    cursor :pointer
 | 
			
		||||
  }
 | 
			
		||||
  .orline{
 | 
			
		||||
    color:var(--text-secondary)
 | 
			
		||||
    span{
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      margin: 0 10px
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
    .lineor{
 | 
			
		||||
 | 
			
		||||
      width: 182px; height: 1px;
 | 
			
		||||
      background: var(--text-secondary)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,19 +1,28 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <button v-if="showButton" @click="scrollToTop" class="scroll-to-top" :style="{bottom: bottom + 'px', right: right + 'px', backgroundColor: bgColor}">
 | 
			
		||||
  <button
 | 
			
		||||
    v-if="showButton"
 | 
			
		||||
    @click="scrollToTop"
 | 
			
		||||
    class="scroll-to-top"
 | 
			
		||||
    :style="{
 | 
			
		||||
      bottom: bottom + 'px',
 | 
			
		||||
      right: right + 'px',
 | 
			
		||||
      backgroundColor: bgColor
 | 
			
		||||
    }"
 | 
			
		||||
  >
 | 
			
		||||
    <el-icon><ArrowUpBold /></el-icon>
 | 
			
		||||
  </button>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import {ArrowUpBold} from "@element-plus/icons-vue";
 | 
			
		||||
import { ArrowUpBold } from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'BackTop',
 | 
			
		||||
  components: {ArrowUpBold},
 | 
			
		||||
  name: "BackTop",
 | 
			
		||||
  components: { ArrowUpBold },
 | 
			
		||||
  props: {
 | 
			
		||||
    bottom: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 30
 | 
			
		||||
      default: 155
 | 
			
		||||
    },
 | 
			
		||||
    right: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
@@ -21,7 +30,7 @@ export default {
 | 
			
		||||
    },
 | 
			
		||||
    bgColor: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '#007bff'
 | 
			
		||||
      default: "#b6aaf9"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
@@ -31,19 +40,19 @@ export default {
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.checkScroll();
 | 
			
		||||
    window.addEventListener('resize', this.checkScroll);
 | 
			
		||||
    this.$el.parentElement.addEventListener('scroll', this.checkScroll);
 | 
			
		||||
    window.addEventListener("resize", this.checkScroll);
 | 
			
		||||
    this.$el.parentElement.addEventListener("scroll", this.checkScroll);
 | 
			
		||||
  },
 | 
			
		||||
  beforeUnmount() {
 | 
			
		||||
    window.removeEventListener('resize', this.checkScroll);
 | 
			
		||||
    this.$el.parentElement.removeEventListener('scroll', this.checkScroll);
 | 
			
		||||
    window.removeEventListener("resize", this.checkScroll);
 | 
			
		||||
    this.$el.parentElement.removeEventListener("scroll", this.checkScroll);
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    scrollToTop() {
 | 
			
		||||
      const container = this.$el.parentElement;
 | 
			
		||||
      container.scrollTo({
 | 
			
		||||
        top: 0,
 | 
			
		||||
        behavior: 'smooth'
 | 
			
		||||
        behavior: "smooth"
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
    checkScroll() {
 | 
			
		||||
@@ -51,7 +60,7 @@ export default {
 | 
			
		||||
      this.showButton = container.scrollTop > 50;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
@@ -63,15 +72,15 @@ export default {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  outline: none;
 | 
			
		||||
  transition: opacity 0.3s;
 | 
			
		||||
  width 40px
 | 
			
		||||
  height 40px
 | 
			
		||||
  width 30px
 | 
			
		||||
  height 30px
 | 
			
		||||
  display flex
 | 
			
		||||
  justify-content center
 | 
			
		||||
  align-items center
 | 
			
		||||
  font-size 20px
 | 
			
		||||
  font-size 18px
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    opacity: 0.6;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@
 | 
			
		||||
              <el-input v-model="form.code" maxlength="6"/>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="8" style="padding-left: 10px">
 | 
			
		||||
              <send-msg :receiver="form.mobile" type="mobile"/>
 | 
			
		||||
              <send-msg :receiver="form.mobile" size="default" type="mobile"/>
 | 
			
		||||
            </el-col>
 | 
			
		||||
          </el-row>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,130 +1,138 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container class="captcha-box">
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="show"
 | 
			
		||||
        :close-on-click-modal="true"
 | 
			
		||||
        :show-close="false"
 | 
			
		||||
        style="width: 360px;"
 | 
			
		||||
    >
 | 
			
		||||
    <el-dialog v-model="show" :close-on-click-modal="true" :show-close="isMobileInternal" style="width: 360px; --el-dialog-padding-primary: 5px 15px 15px 15px">
 | 
			
		||||
      <template #title>
 | 
			
		||||
        <div class="text-center p-3" style="color: var(--el-text-color-primary)" v-if="isMobileInternal">
 | 
			
		||||
          <span>人机验证</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <slide-captcha
 | 
			
		||||
          v-if="isMobile()"
 | 
			
		||||
          :bg-img="bgImg"
 | 
			
		||||
          :bk-img="bkImg"
 | 
			
		||||
          :result="result"
 | 
			
		||||
          @refresh="getSlideCaptcha"
 | 
			
		||||
          @confirm="handleSlideConfirm"
 | 
			
		||||
          @hide="show = false"/>
 | 
			
		||||
        v-if="isMobileInternal"
 | 
			
		||||
        :bg-img="bgImg"
 | 
			
		||||
        :bk-img="bkImg"
 | 
			
		||||
        :result="result"
 | 
			
		||||
        @refresh="getSlideCaptcha"
 | 
			
		||||
        @confirm="handleSlideConfirm"
 | 
			
		||||
        @hide="show = false"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <captcha-plus
 | 
			
		||||
          v-else
 | 
			
		||||
          :max-dot="maxDot"
 | 
			
		||||
          :image-base64="imageBase64"
 | 
			
		||||
          :thumb-base64="thumbBase64"
 | 
			
		||||
          width="300"
 | 
			
		||||
          @close="show = false"
 | 
			
		||||
          @refresh="handleRequestCaptCode"
 | 
			
		||||
          @confirm="handleConfirm"
 | 
			
		||||
        v-else
 | 
			
		||||
        :max-dot="maxDot"
 | 
			
		||||
        :image-base64="imageBase64"
 | 
			
		||||
        :thumb-base64="thumbBase64"
 | 
			
		||||
        width="300"
 | 
			
		||||
        @close="show = false"
 | 
			
		||||
        @refresh="handleRequestCaptCode"
 | 
			
		||||
        @confirm="handleConfirm"
 | 
			
		||||
      />
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
  </el-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import lodash from 'lodash'
 | 
			
		||||
import {validateEmail, validateMobile} from "@/utils/validate";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import lodash from "lodash";
 | 
			
		||||
import { validateEmail, validateMobile } from "@/utils/validate";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import CaptchaPlus from "@/components/CaptchaPlus.vue";
 | 
			
		||||
import SlideCaptcha from "@/components/SlideCaptcha.vue";
 | 
			
		||||
import {isMobile} from "@/utils/libs";
 | 
			
		||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
 | 
			
		||||
import { isMobile } from "@/utils/libs";
 | 
			
		||||
import { showMessageError, showMessageOK } from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const show = ref(false)
 | 
			
		||||
const maxDot = ref(5)
 | 
			
		||||
const imageBase64 = ref('')
 | 
			
		||||
const thumbBase64 = ref('')
 | 
			
		||||
const captKey = ref('')
 | 
			
		||||
const dots = ref(null)
 | 
			
		||||
const show = ref(false);
 | 
			
		||||
const maxDot = ref(5);
 | 
			
		||||
const imageBase64 = ref("");
 | 
			
		||||
const thumbBase64 = ref("");
 | 
			
		||||
const captKey = ref("");
 | 
			
		||||
const dots = ref(null);
 | 
			
		||||
const isMobileInternal = isMobile();
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['success']);
 | 
			
		||||
const emits = defineEmits(["success"]);
 | 
			
		||||
const handleRequestCaptCode = () => {
 | 
			
		||||
 | 
			
		||||
  httpGet('/api/captcha/get').then(res => {
 | 
			
		||||
    const data = res.data
 | 
			
		||||
    imageBase64.value = data.image
 | 
			
		||||
    thumbBase64.value = data.thumb
 | 
			
		||||
    captKey.value = data.key
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError('获取人机验证数据失败:' + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
  httpGet("/api/captcha/get")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      const data = res.data;
 | 
			
		||||
      imageBase64.value = data.image;
 | 
			
		||||
      thumbBase64.value = data.thumb;
 | 
			
		||||
      captKey.value = data.key;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      showMessageError("获取人机验证数据失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleConfirm = (dts) => {
 | 
			
		||||
  if (lodash.size(dts) <= 0) {
 | 
			
		||||
    return showMessageError('请进行人机验证再操作')
 | 
			
		||||
    return showMessageError("请进行人机验证再操作");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let dotArr = []
 | 
			
		||||
  let dotArr = [];
 | 
			
		||||
  lodash.forEach(dts, (dot) => {
 | 
			
		||||
    dotArr.push(dot.x, dot.y)
 | 
			
		||||
  })
 | 
			
		||||
  dots.value = dotArr.join(',')
 | 
			
		||||
  httpPost('/api/captcha/check', {
 | 
			
		||||
    dotArr.push(dot.x, dot.y);
 | 
			
		||||
  });
 | 
			
		||||
  dots.value = dotArr.join(",");
 | 
			
		||||
  httpPost("/api/captcha/check", {
 | 
			
		||||
    dots: dots.value,
 | 
			
		||||
    key: captKey.value
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    // ElMessage.success('人机验证成功')
 | 
			
		||||
    show.value = false
 | 
			
		||||
    emits('success', {key:captKey.value, dots:dots.value})
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    showMessageError('人机验证失败')
 | 
			
		||||
    handleRequestCaptCode()
 | 
			
		||||
    key: captKey.value,
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      // ElMessage.success('人机验证成功')
 | 
			
		||||
      show.value = false;
 | 
			
		||||
      emits("success", { key: captKey.value, dots: dots.value });
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      showMessageError("人机验证失败");
 | 
			
		||||
      handleRequestCaptCode();
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const loadCaptcha = () => {
 | 
			
		||||
  show.value = true
 | 
			
		||||
  show.value = true;
 | 
			
		||||
  // 手机用滑动验证码
 | 
			
		||||
  if (isMobile()) {
 | 
			
		||||
    getSlideCaptcha()
 | 
			
		||||
    getSlideCaptcha();
 | 
			
		||||
  } else {
 | 
			
		||||
    handleRequestCaptCode()
 | 
			
		||||
    handleRequestCaptCode();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 滑动验证码
 | 
			
		||||
const bgImg = ref('')
 | 
			
		||||
const bkImg = ref('')
 | 
			
		||||
const result = ref(0)
 | 
			
		||||
const bgImg = ref("");
 | 
			
		||||
const bkImg = ref("");
 | 
			
		||||
const result = ref(0);
 | 
			
		||||
 | 
			
		||||
const getSlideCaptcha = () => {
 | 
			
		||||
  result.value = 0
 | 
			
		||||
  httpGet("/api/captcha/slide/get").then(res => {
 | 
			
		||||
    bkImg.value = res.data.bkImg
 | 
			
		||||
    bgImg.value = res.data.bgImg
 | 
			
		||||
    captKey.value = res.data.key
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError('获取人机验证数据失败:' + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
  result.value = 0;
 | 
			
		||||
  httpGet("/api/captcha/slide/get")
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      bkImg.value = res.data.bkImg;
 | 
			
		||||
      bgImg.value = res.data.bgImg;
 | 
			
		||||
      captKey.value = res.data.key;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      showMessageError("获取人机验证数据失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleSlideConfirm = (x) => {
 | 
			
		||||
  httpPost("/api/captcha/slide/check", {
 | 
			
		||||
    key: captKey.value,
 | 
			
		||||
    x: x
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    result.value = 1
 | 
			
		||||
    show.value = false
 | 
			
		||||
    emits('success',{key:captKey.value, x:x})
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    result.value = 2
 | 
			
		||||
    x: x,
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      result.value = 1;
 | 
			
		||||
      show.value = false;
 | 
			
		||||
      emits("success", { key: captKey.value, x: x });
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      result.value = 2;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 导出方法以便父组件调用
 | 
			
		||||
defineExpose({
 | 
			
		||||
  loadCaptcha
 | 
			
		||||
  loadCaptcha,
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@@ -137,8 +145,9 @@ defineExpose({
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-dialog__body {
 | 
			
		||||
      padding 0
 | 
			
		||||
      padding-bottom: 5px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,90 +1,93 @@
 | 
			
		||||
<template>
 | 
			
		||||
 | 
			
		||||
  <div class="wg-cap-wrap" :style="{width: width}">
 | 
			
		||||
  <div class="wg-cap-wrap" :style="{ width: width }">
 | 
			
		||||
    <div class="wg-cap-wrap__header">
 | 
			
		||||
      <span>请在下图<em>依次</em>点击:</span>
 | 
			
		||||
      <img class="wg-cap-wrap__thumb" v-if="thumbBase64Code" :src="thumbBase64Code" alt=" ">
 | 
			
		||||
      <img class="wg-cap-wrap__thumb" v-if="thumbBase64Code" :src="thumbBase64Code" alt=" " />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="wg-cap-wrap__body">
 | 
			
		||||
      <img class="wg-cap-wrap__picture" v-if="imageBase64Code" :src="imageBase64Code" alt=" "
 | 
			
		||||
           @click="handleClickPos($event)">
 | 
			
		||||
      <img class="wg-cap-wrap__loading"
 | 
			
		||||
           src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiByZ2JhKDI0MSwgMjQyLCAyNDMsIDApOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjM2LjgxMDEiIHI9IjEzIiBmaWxsPSIjM2U3Y2ZmIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN5IiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgY2FsY01vZGU9InNwbGluZSIga2V5U3BsaW5lcz0iMC40NSAwIDAuOSAwLjU1OzAgMC40NSAwLjU1IDAuOSIga2V5VGltZXM9IjA7MC41OzEiIHZhbHVlcz0iMjM7Nzc7MjMiPjwvYW5pbWF0ZT4KICA8L2NpcmNsZT4KPC9zdmc+"
 | 
			
		||||
           alt="正在加载中...">
 | 
			
		||||
      <img class="wg-cap-wrap__picture" v-if="imageBase64Code" :src="imageBase64Code" alt=" " @click="handleClickPos($event)" />
 | 
			
		||||
      <img
 | 
			
		||||
        class="wg-cap-wrap__loading"
 | 
			
		||||
        src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiByZ2JhKDI0MSwgMjQyLCAyNDMsIDApOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjM2LjgxMDEiIHI9IjEzIiBmaWxsPSIjM2U3Y2ZmIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN5IiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgY2FsY01vZGU9InNwbGluZSIga2V5U3BsaW5lcz0iMC40NSAwIDAuOSAwLjU1OzAgMC40NSAwLjU1IDAuOSIga2V5VGltZXM9IjA7MC41OzEiIHZhbHVlcz0iMjM7Nzc7MjMiPjwvYW5pbWF0ZT4KICA8L2NpcmNsZT4KPC9zdmc+"
 | 
			
		||||
        alt="正在加载中..."
 | 
			
		||||
      />
 | 
			
		||||
      <div v-for="(dot, key) in dots" :key="key" class="wg-cap-wrap__dot" :style="`top: ${dot.y}px; left:${dot.x}px;`">
 | 
			
		||||
        <span>{{ dot.index }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="wg-cap-wrap__footer">
 | 
			
		||||
      <div class="wg-cap-wrap__ico">
 | 
			
		||||
        <img @click="handleCloseEvent"
 | 
			
		||||
             src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDM5NDIzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijg2NzUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDIzLjI3MjcyN2MyNjkuOTE3MDkxIDAgNDg4LjcyNzI3MyAyMTguODEwMTgyIDQ4OC43MjcyNzMgNDg4LjcyNzI3M2E0ODYuNjMyNzI3IDQ4Ni42MzI3MjcgMCAwIDEtODQuOTQ1NDU1IDI3NS40MDk0NTUgNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDAgMS03Ni44NDY1NDUtNTIuNTI2NTQ2QTM5My41NDE4MTggMzkzLjU0MTgxOCAwIDAgMCA5MDcuNjM2MzY0IDUxMmMwLTIxOC41MDc2MzYtMTc3LjEyODcyNy0zOTUuNjM2MzY0LTM5NS42MzYzNjQtMzk1LjYzNjM2NFMxMTYuMzYzNjM2IDI5My40OTIzNjQgMTE2LjM2MzYzNiA1MTJzMTc3LjEyODcyNyAzOTUuNjM2MzY0IDM5NS42MzYzNjQgMzk1LjYzNjM2NGEzOTUuMTcwOTA5IDM5NS4xNzA5MDkgMCAwIDAgMTI1LjQ0LTIwLjI5MzgxOSA0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMCAxIDI5LjQ4NjU0NSA4OC4yOTY3MjhBNDg4LjI2MTgxOCA0ODguMjYxODE4IDAgMCAxIDUxMiAxMDAwLjcyNzI3M0MyNDIuMDgyOTA5IDEwMDAuNzI3MjczIDIzLjI3MjcyNyA3ODEuOTE3MDkxIDIzLjI3MjcyNyA1MTJTMjQyLjA4MjkwOSAyMy4yNzI3MjcgNTEyIDIzLjI3MjcyN3ogbS0xMTUuMiAzMDcuNzEyTDUxMiA0NDYuMTM4MTgybDExNS4yLTExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEgNjUuODE1MjczIDY1Ljg2MTgxOEw1NzcuODYxODE4IDUxMmwxMTUuMiAxMTUuMmE0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMSAxLTY1Ljg2MTgxOCA2NS44MTUyNzNMNTEyIDU3Ny44NjE4MThsLTExNS4yIDExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEtNjUuODE1MjczLTY1Ljg2MTgxOEw0NDYuMTM4MTgyIDUxMmwtMTE1LjItMTE1LjJhNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDEgMSA2NS44NjE4MTgtNjUuODE1MjczeiIgcC1pZD0iODY3NiIgZmlsbD0iIzcwNzA3MCI+PC9wYXRoPjwvc3ZnPg=="
 | 
			
		||||
             alt="关闭">
 | 
			
		||||
        <img @click="handleRefreshEvent"
 | 
			
		||||
             src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDk5NjM4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEzNjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTg3LjQ1NiA0MjUuMDI0YTMzNiAzMzYgMCAwIDAgMzY4LjM4NCA0MjAuMjI0IDQ4IDQ4IDAgMCAxIDEyLjU0NCA5NS4xNjggNDMyIDQzMiAwIDAgMS00NzMuNjY0LTU0MC4xNmwtNTcuMjgtMTUuMzZhMTIuOCAxMi44IDAgMCAxLTYuMjcyLTIwLjkyOGwxNTkuMTY4LTE3OS40NTZhMTIuOCAxMi44IDAgMCAxIDIyLjE0NCA1Ljg4OGw0OC4wNjQgMjM1LjA3MmExMi44IDEyLjggMCAwIDEtMTUuODA4IDE0LjkxMmwtNTcuMjgtMTUuMzZ6TTgzNi40OCA1OTkuMDRhMzM2IDMzNiAwIDAgMC0zNjguMzg0LTQyMC4yMjQgNDggNDggMCAxIDEtMTIuNTQ0LTk1LjE2OCA0MzIgNDMyIDAgMCAxIDQ3My42NjQgNTQwLjE2bDU3LjI4IDE1LjM2YTEyLjggMTIuOCAwIDAgMSA2LjI3MiAyMC45MjhsLTE1OS4xNjggMTc5LjQ1NmExMi44IDEyLjggMCAwIDEtMjIuMTQ0LTUuODg4bC00OC4wNjQtMjM1LjA3MmExMi44IDEyLjggMCAwIDEgMTUuODA4LTE0LjkxMmw1Ny4yOCAxNS4zNnoiIGZpbGw9IiM3MDcwNzAiIHAtaWQ9IjEzNjEiPjwvcGF0aD48L3N2Zz4="
 | 
			
		||||
             alt="刷新">
 | 
			
		||||
      <div class="wg-cap-wrap__ico flex">
 | 
			
		||||
        <img
 | 
			
		||||
          @click="handleCloseEvent"
 | 
			
		||||
          src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDM5NDIzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijg2NzUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDIzLjI3MjcyN2MyNjkuOTE3MDkxIDAgNDg4LjcyNzI3MyAyMTguODEwMTgyIDQ4OC43MjcyNzMgNDg4LjcyNzI3M2E0ODYuNjMyNzI3IDQ4Ni42MzI3MjcgMCAwIDEtODQuOTQ1NDU1IDI3NS40MDk0NTUgNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDAgMS03Ni44NDY1NDUtNTIuNTI2NTQ2QTM5My41NDE4MTggMzkzLjU0MTgxOCAwIDAgMCA5MDcuNjM2MzY0IDUxMmMwLTIxOC41MDc2MzYtMTc3LjEyODcyNy0zOTUuNjM2MzY0LTM5NS42MzYzNjQtMzk1LjYzNjM2NFMxMTYuMzYzNjM2IDI5My40OTIzNjQgMTE2LjM2MzYzNiA1MTJzMTc3LjEyODcyNyAzOTUuNjM2MzY0IDM5NS42MzYzNjQgMzk1LjYzNjM2NGEzOTUuMTcwOTA5IDM5NS4xNzA5MDkgMCAwIDAgMTI1LjQ0LTIwLjI5MzgxOSA0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMCAxIDI5LjQ4NjU0NSA4OC4yOTY3MjhBNDg4LjI2MTgxOCA0ODguMjYxODE4IDAgMCAxIDUxMiAxMDAwLjcyNzI3M0MyNDIuMDgyOTA5IDEwMDAuNzI3MjczIDIzLjI3MjcyNyA3ODEuOTE3MDkxIDIzLjI3MjcyNyA1MTJTMjQyLjA4MjkwOSAyMy4yNzI3MjcgNTEyIDIzLjI3MjcyN3ogbS0xMTUuMiAzMDcuNzEyTDUxMiA0NDYuMTM4MTgybDExNS4yLTExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEgNjUuODE1MjczIDY1Ljg2MTgxOEw1NzcuODYxODE4IDUxMmwxMTUuMiAxMTUuMmE0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMSAxLTY1Ljg2MTgxOCA2NS44MTUyNzNMNTEyIDU3Ny44NjE4MThsLTExNS4yIDExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEtNjUuODE1MjczLTY1Ljg2MTgxOEw0NDYuMTM4MTgyIDUxMmwtMTE1LjItMTE1LjJhNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDEgMSA2NS44NjE4MTgtNjUuODE1MjczeiIgcC1pZD0iODY3NiIgZmlsbD0iIzcwNzA3MCI+PC9wYXRoPjwvc3ZnPg=="
 | 
			
		||||
          alt="关闭"
 | 
			
		||||
        />
 | 
			
		||||
        <img
 | 
			
		||||
          @click="handleRefreshEvent"
 | 
			
		||||
          src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDk5NjM4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEzNjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTg3LjQ1NiA0MjUuMDI0YTMzNiAzMzYgMCAwIDAgMzY4LjM4NCA0MjAuMjI0IDQ4IDQ4IDAgMCAxIDEyLjU0NCA5NS4xNjggNDMyIDQzMiAwIDAgMS00NzMuNjY0LTU0MC4xNmwtNTcuMjgtMTUuMzZhMTIuOCAxMi44IDAgMCAxLTYuMjcyLTIwLjkyOGwxNTkuMTY4LTE3OS40NTZhMTIuOCAxMi44IDAgMCAxIDIyLjE0NCA1Ljg4OGw0OC4wNjQgMjM1LjA3MmExMi44IDEyLjggMCAwIDEtMTUuODA4IDE0LjkxMmwtNTcuMjgtMTUuMzZ6TTgzNi40OCA1OTkuMDRhMzM2IDMzNiAwIDAgMC0zNjguMzg0LTQyMC4yMjQgNDggNDggMCAxIDEtMTIuNTQ0LTk1LjE2OCA0MzIgNDMyIDAgMCAxIDQ3My42NjQgNTQwLjE2bDU3LjI4IDE1LjM2YTEyLjggMTIuOCAwIDAgMSA2LjI3MiAyMC45MjhsLTE1OS4xNjggMTc5LjQ1NmExMi44IDEyLjggMCAwIDEtMjIuMTQ0LTUuODg4bC00OC4wNjQtMjM1LjA3MmExMi44IDEyLjggMCAwIDEgMTUuODA4LTE0LjkxMmw1Ny4yOCAxNS4zNnoiIGZpbGw9IiM3MDcwNzAiIHAtaWQ9IjEzNjEiPjwvcGF0aD48L3N2Zz4="
 | 
			
		||||
          alt="刷新"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="wg-cap-wrap__btn">
 | 
			
		||||
        <el-button type="primary" @click="handleConfirmEvent">确认</el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'CaptchaPlus',
 | 
			
		||||
  name: "CaptchaPlus",
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.$emit('refresh')
 | 
			
		||||
    this.$emit("refresh");
 | 
			
		||||
  },
 | 
			
		||||
  props: {
 | 
			
		||||
    value: Boolean,
 | 
			
		||||
    width: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: '300px'
 | 
			
		||||
      default: "300px",
 | 
			
		||||
    },
 | 
			
		||||
    calcPosType: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: 'dom',
 | 
			
		||||
      validator: value => ['dom', 'screen'].includes(value)
 | 
			
		||||
      default: "dom",
 | 
			
		||||
      validator: (value) => ["dom", "screen"].includes(value),
 | 
			
		||||
    },
 | 
			
		||||
    maxDot: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 5
 | 
			
		||||
      default: 5,
 | 
			
		||||
      // validator: value => value > 10
 | 
			
		||||
    },
 | 
			
		||||
    imageBase64: String,
 | 
			
		||||
    thumbBase64: String
 | 
			
		||||
    thumbBase64: String,
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      dots: [],
 | 
			
		||||
      imageBase64Code: '',
 | 
			
		||||
      thumbBase64Code: ''
 | 
			
		||||
    }
 | 
			
		||||
      imageBase64Code: "",
 | 
			
		||||
      thumbBase64Code: "",
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    value() {
 | 
			
		||||
      this.dots = []
 | 
			
		||||
      this.imageBase64Code = ''
 | 
			
		||||
      this.thumbBase64Code = ''
 | 
			
		||||
      this.dots = [];
 | 
			
		||||
      this.imageBase64Code = "";
 | 
			
		||||
      this.thumbBase64Code = "";
 | 
			
		||||
    },
 | 
			
		||||
    imageBase64(val) {
 | 
			
		||||
      this.dots = []
 | 
			
		||||
      this.imageBase64Code = val
 | 
			
		||||
      this.dots = [];
 | 
			
		||||
      this.imageBase64Code = val;
 | 
			
		||||
    },
 | 
			
		||||
    thumbBase64(val) {
 | 
			
		||||
      this.dots = []
 | 
			
		||||
      this.thumbBase64Code = val
 | 
			
		||||
    }
 | 
			
		||||
      this.dots = [];
 | 
			
		||||
      this.thumbBase64Code = val;
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 处理关闭事件
 | 
			
		||||
     */
 | 
			
		||||
    handleCloseEvent() {
 | 
			
		||||
      this.$emit('close')
 | 
			
		||||
      this.$emit("close");
 | 
			
		||||
      // this.dots = []
 | 
			
		||||
      // this.imageBase64Code = ''
 | 
			
		||||
      // this.thumbBase64Code = ''
 | 
			
		||||
@@ -93,14 +96,14 @@ export default {
 | 
			
		||||
     * @Description: 处理刷新事件
 | 
			
		||||
     */
 | 
			
		||||
    handleRefreshEvent() {
 | 
			
		||||
      this.dots = []
 | 
			
		||||
      this.$emit('refresh')
 | 
			
		||||
      this.dots = [];
 | 
			
		||||
      this.$emit("refresh");
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 处理确认事件
 | 
			
		||||
     */
 | 
			
		||||
    handleConfirmEvent() {
 | 
			
		||||
      this.$emit('confirm', this.dots)
 | 
			
		||||
      this.$emit("confirm", this.dots);
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 处理dot
 | 
			
		||||
@@ -108,102 +111,102 @@ export default {
 | 
			
		||||
     */
 | 
			
		||||
    handleClickPos(ev) {
 | 
			
		||||
      if (this.dots.length >= this.maxDot) {
 | 
			
		||||
        return
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      const e = ev || window.event
 | 
			
		||||
      e.preventDefault()
 | 
			
		||||
      const dom = e.currentTarget
 | 
			
		||||
      const e = ev || window.event;
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      const dom = e.currentTarget;
 | 
			
		||||
 | 
			
		||||
      const {domX, domY} = this.getDomXY(dom)
 | 
			
		||||
      const { domX, domY } = this.getDomXY(dom);
 | 
			
		||||
      // ===============================================
 | 
			
		||||
      // @notice 如 getDomXY 不准确可尝试使用 calcLocationLeft 或 calcLocationTop
 | 
			
		||||
      // const domX = this.calcLocationLeft(dom)
 | 
			
		||||
      // const domY = this.calcLocationTop(dom)
 | 
			
		||||
      // ===============================================
 | 
			
		||||
      let mouseX = (navigator.vendor === 'Netscape') ? e.pageX : e.x + document.body.offsetTop
 | 
			
		||||
      let mouseY = (navigator.vendor === 'Netscape') ? e.pageY : e.y + document.body.offsetTop
 | 
			
		||||
      let mouseX = navigator.vendor === "Netscape" ? e.pageX : e.x + document.body.offsetTop;
 | 
			
		||||
      let mouseY = navigator.vendor === "Netscape" ? e.pageY : e.y + document.body.offsetTop;
 | 
			
		||||
      // 兼容移动触摸事件
 | 
			
		||||
      if (e.touches && e.touches.length > 0) {
 | 
			
		||||
        mouseX = e.touches[0].clientX
 | 
			
		||||
        mouseY = e.touches[0].clientY
 | 
			
		||||
        mouseX = e.touches[0].clientX;
 | 
			
		||||
        mouseY = e.touches[0].clientY;
 | 
			
		||||
      } else {
 | 
			
		||||
        mouseX = e.clientX
 | 
			
		||||
        mouseY = e.clientY
 | 
			
		||||
        mouseX = e.clientX;
 | 
			
		||||
        mouseY = e.clientY;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // 计算点击的相对位置
 | 
			
		||||
      const xPos = mouseX - domX
 | 
			
		||||
      const yPos = mouseY - domY
 | 
			
		||||
      const xPos = mouseX - domX;
 | 
			
		||||
      const yPos = mouseY - domY;
 | 
			
		||||
 | 
			
		||||
      // 转整形
 | 
			
		||||
      const xp = parseInt(xPos.toString())
 | 
			
		||||
      const yp = parseInt(yPos.toString())
 | 
			
		||||
      const xp = parseInt(xPos.toString());
 | 
			
		||||
      const yp = parseInt(yPos.toString());
 | 
			
		||||
 | 
			
		||||
      // 减去点的一半
 | 
			
		||||
      this.dots.push({
 | 
			
		||||
        x: xp - 11,
 | 
			
		||||
        y: yp - 11,
 | 
			
		||||
        index: this.dots.length + 1
 | 
			
		||||
      })
 | 
			
		||||
      return false
 | 
			
		||||
        index: this.dots.length + 1,
 | 
			
		||||
      });
 | 
			
		||||
      return false;
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 找到元素的屏幕位置
 | 
			
		||||
     * @param el
 | 
			
		||||
     */
 | 
			
		||||
    calcLocationLeft(el) {
 | 
			
		||||
      let tmp = el.offsetLeft
 | 
			
		||||
      let val = el.offsetParent
 | 
			
		||||
      let tmp = el.offsetLeft;
 | 
			
		||||
      let val = el.offsetParent;
 | 
			
		||||
      while (val != null) {
 | 
			
		||||
        tmp += val.offsetLeft
 | 
			
		||||
        val = val.offsetParent
 | 
			
		||||
        tmp += val.offsetLeft;
 | 
			
		||||
        val = val.offsetParent;
 | 
			
		||||
      }
 | 
			
		||||
      return tmp
 | 
			
		||||
      return tmp;
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 找到元素的屏幕位置
 | 
			
		||||
     * @param el
 | 
			
		||||
     */
 | 
			
		||||
    calcLocationTop(el) {
 | 
			
		||||
      let tmp = el.offsetTop
 | 
			
		||||
      let val = el.offsetParent
 | 
			
		||||
      let tmp = el.offsetTop;
 | 
			
		||||
      let val = el.offsetParent;
 | 
			
		||||
      while (val != null) {
 | 
			
		||||
        tmp += val.offsetTop
 | 
			
		||||
        val = val.offsetParent
 | 
			
		||||
        tmp += val.offsetTop;
 | 
			
		||||
        val = val.offsetParent;
 | 
			
		||||
      }
 | 
			
		||||
      return tmp
 | 
			
		||||
      return tmp;
 | 
			
		||||
    },
 | 
			
		||||
    /**
 | 
			
		||||
     * @Description: 找到元素的屏幕位置
 | 
			
		||||
     * @param dom
 | 
			
		||||
     */
 | 
			
		||||
    getDomXY(dom) {
 | 
			
		||||
      let x = 0
 | 
			
		||||
      let y = 0
 | 
			
		||||
      let x = 0;
 | 
			
		||||
      let y = 0;
 | 
			
		||||
      if (dom.getBoundingClientRect) {
 | 
			
		||||
        let box = dom.getBoundingClientRect();
 | 
			
		||||
        let D = document.documentElement;
 | 
			
		||||
        x = box.left + Math.max(D.scrollLeft, document.body.scrollLeft) - D.clientLeft;
 | 
			
		||||
        y = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop
 | 
			
		||||
        y = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop;
 | 
			
		||||
      } else {
 | 
			
		||||
        while (dom !== document.body) {
 | 
			
		||||
          x += dom.offsetLeft
 | 
			
		||||
          y += dom.offsetTop
 | 
			
		||||
          dom = dom.offsetParent
 | 
			
		||||
          x += dom.offsetLeft;
 | 
			
		||||
          y += dom.offsetTop;
 | 
			
		||||
          dom = dom.offsetParent;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return {
 | 
			
		||||
        domX: x,
 | 
			
		||||
        domY: y
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
        domY: y,
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
.wg-cap-wrap {
 | 
			
		||||
  background: #ffffff;
 | 
			
		||||
  background: var(--el-bg-color);
 | 
			
		||||
 | 
			
		||||
  -webkit-border-radius: 10px;
 | 
			
		||||
  -moz-border-radius: 10px;
 | 
			
		||||
@@ -219,14 +222,7 @@ export default {
 | 
			
		||||
    height: 50px;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    font-size: 15px;
 | 
			
		||||
 | 
			
		||||
    display: -webkit-box;
 | 
			
		||||
    display: -webkit-flex;
 | 
			
		||||
    display: -ms-flexbox;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    -webkit-box-align: center;
 | 
			
		||||
    -webkit-align-items: center;
 | 
			
		||||
    -ms-flex-align: center;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
 | 
			
		||||
    span {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,65 +1,66 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="chat-line chat-line-prompt-list" v-if="listStyle === 'list'">
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
        <div class="chat-icon">
 | 
			
		||||
          <img :src="data.icon" alt="User"/>
 | 
			
		||||
        </div>
 | 
			
		||||
      <div class="chat-icon">
 | 
			
		||||
        <img :src="data.icon" alt="User" />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
        <div class="chat-item">
 | 
			
		||||
          <div v-if="files.length > 0" class="file-list-box">
 | 
			
		||||
            <div v-for="file in files">
 | 
			
		||||
              <div class="image" v-if="isImage(file.ext)">
 | 
			
		||||
                <el-image :src="file.url" fit="cover"/>
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div v-if="files.length > 0" class="file-list-box">
 | 
			
		||||
          <div v-for="file in files">
 | 
			
		||||
            <div class="image" v-if="isImage(file.ext)">
 | 
			
		||||
              <el-image :src="file.url" fit="cover" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="item" v-else>
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <el-image :src="GetFileIcon(file.ext)" fit="cover" />
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="item" v-else>
 | 
			
		||||
                <div class="icon">
 | 
			
		||||
                  <el-image :src="GetFileIcon(file.ext)" fit="cover"  />
 | 
			
		||||
              <div class="body">
 | 
			
		||||
                <div class="title">
 | 
			
		||||
                  <el-link :href="file.url" target="_blank" style="--el-font-weight-primary: bold">{{ file.name }}</el-link>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="body">
 | 
			
		||||
                  <div class="title">
 | 
			
		||||
                    <el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="info">
 | 
			
		||||
                    <span>{{GetFileType(file.ext)}}</span>
 | 
			
		||||
                    <span>{{FormatFileSize(file.size)}}</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                <div class="info">
 | 
			
		||||
                  <span>{{ GetFileType(file.ext) }}</span>
 | 
			
		||||
                  <span>{{ FormatFileSize(file.size) }}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="content" v-html="content"></div>
 | 
			
		||||
          <div class="bar" v-if="data.created_at > 0">
 | 
			
		||||
            <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
            <span class="bar-item">tokens: {{ finalTokens }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="content" v-html="content"></div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at > 0">
 | 
			
		||||
          <span class="bar-item"
 | 
			
		||||
            ><el-icon><Clock /></el-icon> {{ dateFormat(data.created_at) }}</span
 | 
			
		||||
          >
 | 
			
		||||
          <span class="bar-item">tokens: {{ finalTokens }}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <div class="chat-line chat-line-prompt-chat" v-else>
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
      <div class="chat-icon">
 | 
			
		||||
        <img :src="data.icon" alt="User"/>
 | 
			
		||||
        <img :src="data.icon" alt="User" />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
 | 
			
		||||
        <div v-if="files.length > 0" class="file-list-box">
 | 
			
		||||
          <div v-for="file in files">
 | 
			
		||||
            <div class="image" v-if="isImage(file.ext)">
 | 
			
		||||
              <el-image :src="file.url" fit="cover"/>
 | 
			
		||||
              <el-image :src="file.url" fit="cover" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="item" v-else>
 | 
			
		||||
              <div class="icon">
 | 
			
		||||
                <el-image :src="GetFileIcon(file.ext)" fit="cover"  />
 | 
			
		||||
                <el-image :src="GetFileIcon(file.ext)" fit="cover" />
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="body">
 | 
			
		||||
                <div class="title">
 | 
			
		||||
                  <el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{file.name}}</el-link>
 | 
			
		||||
                  <el-link :href="file.url" target="_blank" style="--el-font-weight-primary: bold">{{ file.name }}</el-link>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="info">
 | 
			
		||||
                  <span>{{GetFileType(file.ext)}}</span>
 | 
			
		||||
                  <span>{{FormatFileSize(file.size)}}</span>
 | 
			
		||||
                  <span>{{ GetFileType(file.ext) }}</span>
 | 
			
		||||
                  <span>{{ FormatFileSize(file.size) }}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -69,8 +70,10 @@
 | 
			
		||||
          <div class="content" v-html="content"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at > 0">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
<!--          <span class="bar-item">tokens: {{ finalTokens }}</span>-->
 | 
			
		||||
          <span class="bar-item"
 | 
			
		||||
            ><el-icon><Clock /></el-icon> {{ dateFormat(data.created_at) }}</span
 | 
			
		||||
          >
 | 
			
		||||
          <!--          <span class="bar-item">tokens: {{ finalTokens }}</span>-->
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -78,66 +81,72 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue"
 | 
			
		||||
import {Clock} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { Clock } from "@element-plus/icons-vue";
 | 
			
		||||
import { httpPost } from "@/utils/http";
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
import {dateFormat, isImage, processPrompt} from "@/utils/libs";
 | 
			
		||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
 | 
			
		||||
import { dateFormat, isImage, processPrompt } from "@/utils/libs";
 | 
			
		||||
import { FormatFileSize, GetFileIcon, GetFileType } from "@/store/system";
 | 
			
		||||
import emoji from "markdown-it-emoji";
 | 
			
		||||
import mathjaxPlugin from "markdown-it-mathjax3";
 | 
			
		||||
import MarkdownIt from "markdown-it";
 | 
			
		||||
 | 
			
		||||
const mathjaxPlugin = require('markdown-it-mathjax3')
 | 
			
		||||
const md = require('markdown-it')({
 | 
			
		||||
const md = new MarkdownIt({
 | 
			
		||||
  breaks: true,
 | 
			
		||||
  html: true,
 | 
			
		||||
  linkify: true,
 | 
			
		||||
  typographer: true,
 | 
			
		||||
  highlight: function (str, lang) {
 | 
			
		||||
    const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
 | 
			
		||||
    const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000);
 | 
			
		||||
    // 显示复制代码按钮
 | 
			
		||||
    const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
 | 
			
		||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>`
 | 
			
		||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
 | 
			
		||||
      /<\/textarea>/g,
 | 
			
		||||
      "</textarea>"
 | 
			
		||||
    )}</textarea>`;
 | 
			
		||||
    if (lang && hl.getLanguage(lang)) {
 | 
			
		||||
      const langHtml = `<span class="lang-name">${lang}</span>`
 | 
			
		||||
      const langHtml = `<span class="lang-name">${lang}</span>`;
 | 
			
		||||
      // 处理代码高亮
 | 
			
		||||
      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>${copyBtn} ${langHtml}</pre>`
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</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>${copyBtn}</pre>`
 | 
			
		||||
  }
 | 
			
		||||
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`;
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
md.use(mathjaxPlugin)
 | 
			
		||||
md.use(mathjaxPlugin);
 | 
			
		||||
md.use(emoji);
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  data: {
 | 
			
		||||
    type: Object,
 | 
			
		||||
    default: {
 | 
			
		||||
      content: '',
 | 
			
		||||
      created_at: '',
 | 
			
		||||
      content: "",
 | 
			
		||||
      created_at: "",
 | 
			
		||||
      tokens: 0,
 | 
			
		||||
      model: '',
 | 
			
		||||
      icon: '',
 | 
			
		||||
      model: "",
 | 
			
		||||
      icon: "",
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  listStyle: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'list',
 | 
			
		||||
    default: "list",
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
const finalTokens = ref(props.data.tokens)
 | 
			
		||||
const content =ref(processPrompt(props.data.content))
 | 
			
		||||
const files = ref([])
 | 
			
		||||
});
 | 
			
		||||
const finalTokens = ref(props.data.tokens);
 | 
			
		||||
const content = ref(processPrompt(props.data.content));
 | 
			
		||||
const files = ref([]);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  processFiles()
 | 
			
		||||
})
 | 
			
		||||
  processFiles();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const processFiles = () => {
 | 
			
		||||
  if (!props.data.content) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const linkRegex = /(https?:\/\/\S+)/g;
 | 
			
		||||
@@ -165,7 +174,7 @@ const processFiles = () => {
 | 
			
		||||
        .catch(() => {});
 | 
			
		||||
 | 
			
		||||
    for (let link of links) {
 | 
			
		||||
      content.value = content.value.replace(link, "");
 | 
			
		||||
      content.value = content.value.replace(link,"")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
@@ -180,12 +189,14 @@ const isExternalImg = (link, files) => {
 | 
			
		||||
@import '@/assets/css/markdown/vue.css';
 | 
			
		||||
.chat-page,.chat-export {
 | 
			
		||||
  .chat-line-prompt-list {
 | 
			
		||||
    background-color #ffffff;
 | 
			
		||||
 | 
			
		||||
    background-color:var( --chat-content-bg-list);
 | 
			
		||||
    color:var(--theme-text-color-primary);
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    width 100%
 | 
			
		||||
    padding-bottom: 1.5rem;
 | 
			
		||||
    padding-top: 1.5rem;
 | 
			
		||||
    border-bottom: 1px solid #d9d9e3;
 | 
			
		||||
    border-bottom: 0.5px solid var(--el-border-color);
 | 
			
		||||
 | 
			
		||||
    .chat-line-inner {
 | 
			
		||||
      display flex;
 | 
			
		||||
@@ -199,7 +210,7 @@ const isExternalImg = (link, files) => {
 | 
			
		||||
        img {
 | 
			
		||||
          width: 36px;
 | 
			
		||||
          height: 36px;
 | 
			
		||||
          border-radius: 10px;
 | 
			
		||||
          border-radius: 50%;
 | 
			
		||||
          padding: 1px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -228,8 +239,9 @@ const isExternalImg = (link, files) => {
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow row
 | 
			
		||||
            border-radius 10px
 | 
			
		||||
            background-color #ffffff
 | 
			
		||||
            background-color:var(--chat-content-bg);
 | 
			
		||||
            border 1px solid #e3e3e3
 | 
			
		||||
            color:var(--theme-text-color-primary);
 | 
			
		||||
            padding 6px
 | 
			
		||||
            margin-bottom 10px
 | 
			
		||||
 | 
			
		||||
@@ -261,7 +273,7 @@ const isExternalImg = (link, files) => {
 | 
			
		||||
        .content {
 | 
			
		||||
          word-break break-word;
 | 
			
		||||
          padding: 0;
 | 
			
		||||
          color #374151;
 | 
			
		||||
          color:var(--theme-text-color-primary);
 | 
			
		||||
          font-size: var(--content-font-size);
 | 
			
		||||
          border-radius: 5px;
 | 
			
		||||
          overflow: auto;
 | 
			
		||||
@@ -289,7 +301,7 @@ const isExternalImg = (link, files) => {
 | 
			
		||||
          padding 10px 10px 10px 0;
 | 
			
		||||
 | 
			
		||||
          .bar-item {
 | 
			
		||||
            background-color #f7f7f8;
 | 
			
		||||
            // background-color #f7f7f8;
 | 
			
		||||
            color #888
 | 
			
		||||
            padding 3px 5px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
@@ -308,7 +320,7 @@ const isExternalImg = (link, files) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .chat-line-prompt-chat {
 | 
			
		||||
    background-color #ffffff;
 | 
			
		||||
    background: var(--chat-bg);
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    width 100%
 | 
			
		||||
    padding-bottom: 1.5rem;
 | 
			
		||||
@@ -355,7 +367,8 @@ const isExternalImg = (link, files) => {
 | 
			
		||||
            display flex
 | 
			
		||||
            flex-flow row
 | 
			
		||||
            border-radius 10px
 | 
			
		||||
            background-color #ffffff
 | 
			
		||||
            background-color:var(--chat-content-bg);
 | 
			
		||||
            color:var(--theme-text-color-primary);
 | 
			
		||||
            border 1px solid #e3e3e3
 | 
			
		||||
            padding 6px
 | 
			
		||||
            margin-bottom 10px
 | 
			
		||||
@@ -392,10 +405,10 @@ const isExternalImg = (link, files) => {
 | 
			
		||||
          .content {
 | 
			
		||||
              word-break break-word;
 | 
			
		||||
              padding: 1rem
 | 
			
		||||
              color #222222;
 | 
			
		||||
              color var(--theme-text-primary);
 | 
			
		||||
              font-size: var(--content-font-size);
 | 
			
		||||
              overflow: auto;
 | 
			
		||||
              background-color #98e165
 | 
			
		||||
              background-color :var(--chat-content-bg);
 | 
			
		||||
              border-radius: 10px 0 10px 10px;
 | 
			
		||||
 | 
			
		||||
              img {
 | 
			
		||||
 
 | 
			
		||||
@@ -2,48 +2,35 @@
 | 
			
		||||
  <div class="chat-line chat-line-reply-list" v-if="listStyle === 'list'">
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
      <div class="chat-icon">
 | 
			
		||||
        <img :src="data.icon" alt="ChatGPT">
 | 
			
		||||
        <img :src="data.icon" alt="ChatGPT" />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div class="content" v-html="md.render(processContent(data.content))"></div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
                    <span class="bar-item">tokens: {{ data.tokens }}</span>
 | 
			
		||||
          <span class="bar-item"
 | 
			
		||||
            ><el-icon><Clock /></el-icon> {{ dateFormat(data.created_at) }}</span
 | 
			
		||||
          >
 | 
			
		||||
          <span class="bar-item">tokens: {{ data.tokens }}</span>
 | 
			
		||||
          <span class="bar-item">
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  content="复制回答"
 | 
			
		||||
                  placement="bottom"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon class="copy-reply" :data-clipboard-text="data.content">
 | 
			
		||||
                  <DocumentCopy/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </span>
 | 
			
		||||
            <el-tooltip class="box-item" effect="dark" content="复制回答" placement="bottom">
 | 
			
		||||
              <el-icon class="copy-reply" :data-clipboard-text="data.content">
 | 
			
		||||
                <DocumentCopy />
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
          <span v-if="!readOnly">
 | 
			
		||||
            <span class="bar-item" @click="reGenerate(data.prompt)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                content="重新生成"
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
              <el-icon><Refresh/></el-icon>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
              <el-tooltip class="box-item" effect="dark" content="重新生成" placement="bottom">
 | 
			
		||||
                <el-icon><Refresh /></el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
          <span class="bar-item" @click="synthesis(data.content)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                content="生成语音朗读"
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
              <i class="iconfont icon-speaker"></i>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
            <span class="bar-item" @click="synthesis(data.content)">
 | 
			
		||||
              <el-tooltip class="box-item" effect="dark" content="生成语音朗读" placement="bottom">
 | 
			
		||||
                <i class="iconfont icon-speaker"></i>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </span>
 | 
			
		||||
          </span>
 | 
			
		||||
          <!--          <span class="bar-item">-->
 | 
			
		||||
          <!--            <el-dropdown trigger="click">-->
 | 
			
		||||
@@ -65,49 +52,36 @@
 | 
			
		||||
  <div class="chat-line chat-line-reply-chat" v-else>
 | 
			
		||||
    <div class="chat-line-inner">
 | 
			
		||||
      <div class="chat-icon">
 | 
			
		||||
        <img :src="data.icon" alt="ChatGPT">
 | 
			
		||||
        <img :src="data.icon" alt="ChatGPT" />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="chat-item">
 | 
			
		||||
        <div class="content-wrapper">
 | 
			
		||||
          <div class="content" v-html="md.render(processContent(data.content))"></div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="bar" v-if="data.created_at">
 | 
			
		||||
          <span class="bar-item"><el-icon><Clock/></el-icon> {{ dateFormat(data.created_at) }}</span>
 | 
			
		||||
<!--          <span class="bar-item">tokens: {{ data.tokens }}</span>-->
 | 
			
		||||
          <span class="bar-item"
 | 
			
		||||
            ><el-icon><Clock /></el-icon> {{ dateFormat(data.created_at) }}</span
 | 
			
		||||
          >
 | 
			
		||||
          <!--          <span class="bar-item">tokens: {{ data.tokens }}</span>-->
 | 
			
		||||
          <span class="bar-item bg">
 | 
			
		||||
              <el-tooltip
 | 
			
		||||
                  class="box-item"
 | 
			
		||||
                  effect="dark"
 | 
			
		||||
                  content="复制回答"
 | 
			
		||||
                  placement="bottom"
 | 
			
		||||
              >
 | 
			
		||||
                <el-icon class="copy-reply" :data-clipboard-text="data.content">
 | 
			
		||||
                  <DocumentCopy/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </span>
 | 
			
		||||
            <el-tooltip class="box-item" effect="dark" content="复制回答" placement="bottom">
 | 
			
		||||
              <el-icon class="copy-reply" :data-clipboard-text="data.content">
 | 
			
		||||
                <DocumentCopy />
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
          <span v-if="!readOnly">
 | 
			
		||||
            <span class="bar-item bg" @click="reGenerate(data.prompt)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                content="重新生成"
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
              <el-icon><Refresh/></el-icon>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
              <el-tooltip class="box-item" effect="dark" content="重新生成" placement="bottom">
 | 
			
		||||
                <el-icon><Refresh /></el-icon>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </span>
 | 
			
		||||
 | 
			
		||||
          <span class="bar-item bg" @click="synthesis(data.content)">
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
                class="box-item"
 | 
			
		||||
                effect="dark"
 | 
			
		||||
                content="生成语音朗读"
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
            >
 | 
			
		||||
              <i class="iconfont icon-speaker"></i>
 | 
			
		||||
            </el-tooltip>
 | 
			
		||||
          </span>
 | 
			
		||||
            <span class="bar-item bg" @click="synthesis(data.content)">
 | 
			
		||||
              <el-tooltip class="box-item" effect="dark" content="生成语音朗读" placement="bottom">
 | 
			
		||||
                <i class="iconfont icon-speaker"></i>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </span>
 | 
			
		||||
          </span>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
@@ -116,10 +90,14 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {Clock, DocumentCopy, Refresh} from "@element-plus/icons-vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat, processContent} from "@/utils/libs";
 | 
			
		||||
import { Clock, DocumentCopy, Refresh } from "@element-plus/icons-vue";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { dateFormat, processContent } from "@/utils/libs";
 | 
			
		||||
import hl from "highlight.js";
 | 
			
		||||
import emoji from "markdown-it-emoji";
 | 
			
		||||
import mathjaxPlugin from "markdown-it-mathjax3";
 | 
			
		||||
import MarkdownIt from "markdown-it";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef,no-unused-vars
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  data: {
 | 
			
		||||
@@ -133,69 +111,75 @@ const props = defineProps({
 | 
			
		||||
  },
 | 
			
		||||
  readOnly: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false
 | 
			
		||||
    default: false,
 | 
			
		||||
  },
 | 
			
		||||
  listStyle: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'list',
 | 
			
		||||
    default: "list",
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const mathjaxPlugin = require('markdown-it-mathjax3')
 | 
			
		||||
const md = require('markdown-it')({
 | 
			
		||||
const md = new MarkdownIt({
 | 
			
		||||
  breaks: true,
 | 
			
		||||
  html: true,
 | 
			
		||||
  linkify: true,
 | 
			
		||||
  typographer: true,
 | 
			
		||||
  highlight: function (str, lang) {
 | 
			
		||||
    const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
 | 
			
		||||
    const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000);
 | 
			
		||||
    // 显示复制代码按钮
 | 
			
		||||
    const copyBtn = `<span class="copy-code-btn" data-clipboard-action="copy" data-clipboard-target="#copy-target-${codeIndex}">复制</span>
 | 
			
		||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(/<\/textarea>/g, '</textarea>')}</textarea>`
 | 
			
		||||
<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy-target-${codeIndex}">${str.replace(
 | 
			
		||||
      /<\/textarea>/g,
 | 
			
		||||
      "</textarea>"
 | 
			
		||||
    )}</textarea>`;
 | 
			
		||||
    if (lang && hl.getLanguage(lang)) {
 | 
			
		||||
      const langHtml = `<span class="lang-name">${lang}</span>`
 | 
			
		||||
      const langHtml = `<span class="lang-name">${lang}</span>`;
 | 
			
		||||
      // 处理代码高亮
 | 
			
		||||
      const preCode = hl.highlight(lang, str, true).value
 | 
			
		||||
      const preCode = hl.highlight(str, { language: lang }).value;
 | 
			
		||||
      // 将代码包裹在 pre 中
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</pre>`
 | 
			
		||||
      return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn} ${langHtml}</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>${copyBtn}</pre>`
 | 
			
		||||
  }
 | 
			
		||||
    return `<pre class="code-container"><code class="language-${lang} hljs">${preCode}</code>${copyBtn}</pre>`;
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
md.use(mathjaxPlugin)
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['regen']);
 | 
			
		||||
md.use(mathjaxPlugin);
 | 
			
		||||
md.use(emoji);
 | 
			
		||||
const emits = defineEmits(["regen"]);
 | 
			
		||||
 | 
			
		||||
if (!props.data.icon) {
 | 
			
		||||
  props.data.icon = "images/gpt-icon.png"
 | 
			
		||||
  props.data.icon = "images/gpt-icon.png";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const synthesis = (text) => {
 | 
			
		||||
  console.log(text)
 | 
			
		||||
  ElMessage.info("语音合成功能暂不可用")
 | 
			
		||||
}
 | 
			
		||||
  console.log(text);
 | 
			
		||||
  ElMessage.info("语音合成功能暂不可用");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 重新生成
 | 
			
		||||
const reGenerate = (prompt) => {
 | 
			
		||||
  console.log(prompt)
 | 
			
		||||
  emits('regen', prompt)
 | 
			
		||||
}
 | 
			
		||||
  console.log(prompt);
 | 
			
		||||
  emits("regen", prompt);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@import '@/assets/css/markdown/vue.css';
 | 
			
		||||
.chat-page,.chat-export {
 | 
			
		||||
  --font-family: Menlo,"微软雅黑","Roboto Mono","Courier New",Courier,monospace,"Inter",sans-serif;
 | 
			
		||||
  font-family: var(--font-family);
 | 
			
		||||
 | 
			
		||||
  .chat-line-reply-list {
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    background-color: rgba(247, 247, 248, 1);
 | 
			
		||||
    background-color: var(--chat-list-bg);
 | 
			
		||||
    color:var(--theme-text-color-primary);
 | 
			
		||||
    width 100%
 | 
			
		||||
    padding-bottom: 1.5rem;
 | 
			
		||||
    padding-top: 1.5rem;
 | 
			
		||||
    border-bottom: 1px solid #d9d9e3;
 | 
			
		||||
    border-bottom: 0.5px solid var(--el-border-color);
 | 
			
		||||
 | 
			
		||||
    .chat-line-inner {
 | 
			
		||||
      display flex;
 | 
			
		||||
@@ -209,7 +193,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
        img {
 | 
			
		||||
          width: 36px;
 | 
			
		||||
          height: 36px;
 | 
			
		||||
          border-radius: 10px;
 | 
			
		||||
          border-radius: 50%;
 | 
			
		||||
          padding: 1px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -224,7 +208,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
          min-height 20px;
 | 
			
		||||
          word-break break-word;
 | 
			
		||||
          padding: 0
 | 
			
		||||
          color #374151;
 | 
			
		||||
          color:var(--theme-text-color-primary);
 | 
			
		||||
          font-size: var(--content-font-size);
 | 
			
		||||
          border-radius: 5px;
 | 
			
		||||
          overflow auto;
 | 
			
		||||
@@ -238,10 +222,13 @@ const reGenerate = (prompt) => {
 | 
			
		||||
            line-height 1.5
 | 
			
		||||
 | 
			
		||||
            code {
 | 
			
		||||
              color #374151
 | 
			
		||||
              background-color #e7e7e8
 | 
			
		||||
              padding 0 3px;
 | 
			
		||||
              border-radius 5px;
 | 
			
		||||
              color:var(--theme-text-color-primary);
 | 
			
		||||
              font-weight 600
 | 
			
		||||
              // color:#fff
 | 
			
		||||
              // background-color var(--el-color-primary-light-3)
 | 
			
		||||
              // background-color: var(--el-color-primary);
 | 
			
		||||
              // padding 3px 5px;
 | 
			
		||||
              // border-radius 5px;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@@ -296,7 +283,8 @@ const reGenerate = (prompt) => {
 | 
			
		||||
            color #212529
 | 
			
		||||
            border-collapse collapse;
 | 
			
		||||
            border 1px solid #dee2e6;
 | 
			
		||||
            background-color #ffffff
 | 
			
		||||
            background-color:var(--chat-content-bg);
 | 
			
		||||
            color:var(--theme-text-color-primary);
 | 
			
		||||
 | 
			
		||||
            thead {
 | 
			
		||||
              th {
 | 
			
		||||
@@ -330,8 +318,6 @@ const reGenerate = (prompt) => {
 | 
			
		||||
          padding 10px 10px 10px 0;
 | 
			
		||||
 | 
			
		||||
          .bar-item {
 | 
			
		||||
            background-color #e7e7e8;
 | 
			
		||||
            color #888
 | 
			
		||||
            padding 3px 5px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
            border-radius 5px;
 | 
			
		||||
@@ -396,10 +382,13 @@ const reGenerate = (prompt) => {
 | 
			
		||||
            min-height 20px;
 | 
			
		||||
            word-break break-word;
 | 
			
		||||
            padding: 1rem
 | 
			
		||||
            color #374151;
 | 
			
		||||
            color var(--theme-text-primary);
 | 
			
		||||
 | 
			
		||||
            font-size: var(--content-font-size);
 | 
			
		||||
            overflow auto;
 | 
			
		||||
            background-color #F5F5F5
 | 
			
		||||
            // background-color #F5F5F5
 | 
			
		||||
            background-color :var(--chat-content-bg);
 | 
			
		||||
 | 
			
		||||
            border-radius: 0 10px 10px 10px;
 | 
			
		||||
 | 
			
		||||
            img {
 | 
			
		||||
@@ -411,10 +400,12 @@ const reGenerate = (prompt) => {
 | 
			
		||||
              line-height 1.5
 | 
			
		||||
 | 
			
		||||
              code {
 | 
			
		||||
                color #374151
 | 
			
		||||
                background-color #e7e7e8
 | 
			
		||||
                padding 0 3px;
 | 
			
		||||
                border-radius 5px;
 | 
			
		||||
                color:var(--code-text-color);
 | 
			
		||||
                font-weight bold
 | 
			
		||||
                font-family: var(--font-family);
 | 
			
		||||
                background-color: var(--code-bg-color);
 | 
			
		||||
                border-radius: 4px;
 | 
			
		||||
                padding: .2rem .4rem;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -469,7 +460,8 @@ const reGenerate = (prompt) => {
 | 
			
		||||
              color #212529
 | 
			
		||||
              border-collapse collapse;
 | 
			
		||||
              border 1px solid #dee2e6;
 | 
			
		||||
              background-color #ffffff
 | 
			
		||||
              background-color:var(--chat-content-bg);
 | 
			
		||||
              color:var(--theme-text-color-primary);
 | 
			
		||||
 | 
			
		||||
              thead {
 | 
			
		||||
                th {
 | 
			
		||||
@@ -504,7 +496,6 @@ const reGenerate = (prompt) => {
 | 
			
		||||
          padding 10px 10px 10px 0;
 | 
			
		||||
 | 
			
		||||
          .bar-item {
 | 
			
		||||
            color #888
 | 
			
		||||
            padding 3px 5px;
 | 
			
		||||
            margin-right 10px;
 | 
			
		||||
            border-radius 5px;
 | 
			
		||||
@@ -517,7 +508,7 @@ const reGenerate = (prompt) => {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          .bar-item.bg {
 | 
			
		||||
            background-color #e7e7e8
 | 
			
		||||
            // background-color var( --gray-btn-bg)
 | 
			
		||||
            cursor pointer
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@@ -541,5 +532,4 @@ const reGenerate = (prompt) => {
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -2,22 +2,22 @@
 | 
			
		||||
  <el-container class="chat-file-list">
 | 
			
		||||
    <div v-for="file in fileList" :key="file.url">
 | 
			
		||||
      <div class="image" v-if="isImage(file.ext)">
 | 
			
		||||
        <el-image :src="file.url" fit="cover"/>
 | 
			
		||||
        <el-image :src="file.url" fit="cover" />
 | 
			
		||||
        <div class="action">
 | 
			
		||||
          <el-icon @click="removeFile(file)"><CircleCloseFilled /></el-icon>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="item" v-else>
 | 
			
		||||
        <div class="icon">
 | 
			
		||||
          <el-image :src="GetFileIcon(file.ext)" fit="cover"  />
 | 
			
		||||
          <el-image :src="GetFileIcon(file.ext)" fit="cover" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="body">
 | 
			
		||||
          <div class="title">
 | 
			
		||||
            <el-link :href="file.url" target="_blank" style="--el-font-weight-primary:bold">{{substr(file.name, 30)}}</el-link>
 | 
			
		||||
            <el-link :href="file.url" target="_blank" style="--el-font-weight-primary: bold">{{ substr(file.name, 30) }}</el-link>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="info">
 | 
			
		||||
            <span>{{GetFileType(file.ext)}}</span>
 | 
			
		||||
            <span>{{FormatFileSize(file.size)}}</span>
 | 
			
		||||
            <span>{{ GetFileType(file.ext) }}</span>
 | 
			
		||||
            <span>{{ FormatFileSize(file.size) }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="action">
 | 
			
		||||
@@ -29,26 +29,24 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {CircleCloseFilled} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import { CircleCloseFilled } from "@element-plus/icons-vue";
 | 
			
		||||
import { isImage, removeArrayItem, substr } from "@/utils/libs";
 | 
			
		||||
import { FormatFileSize, GetFileIcon, GetFileType } from "@/store/system";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  files: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    default:[],
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const emits = defineEmits(['removeFile']);
 | 
			
		||||
const fileList = ref(props.files)
 | 
			
		||||
 | 
			
		||||
    default: [],
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
const emits = defineEmits(["removeFile"]);
 | 
			
		||||
const fileList = ref(props.files);
 | 
			
		||||
 | 
			
		||||
const removeFile = (file) => {
 | 
			
		||||
  fileList.value = removeArrayItem(fileList.value, file, (v1,v2) => v1.url===v2.url)
 | 
			
		||||
  emits('removeFile', file)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  fileList.value = removeArrayItem(fileList.value, file, (v1, v2) => v1.url === v2.url);
 | 
			
		||||
  emits("removeFile", file);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
@@ -75,7 +73,8 @@ const removeFile = (file) => {
 | 
			
		||||
    display flex
 | 
			
		||||
    flex-flow row
 | 
			
		||||
    border-radius 10px
 | 
			
		||||
    background-color #ffffff
 | 
			
		||||
    background-color:var(--chat-content-bg);
 | 
			
		||||
    color:var(--theme-text-color-primary);
 | 
			
		||||
    border 1px solid #e3e3e3
 | 
			
		||||
    padding 6px
 | 
			
		||||
    margin-right 10px
 | 
			
		||||
@@ -110,7 +109,10 @@ const removeFile = (file) => {
 | 
			
		||||
    color #da0d54
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    font-size 20px
 | 
			
		||||
    .el-icon {
 | 
			
		||||
      background-color #fff
 | 
			
		||||
      border-radius 50%
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,145 +3,146 @@
 | 
			
		||||
    <a class="file-upload-img" @click="fetchFiles(1)">
 | 
			
		||||
      <i class="iconfont icon-attachment-st"></i>
 | 
			
		||||
    </a>
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        class="file-list-dialog"
 | 
			
		||||
        v-model="show"
 | 
			
		||||
        :close-on-click-modal="true"
 | 
			
		||||
        :show-close="true"
 | 
			
		||||
        :width="800"
 | 
			
		||||
        title="文件管理"
 | 
			
		||||
    >
 | 
			
		||||
      <el-scrollbar ref="scrollbarRef" max-height="80vh" style="height: 100%;" @scroll="onScroll">
 | 
			
		||||
    <el-dialog class="file-list-dialog" v-model="show" :close-on-click-modal="true" :show-close="true" :width="800" title="文件管理">
 | 
			
		||||
      <el-scrollbar ref="scrollbarRef" max-height="80vh" style="height: 100%" @scroll="onScroll">
 | 
			
		||||
        <div class="file-list">
 | 
			
		||||
          <el-row :gutter="20">
 | 
			
		||||
            <el-col :span="3">
 | 
			
		||||
              <div class="grid-content">
 | 
			
		||||
                <el-upload
 | 
			
		||||
                    class="avatar-uploader"
 | 
			
		||||
                    :auto-upload="true"
 | 
			
		||||
                    :show-file-list="false"
 | 
			
		||||
                    :http-request="afterRead"
 | 
			
		||||
                    accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf,.mp4,.mp3"
 | 
			
		||||
                  class="avatar-uploader"
 | 
			
		||||
                  :auto-upload="true"
 | 
			
		||||
                  :show-file-list="false"
 | 
			
		||||
                  :http-request="afterRead"
 | 
			
		||||
                  accept=".doc,.docx,.jpg,.png,.jpeg,.xls,.xlsx,.ppt,.pptx,.pdf,.mp4,.mp3"
 | 
			
		||||
                >
 | 
			
		||||
                  <el-icon class="avatar-uploader-icon">
 | 
			
		||||
                    <Plus/>
 | 
			
		||||
                    <Plus />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </el-upload>
 | 
			
		||||
              </div>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="3" v-for="file in fileData.items" :key="file.url">
 | 
			
		||||
              <div class="grid-content">
 | 
			
		||||
                <el-tooltip
 | 
			
		||||
                    class="box-item"
 | 
			
		||||
                    effect="dark"
 | 
			
		||||
                    :content="file.name"
 | 
			
		||||
                    placement="top">
 | 
			
		||||
                  <el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file)"/>
 | 
			
		||||
                  <el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)"/>
 | 
			
		||||
                <el-tooltip class="box-item" effect="dark" :content="file.name" placement="top">
 | 
			
		||||
                  <el-image :src="file.url" fit="cover" v-if="isImage(file.ext)" @click="insertURL(file)" />
 | 
			
		||||
                  <el-image :src="GetFileIcon(file.ext)" fit="cover" v-else @click="insertURL(file)" />
 | 
			
		||||
                </el-tooltip>
 | 
			
		||||
 | 
			
		||||
                <div class="opt">
 | 
			
		||||
                  <el-button type="danger" size="small" :icon="Delete" @click="removeFile(file)" circle/>
 | 
			
		||||
                  <el-button type="danger" size="small" :icon="Delete" @click="removeFile(file)" circle />
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </el-col>
 | 
			
		||||
          </el-row>
 | 
			
		||||
          <el-row justify="center" v-if="!fileData.isLastPage" @click="fetchFiles(fileData.page)">
 | 
			
		||||
            <el-link>加载更多</el-link>
 | 
			
		||||
            
 | 
			
		||||
          </el-row>
 | 
			
		||||
        </div>        
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-scrollbar>
 | 
			
		||||
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </el-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {reactive, ref} from "vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {Delete, Plus} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
import {GetFileIcon} from "@/store/system";
 | 
			
		||||
 | 
			
		||||
import { reactive, ref } from "vue";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { Delete, Plus } from "@element-plus/icons-vue";
 | 
			
		||||
import { isImage, removeArrayItem } from "@/utils/libs";
 | 
			
		||||
import { GetFileIcon } from "@/store/system";
 | 
			
		||||
import { checkSession } from "@/store/cache";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  userId: Number,
 | 
			
		||||
});
 | 
			
		||||
const emits = defineEmits(['selected']);
 | 
			
		||||
const show = ref(false)
 | 
			
		||||
const scrollbarRef = ref(null)
 | 
			
		||||
const emits = defineEmits(["selected"]);
 | 
			
		||||
const show = ref(false);
 | 
			
		||||
const scrollbarRef = ref(null);
 | 
			
		||||
const fileData = reactive({
 | 
			
		||||
  items:[],
 | 
			
		||||
  items: [],
 | 
			
		||||
  page: 1,
 | 
			
		||||
  isLastPage: true,
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
const fetchFiles = (pageNo) => {
 | 
			
		||||
  if(pageNo === 1) show.value = true
 | 
			
		||||
  httpPost("/api/upload/list", { page: pageNo || 1, page_size: 30 }).then(res => {
 | 
			
		||||
    const { items, page, total_page } = res.data
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      show.value = true;
 | 
			
		||||
      httpPost("/api/upload/list", { page: pageNo || 1, page_size: 30 })
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          const { items, page, total_page } = res.data;
 | 
			
		||||
 | 
			
		||||
    if(page === 1){
 | 
			
		||||
      fileData.items = items
 | 
			
		||||
    }else{
 | 
			
		||||
      fileData.items = [...fileData.items, ...items]
 | 
			
		||||
    }
 | 
			
		||||
          if (page === 1) {
 | 
			
		||||
            fileData.items = items;
 | 
			
		||||
          } else {
 | 
			
		||||
            fileData.items = [...fileData.items, ...items];
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
    fileData.isLastPage = (page === total_page)
 | 
			
		||||
          fileData.isLastPage = page === total_page;
 | 
			
		||||
 | 
			
		||||
    if(!fileData.isLastPage){
 | 
			
		||||
      fileData.page = page + 1
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
          if (!fileData.isLastPage) {
 | 
			
		||||
            fileData.page = page + 1;
 | 
			
		||||
          }
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          showMessageError("获取文件列表失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
    .catch(() => {
 | 
			
		||||
      store.setShowLoginDialog(true);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// el-scrollbar 滚动回调
 | 
			
		||||
const onScroll = (options) => {
 | 
			
		||||
  const wrapRef = scrollbarRef.value.wrapRef
 | 
			
		||||
  scrollbarRef.value.moveY = wrapRef.scrollTop * 100 / wrapRef.clientHeight
 | 
			
		||||
  scrollbarRef.value.moveX = wrapRef.scrollLeft * 100 / wrapRef.clientWidth
 | 
			
		||||
  const poor = wrapRef.scrollHeight - wrapRef.clientHeight
 | 
			
		||||
  const wrapRef = scrollbarRef.value.wrapRef;
 | 
			
		||||
  scrollbarRef.value.moveY = (wrapRef.scrollTop * 100) / wrapRef.clientHeight;
 | 
			
		||||
  scrollbarRef.value.moveX = (wrapRef.scrollLeft * 100) / wrapRef.clientWidth;
 | 
			
		||||
  const poor = wrapRef.scrollHeight - wrapRef.clientHeight;
 | 
			
		||||
  // 判断滚动到底部 自动加载数据
 | 
			
		||||
  if (options.scrollTop + 2 >= poor && !fileData.isLastPage) {
 | 
			
		||||
    fetchFiles(fileData.page)
 | 
			
		||||
    fetchFiles(fileData.page);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const afterRead = (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) => {
 | 
			
		||||
    fileData.items.unshift(res.data)
 | 
			
		||||
    ElMessage.success({message: "上传成功", duration: 500})
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error('图片上传失败:' + e.message)
 | 
			
		||||
  })
 | 
			
		||||
  httpPost("/api/upload", formData)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      fileData.items.unshift(res.data);
 | 
			
		||||
      ElMessage.success({ message: "上传成功", duration: 500 });
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("图片上传失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const removeFile = (file) => {
 | 
			
		||||
  httpGet('/api/upload/remove?id=' + file.id).then(() => {
 | 
			
		||||
    fileData.items = removeArrayItem(fileData.items, file, (v1, v2) => {
 | 
			
		||||
      return v1.id === v2.id
 | 
			
		||||
  httpGet("/api/upload/remove?id=" + file.id)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      fileData.items = removeArrayItem(fileData.items, file, (v1, v2) => {
 | 
			
		||||
        return v1.id === v2.id;
 | 
			
		||||
      });
 | 
			
		||||
      ElMessage.success("文件删除成功!");
 | 
			
		||||
      fetchFiles(1);
 | 
			
		||||
    })
 | 
			
		||||
    ElMessage.success("文件删除成功!")
 | 
			
		||||
    fetchFiles(1)
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error('文件删除失败:' + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("文件删除失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const insertURL = (file) => {
 | 
			
		||||
  show.value = false
 | 
			
		||||
  show.value = false;
 | 
			
		||||
  // 如果是相对路径,处理成绝对路径
 | 
			
		||||
  if (file.url.indexOf("http") === -1) {
 | 
			
		||||
    file.url = location.protocol + "//" + location.host + file.url
 | 
			
		||||
    file.url = location.protocol + "//" + location.host + file.url;
 | 
			
		||||
  }
 | 
			
		||||
  emits('selected', file)
 | 
			
		||||
}
 | 
			
		||||
  emits("selected", file);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@@ -149,7 +150,7 @@ const insertURL = (file) => {
 | 
			
		||||
.file-select-box {
 | 
			
		||||
  .file-upload-img {
 | 
			
		||||
    .iconfont {
 | 
			
		||||
      font-size: 24px;
 | 
			
		||||
      font-size: 19px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -215,4 +216,4 @@ const insertURL = (file) => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="foot-container">
 | 
			
		||||
    <div class="footer">
 | 
			
		||||
      <div><span :style="{color:textColor}">{{copyRight}}</span></div>
 | 
			
		||||
      <div>
 | 
			
		||||
        <span>{{ copyRight }}</span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div v-if="!license.de_copy">
 | 
			
		||||
        <a :href="gitURL" target="_blank" :style="{color:textColor}">
 | 
			
		||||
        <a :href="gitURL" target="_blank">
 | 
			
		||||
          {{ title }} -
 | 
			
		||||
          {{ version }}
 | 
			
		||||
        </a>
 | 
			
		||||
@@ -12,37 +14,45 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import { httpGet } from "@/utils/http";
 | 
			
		||||
import { showMessageError } from "@/utils/dialog";
 | 
			
		||||
import { getLicenseInfo, getSystemInfo } from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {showMessageError} from "@/utils/dialog";
 | 
			
		||||
import {getLicenseInfo, getSystemInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const title = ref("")
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION)
 | 
			
		||||
const gitURL = ref(process.env.VUE_APP_GIT_URL)
 | 
			
		||||
const copyRight = ref('')
 | 
			
		||||
const license = ref({})
 | 
			
		||||
const title = ref("");
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION);
 | 
			
		||||
const gitURL = ref(process.env.VUE_APP_GIT_URL);
 | 
			
		||||
const copyRight = ref("");
 | 
			
		||||
const license = ref({});
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  textColor: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '#ffffff'
 | 
			
		||||
  },
 | 
			
		||||
    default: "#ffffff"
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 获取系统配置
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  title.value = res.data.title??process.env.VUE_APP_TITLE
 | 
			
		||||
  copyRight.value = res.data.copyright?res.data.copyright:'极客学长 © 2023 - '+new Date().getFullYear()+' All rights reserved.'
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showMessageError("获取系统配置失败:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
getSystemInfo()
 | 
			
		||||
  .then((res) => {
 | 
			
		||||
    title.value = res.data.title ?? process.env.VUE_APP_TITLE;
 | 
			
		||||
    copyRight.value =
 | 
			
		||||
      res.data.copyright.length > 1
 | 
			
		||||
        ? res.data.copyright
 | 
			
		||||
        : "极客学长 © 2023 - " +
 | 
			
		||||
          new Date().getFullYear() +
 | 
			
		||||
          " All rights reserved.";
 | 
			
		||||
  })
 | 
			
		||||
  .catch((e) => {
 | 
			
		||||
    showMessageError("获取系统配置失败:" + e.message);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
getLicenseInfo().then(res => {
 | 
			
		||||
  license.value = res.data
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  showMessageError("获取 License 失败:" + e.message)
 | 
			
		||||
})
 | 
			
		||||
getLicenseInfo()
 | 
			
		||||
  .then((res) => {
 | 
			
		||||
    license.value = res.data;
 | 
			
		||||
  })
 | 
			
		||||
  .catch((e) => {
 | 
			
		||||
    showMessageError("获取 License 失败:" + e.message);
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
@@ -53,6 +63,8 @@ getLicenseInfo().then(res => {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  display flex;
 | 
			
		||||
  justify-content center
 | 
			
		||||
  background: var(--theme-bg);
 | 
			
		||||
  margin-top -4px
 | 
			
		||||
 | 
			
		||||
  .footer {
 | 
			
		||||
    max-width 400px;
 | 
			
		||||
@@ -62,11 +74,15 @@ getLicenseInfo().then(res => {
 | 
			
		||||
    width 100%
 | 
			
		||||
 | 
			
		||||
    a {
 | 
			
		||||
      color:var(--text-color)
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        text-decoration underline
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    span{
 | 
			
		||||
      color:var(--text-color)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,76 +1,83 @@
 | 
			
		||||
<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
 | 
			
		||||
      >
 | 
			
		||||
        <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
 | 
			
		||||
                     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"/>
 | 
			
		||||
 | 
			
		||||
      <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"
 | 
			
		||||
      />
 | 
			
		||||
    </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) => {
 | 
			
		||||
    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 => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
  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;
 | 
			
		||||
      }
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
@@ -90,4 +97,4 @@ const fetchData = () => {
 | 
			
		||||
    color #20a0ff
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +1,22 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-dialog
 | 
			
		||||
      class="login-dialog"
 | 
			
		||||
      v-model="showDialog"
 | 
			
		||||
      :close-on-click-modal="true"
 | 
			
		||||
      :show-close="false"
 | 
			
		||||
      :before-close="close"
 | 
			
		||||
  >
 | 
			
		||||
    <template #header="{titleId, titleClass }">
 | 
			
		||||
      <div class="header">
 | 
			
		||||
        <div class="title" v-if="login">用户登录</div>
 | 
			
		||||
        <div class="title" v-else>用户注册</div>
 | 
			
		||||
        <div class="close-icon">
 | 
			
		||||
          <el-icon @click="close">
 | 
			
		||||
            <Close/>
 | 
			
		||||
          </el-icon>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
 | 
			
		||||
  <div class="login-dialog w-full">
 | 
			
		||||
    <div class="login-box" v-if="login">
 | 
			
		||||
      <el-form :model="data" label-width="120px" class="form">
 | 
			
		||||
      <el-form :model="data" class="form">
 | 
			
		||||
        <div class="block">
 | 
			
		||||
          <el-input placeholder="账号"
 | 
			
		||||
                    size="large"
 | 
			
		||||
                    v-model="data.username"
 | 
			
		||||
                    autocomplete="off">
 | 
			
		||||
          <el-input placeholder="账号" size="large" v-model="data.username" autocomplete="off">
 | 
			
		||||
            <template #prefix>
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <Iphone/>
 | 
			
		||||
                <Iphone />
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="block">
 | 
			
		||||
          <el-input placeholder="请输入密码(8-16位)"
 | 
			
		||||
                    maxlength="16" size="large"
 | 
			
		||||
                    v-model="data.password" show-password
 | 
			
		||||
                    autocomplete="off">
 | 
			
		||||
          <el-input placeholder="请输入密码(8-16位)" maxlength="16" size="large" v-model="data.password" show-password autocomplete="off">
 | 
			
		||||
            <template #prefix>
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <Lock/>
 | 
			
		||||
                <Lock />
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
@@ -48,45 +24,45 @@
 | 
			
		||||
 | 
			
		||||
        <el-row class="btn-row" :gutter="20">
 | 
			
		||||
          <el-col :span="24">
 | 
			
		||||
            <el-button class="login-btn" type="primary" size="large" @click="submitLogin">登录</el-button>
 | 
			
		||||
            <el-button class="login-btn" type="primary" size="large" @click="submitLogin">登 录</el-button>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
 | 
			
		||||
        <el-row>
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <div class="reg">
 | 
			
		||||
              还没有账号?
 | 
			
		||||
              <el-button type="primary" class="forget" size="small" @click="login = false">注册</el-button>
 | 
			
		||||
        <div class="w-full">
 | 
			
		||||
          <div class="text flex justify-center items-center pt-3 text-sm">
 | 
			
		||||
            还没有账号?
 | 
			
		||||
            <el-button size="small" @click="login = false">注册</el-button>
 | 
			
		||||
 | 
			
		||||
              <el-button type="info" class="forget" size="small" @click="showResetPass = true">忘记密码?</el-button>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-col>
 | 
			
		||||
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <div class="c-login" v-if="wechatLoginURL !== ''">
 | 
			
		||||
              <div class="text">其他登录方式:</div>
 | 
			
		||||
              <div class="login-type">
 | 
			
		||||
                <a class="wechat-login" :href="wechatLoginURL"  @click="setRoute(router.currentRoute.value.path)"><i class="iconfont icon-wechat"></i></a>
 | 
			
		||||
            <el-button type="info" class="forget" size="small" @click="showResetPass = true">忘记密码?</el-button>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div v-if="wechatLoginURL !== ''">
 | 
			
		||||
            <el-divider>
 | 
			
		||||
              <div class="text-center">其他登录方式</div>
 | 
			
		||||
            </el-divider>
 | 
			
		||||
            <div class="c-login flex justify-center">
 | 
			
		||||
              <!-- <div class="login-type mr-2">
 | 
			
		||||
            <a class="wechat-login" :href="wechatLoginURL" @click="setRoute(router.currentRoute.value.path)"><i class="iconfont icon-wechat"></i></a>
 | 
			
		||||
          </div> -->
 | 
			
		||||
              <div class="p-2 w-full">
 | 
			
		||||
                <el-button type="success" class="w-full" size="large" :href="wechatLoginURL" @click="setRoute(router.currentRoute.value.path)"
 | 
			
		||||
                  ><i class="iconfont icon-wechat mr-2"></i> 微信登录
 | 
			
		||||
                </el-button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-form>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="register-box" v-else>
 | 
			
		||||
    <div class="register-box w-full" v-else>
 | 
			
		||||
      <el-form :model="data" class="form" v-if="enableRegister">
 | 
			
		||||
        <el-tabs v-model="activeName" class="demo-tabs">
 | 
			
		||||
          <el-tab-pane label="手机注册" name="mobile" v-if="enableMobile">
 | 
			
		||||
            <div class="block">
 | 
			
		||||
              <el-input placeholder="手机号码"
 | 
			
		||||
                        size="large"
 | 
			
		||||
                        v-model="data.mobile"
 | 
			
		||||
                        maxlength="11"
 | 
			
		||||
                        autocomplete="off">
 | 
			
		||||
              <el-input placeholder="手机号码" size="large" v-model="data.mobile" maxlength="11" autocomplete="off">
 | 
			
		||||
                <template #prefix>
 | 
			
		||||
                  <el-icon>
 | 
			
		||||
                    <Iphone/>
 | 
			
		||||
                    <Iphone />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-input>
 | 
			
		||||
@@ -94,32 +70,26 @@
 | 
			
		||||
            <div class="block">
 | 
			
		||||
              <el-row :gutter="10">
 | 
			
		||||
                <el-col :span="12">
 | 
			
		||||
                  <el-input placeholder="验证码"
 | 
			
		||||
                            size="large" maxlength="30"
 | 
			
		||||
                            v-model="data.code"
 | 
			
		||||
                            autocomplete="off">
 | 
			
		||||
                  <el-input placeholder="验证码" size="large" maxlength="30" v-model="data.code" autocomplete="off">
 | 
			
		||||
                    <template #prefix>
 | 
			
		||||
                      <el-icon>
 | 
			
		||||
                        <Checked/>
 | 
			
		||||
                        <Checked />
 | 
			
		||||
                      </el-icon>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-input>
 | 
			
		||||
                </el-col>
 | 
			
		||||
                <el-col :span="12">
 | 
			
		||||
                  <send-msg size="large" :receiver="data.mobile" type="mobile"/>
 | 
			
		||||
                  <send-msg size="large" :receiver="data.mobile" type="mobile" />
 | 
			
		||||
                </el-col>
 | 
			
		||||
              </el-row>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-tab-pane>
 | 
			
		||||
          <el-tab-pane label="邮箱注册" name="email" v-if="enableEmail">
 | 
			
		||||
            <div class="block">
 | 
			
		||||
              <el-input placeholder="邮箱地址"
 | 
			
		||||
                        size="large"
 | 
			
		||||
                        v-model="data.email"
 | 
			
		||||
                        autocomplete="off">
 | 
			
		||||
              <el-input placeholder="邮箱地址" size="large" v-model="data.email" autocomplete="off">
 | 
			
		||||
                <template #prefix>
 | 
			
		||||
                  <el-icon>
 | 
			
		||||
                    <Message/>
 | 
			
		||||
                    <Message />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-input>
 | 
			
		||||
@@ -127,32 +97,26 @@
 | 
			
		||||
            <div class="block">
 | 
			
		||||
              <el-row :gutter="10">
 | 
			
		||||
                <el-col :span="12">
 | 
			
		||||
                  <el-input placeholder="验证码"
 | 
			
		||||
                            size="large" maxlength="30"
 | 
			
		||||
                            v-model="data.code"
 | 
			
		||||
                            autocomplete="off">
 | 
			
		||||
                  <el-input placeholder="验证码" size="large" maxlength="30" v-model="data.code" autocomplete="off">
 | 
			
		||||
                    <template #prefix>
 | 
			
		||||
                      <el-icon>
 | 
			
		||||
                        <Checked/>
 | 
			
		||||
                        <Checked />
 | 
			
		||||
                      </el-icon>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-input>
 | 
			
		||||
                </el-col>
 | 
			
		||||
                <el-col :span="12">
 | 
			
		||||
                  <send-msg size="large" :receiver="data.email" type="email"/>
 | 
			
		||||
                  <send-msg size="large" :receiver="data.email" type="email" />
 | 
			
		||||
                </el-col>
 | 
			
		||||
              </el-row>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-tab-pane>
 | 
			
		||||
          <el-tab-pane label="用户名注册" name="username" v-if="enableUser">
 | 
			
		||||
            <div class="block">
 | 
			
		||||
              <el-input placeholder="用户名"
 | 
			
		||||
                        size="large"
 | 
			
		||||
                        v-model="data.username"
 | 
			
		||||
                        autocomplete="off">
 | 
			
		||||
              <el-input placeholder="用户名" size="large" v-model="data.username" autocomplete="off">
 | 
			
		||||
                <template #prefix>
 | 
			
		||||
                  <el-icon>
 | 
			
		||||
                    <Iphone/>
 | 
			
		||||
                    <Iphone />
 | 
			
		||||
                  </el-icon>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-input>
 | 
			
		||||
@@ -161,55 +125,43 @@
 | 
			
		||||
        </el-tabs>
 | 
			
		||||
 | 
			
		||||
        <div class="block">
 | 
			
		||||
          <el-input placeholder="请输入密码(8-16位)"
 | 
			
		||||
                    maxlength="16" size="large"
 | 
			
		||||
                    v-model="data.password" show-password
 | 
			
		||||
                    autocomplete="off">
 | 
			
		||||
          <el-input placeholder="请输入密码(8-16位)" maxlength="16" size="large" v-model="data.password" show-password autocomplete="off">
 | 
			
		||||
            <template #prefix>
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <Lock/>
 | 
			
		||||
                <Lock />
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="block">
 | 
			
		||||
          <el-input placeholder="重复密码(8-16位)"
 | 
			
		||||
                    size="large" maxlength="16" v-model="data.repass" show-password
 | 
			
		||||
                    autocomplete="off">
 | 
			
		||||
          <el-input placeholder="重复密码(8-16位)" size="large" maxlength="16" v-model="data.repass" show-password autocomplete="off">
 | 
			
		||||
            <template #prefix>
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <Lock/>
 | 
			
		||||
                <Lock />
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="block">
 | 
			
		||||
          <el-input placeholder="邀请码(可选)"
 | 
			
		||||
                    size="large"
 | 
			
		||||
                    v-model="data.invite_code"
 | 
			
		||||
                    autocomplete="off">
 | 
			
		||||
          <el-input placeholder="邀请码(可选)" size="large" v-model="data.invite_code" autocomplete="off">
 | 
			
		||||
            <template #prefix>
 | 
			
		||||
              <el-icon>
 | 
			
		||||
                <Message/>
 | 
			
		||||
                <Message />
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-input>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <el-row class="btn-row" :gutter="20">
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <el-button class="login-btn" type="primary" size="large" @click="submitRegister">注册</el-button>
 | 
			
		||||
          </el-col>
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <div class="text">
 | 
			
		||||
              已有账号?
 | 
			
		||||
              <el-tag @click="login = true">登录</el-tag>
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        <div class="w-full">
 | 
			
		||||
          <el-button class="login-btn w-full" type="primary" size="large" @click="submitRegister">注 册</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        </el-row>
 | 
			
		||||
        <div class="text text-sm flex justify-center items-center w-full pt-3">
 | 
			
		||||
          已有账号?
 | 
			
		||||
          <el-button size="small" @click="login = true">登录</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-form>
 | 
			
		||||
 | 
			
		||||
      <div class="tip-result" v-else>
 | 
			
		||||
@@ -224,45 +176,47 @@
 | 
			
		||||
 | 
			
		||||
          <el-col :span="12">
 | 
			
		||||
            <div class="wechat-card">
 | 
			
		||||
              <el-image :src="wxImg"/>
 | 
			
		||||
              <el-image :src="wxImg" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </el-col>
 | 
			
		||||
        </el-row>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <captcha v-if="enableVerify" @success="submit" ref="captchaRef" />
 | 
			
		||||
 | 
			
		||||
    <captcha v-if="enableVerify" @success="submit" ref="captchaRef"/>
 | 
			
		||||
 | 
			
		||||
    <reset-pass @hide="showResetPass = false" :show="showResetPass"/>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
    <reset-pass @hide="showResetPass = false" :show="showResetPass" />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref, watch} from "vue"
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {setUserToken} from "@/store/session";
 | 
			
		||||
import {validateEmail, validateMobile} from "@/utils/validate";
 | 
			
		||||
import {Checked, Close, Iphone, Lock, Message} from "@element-plus/icons-vue";
 | 
			
		||||
import { onMounted, ref, watch } from "vue";
 | 
			
		||||
import { httpGet, httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { setUserToken } from "@/store/session";
 | 
			
		||||
import { validateEmail, validateMobile } from "@/utils/validate";
 | 
			
		||||
import { Checked, Close, Iphone, Lock, Message } from "@element-plus/icons-vue";
 | 
			
		||||
import SendMsg from "@/components/SendMsg.vue";
 | 
			
		||||
import {arrayContains} from "@/utils/libs";
 | 
			
		||||
import {getSystemInfo} from "@/store/cache";
 | 
			
		||||
import { arrayContains } from "@/utils/libs";
 | 
			
		||||
import { getSystemInfo } from "@/store/cache";
 | 
			
		||||
import Captcha from "@/components/Captcha.vue";
 | 
			
		||||
import ResetPass from "@/components/ResetPass.vue";
 | 
			
		||||
import {setRoute} from "@/store/system";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { setRoute } from "@/store/system";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: Boolean,
 | 
			
		||||
});
 | 
			
		||||
const showDialog = ref(false)
 | 
			
		||||
watch(() => props.show, (newValue) => {
 | 
			
		||||
  showDialog.value = newValue
 | 
			
		||||
})
 | 
			
		||||
const showDialog = ref(false);
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.show,
 | 
			
		||||
  (newValue) => {
 | 
			
		||||
    showDialog.value = newValue;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const login = ref(true)
 | 
			
		||||
const login = ref(true);
 | 
			
		||||
const data = ref({
 | 
			
		||||
  username: process.env.VUE_APP_USER,
 | 
			
		||||
  password: process.env.VUE_APP_PASS,
 | 
			
		||||
@@ -270,193 +224,171 @@ const data = ref({
 | 
			
		||||
  email: "",
 | 
			
		||||
  repass: "",
 | 
			
		||||
  code: "",
 | 
			
		||||
  invite_code: ""
 | 
			
		||||
})
 | 
			
		||||
const enableMobile = ref(false)
 | 
			
		||||
const enableEmail = ref(false)
 | 
			
		||||
const enableUser = ref(false)
 | 
			
		||||
const enableRegister = ref(true)
 | 
			
		||||
const wechatLoginURL = ref('')
 | 
			
		||||
const activeName = ref("")
 | 
			
		||||
const wxImg = ref("/images/wx.png")
 | 
			
		||||
const captchaRef = ref(null)
 | 
			
		||||
  invite_code: "",
 | 
			
		||||
});
 | 
			
		||||
const enableMobile = ref(false);
 | 
			
		||||
const enableEmail = ref(false);
 | 
			
		||||
const enableUser = ref(false);
 | 
			
		||||
const enableRegister = ref(true);
 | 
			
		||||
const wechatLoginURL = ref("");
 | 
			
		||||
const activeName = ref("");
 | 
			
		||||
const wxImg = ref("/images/wx.png");
 | 
			
		||||
const captchaRef = ref(null);
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const emits = defineEmits(['hide', 'success']);
 | 
			
		||||
const action = ref("login")
 | 
			
		||||
const enableVerify = ref(false)
 | 
			
		||||
const showResetPass = ref(false)
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
// 是否需要验证码,输入一次密码错之后就要验证码
 | 
			
		||||
const needVerify = ref(false)
 | 
			
		||||
const emits = defineEmits(["hide", "success"]);
 | 
			
		||||
const action = ref("login");
 | 
			
		||||
const enableVerify = ref(false);
 | 
			
		||||
const showResetPass = ref(false);
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  const returnURL = `${location.protocol}//${location.host}/login/callback?action=login`
 | 
			
		||||
  httpGet("/api/user/clogin?return_url="+returnURL).then(res => {
 | 
			
		||||
    wechatLoginURL.value = res.data.url
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    console.log(e.message)
 | 
			
		||||
  })
 | 
			
		||||
  const returnURL = `${location.protocol}//${location.host}/login/callback?action=login`;
 | 
			
		||||
  httpGet("/api/user/clogin?return_url=" + returnURL)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      wechatLoginURL.value = res.data.url;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      console.log(e.message);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    if (res.data) {
 | 
			
		||||
      const registerWays = res.data['register_ways']
 | 
			
		||||
      if (arrayContains(registerWays, "username")) {
 | 
			
		||||
        enableUser.value = true
 | 
			
		||||
        activeName.value = 'username'
 | 
			
		||||
  getSystemInfo()
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      if (res.data) {
 | 
			
		||||
        const registerWays = res.data["register_ways"];
 | 
			
		||||
        if (arrayContains(registerWays, "username")) {
 | 
			
		||||
          enableUser.value = true;
 | 
			
		||||
          activeName.value = "username";
 | 
			
		||||
        }
 | 
			
		||||
        if (arrayContains(registerWays, "email")) {
 | 
			
		||||
          enableEmail.value = true;
 | 
			
		||||
          activeName.value = "email";
 | 
			
		||||
        }
 | 
			
		||||
        if (arrayContains(registerWays, "mobile")) {
 | 
			
		||||
          enableMobile.value = true;
 | 
			
		||||
          activeName.value = "mobile";
 | 
			
		||||
        }
 | 
			
		||||
        // 是否启用注册
 | 
			
		||||
        enableRegister.value = res.data["enabled_register"];
 | 
			
		||||
        // 使用后台上传的客服微信二维码
 | 
			
		||||
        if (res.data["wechat_card_url"] !== "") {
 | 
			
		||||
          wxImg.value = res.data["wechat_card_url"];
 | 
			
		||||
        }
 | 
			
		||||
        enableVerify.value = res.data["enabled_verify"];
 | 
			
		||||
      }
 | 
			
		||||
      if (arrayContains(registerWays, "email")) {
 | 
			
		||||
        enableEmail.value = true
 | 
			
		||||
        activeName.value = 'email'
 | 
			
		||||
      }
 | 
			
		||||
      if (arrayContains(registerWays, "mobile")) {
 | 
			
		||||
        enableMobile.value = true
 | 
			
		||||
        activeName.value = 'mobile'
 | 
			
		||||
      }
 | 
			
		||||
      // 是否启用注册
 | 
			
		||||
      enableRegister.value = res.data['enabled_register']
 | 
			
		||||
      // 使用后台上传的客服微信二维码
 | 
			
		||||
      if (res.data['wechat_card_url'] !== '') {
 | 
			
		||||
        wxImg.value = res.data['wechat_card_url']
 | 
			
		||||
      }
 | 
			
		||||
      enableVerify.value = res.data['enabled_verify']
 | 
			
		||||
    }
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取系统配置失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const submit = (verifyData) => {
 | 
			
		||||
  if (action.value === "login") {
 | 
			
		||||
      doLogin(verifyData)
 | 
			
		||||
    doLogin(verifyData);
 | 
			
		||||
  } else if (action.value === "register") {
 | 
			
		||||
    doRegister(verifyData)
 | 
			
		||||
    doRegister(verifyData);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 登录操作
 | 
			
		||||
const submitLogin = () => {
 | 
			
		||||
  if (data.value.username === '') {
 | 
			
		||||
    return ElMessage.error('请输入用户名');
 | 
			
		||||
  if (!data.value.username) {
 | 
			
		||||
    return ElMessage.error("请输入用户名");
 | 
			
		||||
  }
 | 
			
		||||
  if (data.value.password === '') {
 | 
			
		||||
    return ElMessage.error('请输入密码');
 | 
			
		||||
  if (!data.value.password) {
 | 
			
		||||
    return ElMessage.error("请输入密码");
 | 
			
		||||
  }
 | 
			
		||||
  if (enableVerify.value && needVerify.value) {
 | 
			
		||||
    captchaRef.value.loadCaptcha()
 | 
			
		||||
    action.value = "login"
 | 
			
		||||
  if (enableVerify.value) {
 | 
			
		||||
    captchaRef.value.loadCaptcha();
 | 
			
		||||
    action.value = "login";
 | 
			
		||||
  } else {
 | 
			
		||||
    doLogin({})
 | 
			
		||||
    doLogin({});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const doLogin = (verifyData) => {
 | 
			
		||||
  data.value.key = verifyData.key
 | 
			
		||||
  data.value.dots = verifyData.dots
 | 
			
		||||
  data.value.x = verifyData.x
 | 
			
		||||
  httpPost('/api/user/login', data.value).then((res) => {
 | 
			
		||||
    setUserToken(res.data.token)
 | 
			
		||||
    store.setIsLogin(true)
 | 
			
		||||
    ElMessage.success("登录成功!")
 | 
			
		||||
    emits("hide")
 | 
			
		||||
    emits('success')
 | 
			
		||||
    needVerify.value = false
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error('登录失败,' + e.message)
 | 
			
		||||
    needVerify.value = true
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
  data.value.key = verifyData.key;
 | 
			
		||||
  data.value.dots = verifyData.dots;
 | 
			
		||||
  data.value.x = verifyData.x;
 | 
			
		||||
  httpPost("/api/user/login", data.value)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      setUserToken(res.data.token);
 | 
			
		||||
      store.setIsLogin(true);
 | 
			
		||||
      ElMessage.success("登录成功!");
 | 
			
		||||
      emits("hide");
 | 
			
		||||
      emits("success");
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("登录失败," + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 注册操作
 | 
			
		||||
const submitRegister = () => {
 | 
			
		||||
  if (activeName.value === 'username' && data.value.username === '') {
 | 
			
		||||
    return ElMessage.error('请输入用户名');
 | 
			
		||||
  if (activeName.value === "username" && data.value.username === "") {
 | 
			
		||||
    return ElMessage.error("请输入用户名");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (activeName.value === 'mobile' && !validateMobile(data.value.mobile)) {
 | 
			
		||||
    return ElMessage.error('请输入合法的手机号');
 | 
			
		||||
  if (activeName.value === "mobile" && !validateMobile(data.value.mobile)) {
 | 
			
		||||
    return ElMessage.error("请输入合法的手机号");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (activeName.value === 'email' && !validateEmail(data.value.email)) {
 | 
			
		||||
    return ElMessage.error('请输入合法的邮箱地址');
 | 
			
		||||
  if (activeName.value === "email" && !validateEmail(data.value.email)) {
 | 
			
		||||
    return ElMessage.error("请输入合法的邮箱地址");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (data.value.password.length < 8) {
 | 
			
		||||
    return ElMessage.error('密码的长度为8-16个字符');
 | 
			
		||||
    return ElMessage.error("密码的长度为8-16个字符");
 | 
			
		||||
  }
 | 
			
		||||
  if (data.value.repass !== data.value.password) {
 | 
			
		||||
    return ElMessage.error('两次输入密码不一致');
 | 
			
		||||
    return ElMessage.error("两次输入密码不一致");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ((activeName.value === 'mobile' || activeName.value === 'email') && data.value.code === '') {
 | 
			
		||||
    return ElMessage.error('请输入验证码');
 | 
			
		||||
  if ((activeName.value === "mobile" || activeName.value === "email") && data.value.code === "") {
 | 
			
		||||
    return ElMessage.error("请输入验证码");
 | 
			
		||||
  }
 | 
			
		||||
  if (enableVerify.value && activeName.value === 'username') {
 | 
			
		||||
    captchaRef.value.loadCaptcha()
 | 
			
		||||
    action.value = "register"
 | 
			
		||||
  if (enableVerify.value && activeName.value === "username") {
 | 
			
		||||
    captchaRef.value.loadCaptcha();
 | 
			
		||||
    action.value = "register";
 | 
			
		||||
  } else {
 | 
			
		||||
    doRegister({})
 | 
			
		||||
    doRegister({});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const doRegister = (verifyData) => {
 | 
			
		||||
  data.value.key = verifyData.key
 | 
			
		||||
  data.value.dots = verifyData.dots
 | 
			
		||||
  data.value.x = verifyData.x
 | 
			
		||||
  data.value.reg_way = activeName.value
 | 
			
		||||
  httpPost('/api/user/register', data.value).then((res) => {
 | 
			
		||||
    setUserToken(res.data.token)
 | 
			
		||||
    ElMessage.success({
 | 
			
		||||
      "message": "注册成功!",
 | 
			
		||||
      onClose: () => {
 | 
			
		||||
        emits("hide")
 | 
			
		||||
        emits('success')
 | 
			
		||||
      },
 | 
			
		||||
      duration: 1000
 | 
			
		||||
  data.value.key = verifyData.key;
 | 
			
		||||
  data.value.dots = verifyData.dots;
 | 
			
		||||
  data.value.x = verifyData.x;
 | 
			
		||||
  data.value.reg_way = activeName.value;
 | 
			
		||||
  httpPost("/api/user/register", data.value)
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      setUserToken(res.data.token);
 | 
			
		||||
      ElMessage.success({
 | 
			
		||||
        message: "注册成功!",
 | 
			
		||||
        onClose: () => {
 | 
			
		||||
          emits("hide");
 | 
			
		||||
          emits("success");
 | 
			
		||||
        },
 | 
			
		||||
        duration: 1000,
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error('注册失败,' + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const close = function () {
 | 
			
		||||
  emits('hide', false)
 | 
			
		||||
  login.value = true
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("注册失败," + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
.login-dialog {
 | 
			
		||||
  border-radius 10px
 | 
			
		||||
  max-width 600px
 | 
			
		||||
 | 
			
		||||
  .header {
 | 
			
		||||
    position relative
 | 
			
		||||
 | 
			
		||||
    .title {
 | 
			
		||||
      padding 0
 | 
			
		||||
      font-size 18px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .close-icon {
 | 
			
		||||
      cursor pointer
 | 
			
		||||
      position absolute
 | 
			
		||||
      right 0
 | 
			
		||||
      top 0
 | 
			
		||||
      font-weight normal
 | 
			
		||||
      font-size 20px
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        color #20a0ff
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  .el-tabs__nav {
 | 
			
		||||
    display flex
 | 
			
		||||
    width 100%
 | 
			
		||||
    justify-content space-between
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  .el-dialog__body {
 | 
			
		||||
    padding 10px 20px 20px 20px
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .form {
 | 
			
		||||
    .block {
 | 
			
		||||
      margin-bottom 10px
 | 
			
		||||
@@ -466,6 +398,7 @@ const close = function () {
 | 
			
		||||
      display flex
 | 
			
		||||
 | 
			
		||||
      .login-btn {
 | 
			
		||||
        font-size 16px
 | 
			
		||||
        width 100%
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -491,7 +424,6 @@ const close = function () {
 | 
			
		||||
        align-items: center;
 | 
			
		||||
      }
 | 
			
		||||
      .login-type {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        display flex
 | 
			
		||||
        justify-content center
 | 
			
		||||
 | 
			
		||||
@@ -507,14 +439,8 @@ const close = function () {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .reg {
 | 
			
		||||
      height 50px
 | 
			
		||||
      display flex
 | 
			
		||||
      align-items center
 | 
			
		||||
 | 
			
		||||
      .el-button {
 | 
			
		||||
        margin-left 10px
 | 
			
		||||
      }
 | 
			
		||||
    .text {
 | 
			
		||||
      color var(--el-text-color-primary)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -525,4 +451,4 @@ const close = function () {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
    })
 | 
			
		||||
  new RealtimeClient({
 | 
			
		||||
    url: `${host}/api/realtime`,
 | 
			
		||||
    apiKey: getUserToken(),
 | 
			
		||||
    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,62 +138,66 @@ 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;
 | 
			
		||||
  try {
 | 
			
		||||
   const trackSampleOffset = await wavStreamPlayer.value.interrupt();
 | 
			
		||||
   if (trackSampleOffset?.trackId) {
 | 
			
		||||
     const { trackId, offset } = trackSampleOffset;
 | 
			
		||||
     client.value.cancelResponse(trackId, offset);
 | 
			
		||||
   }
 | 
			
		||||
   await wavRecorder.value.record((data) => client.value.appendInputAudio(data.mono));
 | 
			
		||||
    const trackSampleOffset = await wavStreamPlayer.value.interrupt();
 | 
			
		||||
    if (trackSampleOffset?.trackId) {
 | 
			
		||||
      const { trackId, offset } = trackSampleOffset;
 | 
			
		||||
      client.value.cancelResponse(trackId, offset);
 | 
			
		||||
    }
 | 
			
		||||
    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')
 | 
			
		||||
              : { values: new Float32Array([0]) };
 | 
			
		||||
          WavRenderer.drawBars(canvas, ctx, result.values, '#0099ff', 10, 0, 8);
 | 
			
		||||
            ? wavRecorder.value.getFrequencies("voice")
 | 
			
		||||
            : { values: new Float32Array([0]) };
 | 
			
		||||
          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')
 | 
			
		||||
              : { values: new Float32Array([0]) };
 | 
			
		||||
          WavRenderer.drawBars(canvas, ctx, result.values, '#009900', 10, 0, 8);
 | 
			
		||||
            ? wavStreamPlayer.value.getFrequencies("voice")
 | 
			
		||||
            : { values: new Float32Array([0]) };
 | 
			
		||||
          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>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
  >
 | 
			
		||||
    <div class="form" id="bind-mobile-form">
 | 
			
		||||
      <el-form :model="form">
 | 
			
		||||
        <el-form-item label="兑换码">
 | 
			
		||||
        <el-form-item>
 | 
			
		||||
          <el-input v-model="form.code"/>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,63 +1,45 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="reset-pass">
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="showDialog"
 | 
			
		||||
        :close-on-click-modal="true"
 | 
			
		||||
        width="540px"
 | 
			
		||||
        :before-close="close"
 | 
			
		||||
        :title="title"
 | 
			
		||||
        class="reset-pass-dialog"
 | 
			
		||||
    >
 | 
			
		||||
    <el-dialog v-model="showDialog" :close-on-click-modal="true" width="500px" :before-close="close" :title="title" class="reset-pass-dialog">
 | 
			
		||||
      <div class="form">
 | 
			
		||||
        <el-form :model="form" label-width="80px" label-position="left">
 | 
			
		||||
          <el-tabs v-model="form.type" class="demo-tabs">
 | 
			
		||||
            <el-tab-pane label="手机号验证" name="mobile">
 | 
			
		||||
              <el-form-item label="手机号">
 | 
			
		||||
                <el-input v-model="form.mobile" placeholder="请输入手机号"/>
 | 
			
		||||
                <el-input v-model="form.mobile" placeholder="请输入手机号" />
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
              <el-form-item label="验证码">
 | 
			
		||||
                <el-row class="code-row">
 | 
			
		||||
                  <el-col :span="16">
 | 
			
		||||
                    <el-input v-model="form.code" maxlength="6"/>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                  <el-col :span="8" class="send-button">
 | 
			
		||||
                    <send-msg size="" :receiver="form.mobile" type="mobile"/>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                </el-row>
 | 
			
		||||
                <div class="flex">
 | 
			
		||||
                  <el-input v-model="form.code" maxlength="6" class="mr-2 w-1/2" />
 | 
			
		||||
                  <send-msg size="" :receiver="form.mobile" type="mobile" />
 | 
			
		||||
                </div>
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
 | 
			
		||||
            </el-tab-pane>
 | 
			
		||||
            <el-tab-pane label="邮箱验证" name="email">
 | 
			
		||||
              <el-form-item label="邮箱地址">
 | 
			
		||||
                <el-input v-model="form.email" placeholder="请输入邮箱地址"/>
 | 
			
		||||
                <el-input v-model="form.email" placeholder="请输入邮箱地址" />
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
              <el-form-item label="验证码">
 | 
			
		||||
                <el-row class="code-row">
 | 
			
		||||
                  <el-col :span="16">
 | 
			
		||||
                    <el-input v-model="form.code" maxlength="6"/>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                  <el-col :span="8" class="send-button">
 | 
			
		||||
                    <send-msg size="" :receiver="form.email" type="email"/>
 | 
			
		||||
                  </el-col>
 | 
			
		||||
                </el-row>
 | 
			
		||||
                <div class="flex">
 | 
			
		||||
                  <el-input v-model="form.code" maxlength="6" class="mr-2 w-1/2" />
 | 
			
		||||
                  <send-msg size="" :receiver="form.email" type="email" />
 | 
			
		||||
                </div>
 | 
			
		||||
              </el-form-item>
 | 
			
		||||
            </el-tab-pane>
 | 
			
		||||
          </el-tabs>
 | 
			
		||||
 | 
			
		||||
          <el-form-item label="新密码">
 | 
			
		||||
            <el-input v-model="form.password" type="password"/>
 | 
			
		||||
            <el-input v-model="form.password" type="password" />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
          <el-form-item label="重复密码">
 | 
			
		||||
            <el-input v-model="form.repass" type="password"/>
 | 
			
		||||
            <el-input v-model="form.repass" type="password" />
 | 
			
		||||
          </el-form-item>
 | 
			
		||||
        </el-form>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <template #footer>
 | 
			
		||||
        <div class="dialog-footer">
 | 
			
		||||
          <el-button type="primary" @click="save" round>
 | 
			
		||||
            重置密码
 | 
			
		||||
          </el-button>
 | 
			
		||||
          <el-button type="primary" @click="save" round> 重置密码 </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
@@ -65,35 +47,35 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {computed, ref} from "vue";
 | 
			
		||||
import { computed, ref } from "vue";
 | 
			
		||||
import SendMsg from "@/components/SendMsg.vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {validateEmail, validateMobile} from "@/utils/validate";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { httpPost } from "@/utils/http";
 | 
			
		||||
import { validateEmail, validateMobile } from "@/utils/validate";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: Boolean,
 | 
			
		||||
  mobile: String
 | 
			
		||||
  mobile: String,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const showDialog = computed(() => {
 | 
			
		||||
  return props.show
 | 
			
		||||
})
 | 
			
		||||
  return props.show;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const title = ref('重置密码')
 | 
			
		||||
const title = ref("重置密码");
 | 
			
		||||
const form = ref({
 | 
			
		||||
  mobile: '',
 | 
			
		||||
  email: '',
 | 
			
		||||
  type: 'mobile',
 | 
			
		||||
  code: '',
 | 
			
		||||
  password: '',
 | 
			
		||||
  repass: ''
 | 
			
		||||
})
 | 
			
		||||
  mobile: "",
 | 
			
		||||
  email: "",
 | 
			
		||||
  type: "mobile",
 | 
			
		||||
  code: "",
 | 
			
		||||
  password: "",
 | 
			
		||||
  repass: "",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['hide']);
 | 
			
		||||
const emits = defineEmits(["hide"]);
 | 
			
		||||
 | 
			
		||||
const save = () => {
 | 
			
		||||
  if (form.value.code === '') {
 | 
			
		||||
  if (form.value.code === "") {
 | 
			
		||||
    return ElMessage.error("请输入验证码");
 | 
			
		||||
  }
 | 
			
		||||
  if (form.value.password.length < 8) {
 | 
			
		||||
@@ -103,18 +85,22 @@ const save = () => {
 | 
			
		||||
    return ElMessage.error("两次输入密码不一致");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  httpPost('/api/user/resetPass', form.value).then(() => {
 | 
			
		||||
    ElMessage.success({
 | 
			
		||||
      message: '重置密码成功', duration: 1000, onClose: () => emits('hide', false)
 | 
			
		||||
  httpPost("/api/user/resetPass", form.value)
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success({
 | 
			
		||||
        message: "重置密码成功",
 | 
			
		||||
        duration: 1000,
 | 
			
		||||
        onClose: () => emits("hide", false),
 | 
			
		||||
      });
 | 
			
		||||
    })
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("重置密码失败:" + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("重置密码失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const close = function () {
 | 
			
		||||
  emits('hide', false);
 | 
			
		||||
}
 | 
			
		||||
  emits("hide", false);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@@ -140,5 +126,4 @@ const close = function () {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										172
									
								
								web/src/components/SdTaskView.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,172 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-dialog v-model="show" :fullscreen="true" @close="close" style="--el-dialog-border-radius: 0px">
 | 
			
		||||
    <template #header>
 | 
			
		||||
      <div class="header">
 | 
			
		||||
        <h3 style="color: var(--text-theme-color)">绘画任务详情</h3>
 | 
			
		||||
      </div>
 | 
			
		||||
    </template>
 | 
			
		||||
    <el-row :gutter="20">
 | 
			
		||||
      <el-col :span="16">
 | 
			
		||||
        <div class="img-container">
 | 
			
		||||
          <el-image :src="item['img_url']" fit="contain">
 | 
			
		||||
            <template #placeholder>
 | 
			
		||||
              <div class="image-slot">正在加载图片</div>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #error>
 | 
			
		||||
              <div class="image-slot">
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <i class="iconfont icon-image"></i>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-image>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :span="8">
 | 
			
		||||
        <div class="task-info">
 | 
			
		||||
          <div class="info-line">
 | 
			
		||||
            <el-divider> 正向提示词 </el-divider>
 | 
			
		||||
            <div class="prompt">
 | 
			
		||||
              <span>{{ item.prompt }}</span>
 | 
			
		||||
              <el-icon class="copy-prompt-wall" :data-clipboard-text="item.prompt">
 | 
			
		||||
                <i class="iconfont icon-copy"></i>
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="info-line">
 | 
			
		||||
            <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">
 | 
			
		||||
                <i class="iconfont icon-copy"></i>
 | 
			
		||||
              </el-icon>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="info-line">
 | 
			
		||||
            <div class="wrapper">
 | 
			
		||||
              <label>采样方法:</label>
 | 
			
		||||
              <div class="item-value">{{ item.params.sampler }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="info-line">
 | 
			
		||||
            <div class="wrapper">
 | 
			
		||||
              <label>图片尺寸:</label>
 | 
			
		||||
              <div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="info-line">
 | 
			
		||||
            <div class="wrapper">
 | 
			
		||||
              <label>迭代步数:</label>
 | 
			
		||||
              <div class="item-value">{{ item.params.steps }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="info-line">
 | 
			
		||||
            <div class="wrapper">
 | 
			
		||||
              <label>引导系数:</label>
 | 
			
		||||
              <div class="item-value">{{ item.params.cfg_scale }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="info-line">
 | 
			
		||||
            <div class="wrapper">
 | 
			
		||||
              <label>随机因子:</label>
 | 
			
		||||
              <div class="item-value">{{ item.params.seed }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div v-if="item.params.hd_fix">
 | 
			
		||||
            <el-divider> 高清修复 </el-divider>
 | 
			
		||||
            <div class="info-line">
 | 
			
		||||
              <div class="wrapper">
 | 
			
		||||
                <label>重绘幅度:</label>
 | 
			
		||||
                <div class="item-value">{{ item.params.hd_redraw_rate }}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="info-line">
 | 
			
		||||
              <div class="wrapper">
 | 
			
		||||
                <label>放大算法:</label>
 | 
			
		||||
                <div class="item-value">{{ item.params.hd_scale_alg }}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="info-line">
 | 
			
		||||
              <div class="wrapper">
 | 
			
		||||
                <label>放大倍数:</label>
 | 
			
		||||
                <div class="item-value">{{ item.params.hd_scale }}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <div class="info-line">
 | 
			
		||||
              <div class="wrapper">
 | 
			
		||||
                <label>迭代步数:</label>
 | 
			
		||||
                <div class="item-value">{{ item.params.hd_steps }}</div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="copy-params">
 | 
			
		||||
            <el-button type="primary" round @click="drawSame(item)">画一张同款的</el-button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref, watch, onMounted } from "vue";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import { showMessageOK, showMessageError } from "@/utils/dialog";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  modelValue: Boolean,
 | 
			
		||||
  data: Object,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const item = ref(props.data);
 | 
			
		||||
const show = ref(props.modelValue);
 | 
			
		||||
const emit = defineEmits(["drawSame", "close"]);
 | 
			
		||||
 | 
			
		||||
const clipboard = ref(null);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  clipboard.value = new Clipboard(".copy-prompt-wall");
 | 
			
		||||
  clipboard.value.on("success", () => {
 | 
			
		||||
    showMessageOK("复制成功!");
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  clipboard.value.on("error", () => {
 | 
			
		||||
    showMessageError("复制失败!");
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.modelValue,
 | 
			
		||||
  (newValue) => {
 | 
			
		||||
    show.value = newValue;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.data,
 | 
			
		||||
  (newValue) => {
 | 
			
		||||
    item.value = newValue;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const drawSame = (item) => {
 | 
			
		||||
  emit("drawSame", item);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const close = () => {
 | 
			
		||||
  emit("close");
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped></style>
 | 
			
		||||
@@ -1,21 +1,21 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container class="send-verify-code">
 | 
			
		||||
    <el-button type="primary" class="send-btn" :size="props.size" :disabled="!canSend" @click="sendMsg" plain>
 | 
			
		||||
    <el-button type="success" :size="props.size" :disabled="!canSend" @click="sendMsg">
 | 
			
		||||
      {{ btnText }}
 | 
			
		||||
    </el-button>
 | 
			
		||||
 | 
			
		||||
    <captcha @success="doSendMsg" ref="captchaRef"/>
 | 
			
		||||
    <captcha @success="doSendMsg" ref="captchaRef" />
 | 
			
		||||
  </el-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
// 发送短信验证码组件
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {validateEmail, validateMobile} from "@/utils/validate";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {showMessageError, showMessageOK} from "@/utils/dialog";
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import { validateEmail, validateMobile } from "@/utils/validate";
 | 
			
		||||
import { httpPost } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import Captcha from "@/components/Captcha.vue";
 | 
			
		||||
import {getSystemInfo} from "@/store/cache";
 | 
			
		||||
import { getSystemInfo } from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@@ -23,58 +23,65 @@ const props = defineProps({
 | 
			
		||||
  size: String,
 | 
			
		||||
  type: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'mobile'
 | 
			
		||||
  }
 | 
			
		||||
    default: "mobile",
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
const btnText = ref('发送验证码')
 | 
			
		||||
const canSend = ref(true)
 | 
			
		||||
const captchaRef = ref(null)
 | 
			
		||||
const enableVerify = ref(false)
 | 
			
		||||
const btnText = ref("发送验证码");
 | 
			
		||||
const canSend = ref(true);
 | 
			
		||||
const captchaRef = ref(null);
 | 
			
		||||
const enableVerify = ref(false);
 | 
			
		||||
 | 
			
		||||
getSystemInfo().then(res => {
 | 
			
		||||
  enableVerify.value = res.data['enabled_verify']
 | 
			
		||||
})
 | 
			
		||||
getSystemInfo().then((res) => {
 | 
			
		||||
  enableVerify.value = res.data["enabled_verify"];
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const sendMsg = () => {
 | 
			
		||||
  if (!validateMobile(props.receiver) && props.type === 'mobile') {
 | 
			
		||||
    return showMessageError("请输入合法的手机号")
 | 
			
		||||
  if (!validateMobile(props.receiver) && props.type === "mobile") {
 | 
			
		||||
    return ElMessage.error("请输入合法的手机号");
 | 
			
		||||
  }
 | 
			
		||||
  if (!validateEmail(props.receiver) && props.type === 'email') {
 | 
			
		||||
    return showMessageError("请输入合法的邮箱地址")
 | 
			
		||||
  if (!validateEmail(props.receiver) && props.type === "email") {
 | 
			
		||||
    return ElMessage.error("请输入合法的邮箱地址");
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  if (enableVerify.value) {
 | 
			
		||||
    captchaRef.value.loadCaptcha()
 | 
			
		||||
    captchaRef.value.loadCaptcha();
 | 
			
		||||
  } else {
 | 
			
		||||
    doSendMsg({})
 | 
			
		||||
    doSendMsg({});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const doSendMsg = (data) => {
 | 
			
		||||
  if (!canSend.value) {
 | 
			
		||||
    return
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  canSend.value = false
 | 
			
		||||
  httpPost('/api/sms/code', {receiver: props.receiver, key: data.key, dots: data.dots, x:data.x}).then(() => {
 | 
			
		||||
    showMessageOK('验证码发送成功')
 | 
			
		||||
    let time = 60
 | 
			
		||||
    btnText.value = time
 | 
			
		||||
    const handler = setInterval(() => {
 | 
			
		||||
      time = time - 1
 | 
			
		||||
      if (time <= 0) {
 | 
			
		||||
        clearInterval(handler)
 | 
			
		||||
        btnText.value = '重新发送'
 | 
			
		||||
        canSend.value = true
 | 
			
		||||
      } else {
 | 
			
		||||
        btnText.value = time
 | 
			
		||||
      }
 | 
			
		||||
    }, 1000)
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    canSend.value = true
 | 
			
		||||
    showMessageError('验证码发送失败:' + e.message)
 | 
			
		||||
  canSend.value = false;
 | 
			
		||||
  httpPost("/api/sms/code", {
 | 
			
		||||
    receiver: props.receiver,
 | 
			
		||||
    key: data.key,
 | 
			
		||||
    dots: data.dots,
 | 
			
		||||
    x: data.x,
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      ElMessage.success("验证码发送成功");
 | 
			
		||||
      let time = 60;
 | 
			
		||||
      btnText.value = time;
 | 
			
		||||
      const handler = setInterval(() => {
 | 
			
		||||
        time = time - 1;
 | 
			
		||||
        if (time <= 0) {
 | 
			
		||||
          clearInterval(handler);
 | 
			
		||||
          btnText.value = "重新发送";
 | 
			
		||||
          canSend.value = true;
 | 
			
		||||
        } else {
 | 
			
		||||
          btnText.value = time;
 | 
			
		||||
        }
 | 
			
		||||
      }, 1000);
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      canSend.value = true;
 | 
			
		||||
      ElMessage.error("验证码发送失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,169 +1,177 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="slide-captcha">
 | 
			
		||||
    <div class="bg-img">
 | 
			
		||||
      <el-image :src="backgroundImg" />
 | 
			
		||||
      <div :class="verifyMsgClass" v-if="checked !== 0">
 | 
			
		||||
        <span v-if="checked ===1">{{time}}s</span>
 | 
			
		||||
        {{verifyMsg}}
 | 
			
		||||
  <div class="flex justify-center items-center">
 | 
			
		||||
    <div class="slide-captcha">
 | 
			
		||||
      <div class="bg-img">
 | 
			
		||||
        <el-image :src="backgroundImg" />
 | 
			
		||||
        <div :class="verifyMsgClass" v-if="checked !== 0">
 | 
			
		||||
          <span v-if="checked === 1">{{ time }}s</span>
 | 
			
		||||
          {{ verifyMsg }}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="refresh" @click="emits('refresh')">
 | 
			
		||||
          <el-icon><Refresh /></el-icon>
 | 
			
		||||
        </div>
 | 
			
		||||
        <span class="block">
 | 
			
		||||
          <el-image :src="blockImg" :style="{ left: blockLeft + 'px' }" />
 | 
			
		||||
        </span>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="refresh" @click="emits('refresh')">
 | 
			
		||||
        <el-icon><Refresh /></el-icon>
 | 
			
		||||
      </div>
 | 
			
		||||
      <span class="block">
 | 
			
		||||
        <el-image :src="blockImg" :style="{left: blockLeft+'px'}" />
 | 
			
		||||
      </span>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="verify">
 | 
			
		||||
      <div class="verify-bar-area">
 | 
			
		||||
        <span class="verify-msg">{{verifyText}}</span>
 | 
			
		||||
      <div class="verify">
 | 
			
		||||
        <div class="verify-bar-area">
 | 
			
		||||
          <span class="verify-msg">{{ verifyText }}</span>
 | 
			
		||||
 | 
			
		||||
        <div :class="leftBarClass" :style="{width: leftBarWidth+'px'}">
 | 
			
		||||
          <div :class="blockClass" id="dragBlock"
 | 
			
		||||
               :style="{left: blockLeft+'px'}">
 | 
			
		||||
            <el-icon v-if="checked === 0"><ArrowRightBold /></el-icon>
 | 
			
		||||
            <el-icon v-if="checked === 1"><CircleCheckFilled /></el-icon>
 | 
			
		||||
            <el-icon v-if="checked === 2"><CircleCloseFilled /></el-icon>
 | 
			
		||||
          <div :class="leftBarClass" :style="{ width: leftBarWidth + 'px' }">
 | 
			
		||||
            <div :class="blockClass" id="dragBlock" :style="{ left: blockLeft + 'px' }">
 | 
			
		||||
              <el-icon v-if="checked === 0"><ArrowRightBold /></el-icon>
 | 
			
		||||
              <el-icon v-if="checked === 1"><CircleCheckFilled /></el-icon>
 | 
			
		||||
              <el-icon v-if="checked === 2"><CircleCloseFilled /></el-icon>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
import {onMounted, ref, watch} from "vue";
 | 
			
		||||
import {ArrowRightBold, CircleCheckFilled, CircleCloseFilled, Refresh} from "@element-plus/icons-vue";
 | 
			
		||||
import { onMounted, ref, watch } from "vue";
 | 
			
		||||
import { ArrowRightBold, CircleCheckFilled, CircleCloseFilled, Refresh } from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  bgImg: String,
 | 
			
		||||
  bkImg: String,
 | 
			
		||||
  result: Number,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const verifyText = ref('向右滑动完成验证')
 | 
			
		||||
const verifyMsg = ref('')
 | 
			
		||||
const verifyMsgClass = ref("verify-text success")
 | 
			
		||||
const blockClass = ref('verify-move-block')
 | 
			
		||||
const leftBarClass = ref('verify-left-bar')
 | 
			
		||||
const backgroundImg = ref('')
 | 
			
		||||
const blockImg = ref('')
 | 
			
		||||
const leftBarWidth = ref(0)
 | 
			
		||||
const blockLeft = ref(0)
 | 
			
		||||
const checked = ref(0)
 | 
			
		||||
const time = ref('')
 | 
			
		||||
 | 
			
		||||
watch(() => props.bgImg, (newVal) => {
 | 
			
		||||
  backgroundImg.value = newVal;
 | 
			
		||||
});
 | 
			
		||||
watch(() => props.bkImg, (newVal) => {
 | 
			
		||||
  blockImg.value = newVal;
 | 
			
		||||
});
 | 
			
		||||
watch(() => props.result, (newVal) => {
 | 
			
		||||
  checked.value = newVal;
 | 
			
		||||
  if (newVal === 1) {
 | 
			
		||||
    verifyMsgClass.value = "verify-text success"
 | 
			
		||||
    blockClass.value = 'verify-move-block success'
 | 
			
		||||
    leftBarClass.value = 'verify-left-bar success'
 | 
			
		||||
    verifyMsg.value = '验证成功'
 | 
			
		||||
    setTimeout(() => emits('hide'), 1000)
 | 
			
		||||
  } else if (newVal ===2) {
 | 
			
		||||
    verifyMsgClass.value = "verify-text error"
 | 
			
		||||
    blockClass.value = 'verify-move-block error'
 | 
			
		||||
    leftBarClass.value = 'verify-left-bar error'
 | 
			
		||||
    verifyMsg.value = '验证失败'
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      reset()
 | 
			
		||||
      emits('refresh')
 | 
			
		||||
    }, 1000)
 | 
			
		||||
  } else {
 | 
			
		||||
    reset()
 | 
			
		||||
 | 
			
		||||
const verifyText = ref("向右滑动完成验证");
 | 
			
		||||
const verifyMsg = ref("");
 | 
			
		||||
const verifyMsgClass = ref("verify-text success");
 | 
			
		||||
const blockClass = ref("verify-move-block");
 | 
			
		||||
const leftBarClass = ref("verify-left-bar");
 | 
			
		||||
const backgroundImg = ref("");
 | 
			
		||||
const blockImg = ref("");
 | 
			
		||||
const leftBarWidth = ref(0);
 | 
			
		||||
const blockLeft = ref(0);
 | 
			
		||||
const checked = ref(0);
 | 
			
		||||
const time = ref("");
 | 
			
		||||
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.bgImg,
 | 
			
		||||
  (newVal) => {
 | 
			
		||||
    backgroundImg.value = newVal;
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
);
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.bkImg,
 | 
			
		||||
  (newVal) => {
 | 
			
		||||
    blockImg.value = newVal;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.result,
 | 
			
		||||
  (newVal) => {
 | 
			
		||||
    checked.value = newVal;
 | 
			
		||||
    if (newVal === 1) {
 | 
			
		||||
      verifyMsgClass.value = "verify-text success";
 | 
			
		||||
      blockClass.value = "verify-move-block success";
 | 
			
		||||
      leftBarClass.value = "verify-left-bar success";
 | 
			
		||||
      verifyMsg.value = "验证成功";
 | 
			
		||||
      setTimeout(() => emits("hide"), 1000);
 | 
			
		||||
    } else if (newVal === 2) {
 | 
			
		||||
      verifyMsgClass.value = "verify-text error";
 | 
			
		||||
      blockClass.value = "verify-move-block error";
 | 
			
		||||
      leftBarClass.value = "verify-left-bar error";
 | 
			
		||||
      verifyMsg.value = "验证失败";
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        reset();
 | 
			
		||||
        emits("refresh");
 | 
			
		||||
      }, 1000);
 | 
			
		||||
    } else {
 | 
			
		||||
      reset();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const emits = defineEmits(['confirm','refresh','hide']);
 | 
			
		||||
const emits = defineEmits(["confirm", "refresh", "hide"]);
 | 
			
		||||
 | 
			
		||||
let offsetX = 0, isDragging  = false
 | 
			
		||||
let start = 0
 | 
			
		||||
let offsetX = 0,
 | 
			
		||||
  isDragging = false;
 | 
			
		||||
let start = 0;
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  const dragBlock = document.getElementById('dragBlock');
 | 
			
		||||
  dragBlock.addEventListener('mousedown', (evt) => {
 | 
			
		||||
    blockClass.value = 'verify-move-block active'
 | 
			
		||||
    leftBarClass.value = 'verify-left-bar active'
 | 
			
		||||
    leftBarWidth.value = 32
 | 
			
		||||
    isDragging  = true
 | 
			
		||||
    verifyText.value = ""
 | 
			
		||||
    offsetX = evt.clientX
 | 
			
		||||
    start = new Date().getTime()
 | 
			
		||||
  const dragBlock = document.getElementById("dragBlock");
 | 
			
		||||
  dragBlock.addEventListener("mousedown", (evt) => {
 | 
			
		||||
    blockClass.value = "verify-move-block active";
 | 
			
		||||
    leftBarClass.value = "verify-left-bar active";
 | 
			
		||||
    leftBarWidth.value = 32;
 | 
			
		||||
    isDragging = true;
 | 
			
		||||
    verifyText.value = "";
 | 
			
		||||
    offsetX = evt.clientX;
 | 
			
		||||
    start = new Date().getTime();
 | 
			
		||||
    evt.preventDefault();
 | 
			
		||||
  })
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  document.body.addEventListener('mousemove',(evt) => {
 | 
			
		||||
  document.body.addEventListener("mousemove", (evt) => {
 | 
			
		||||
    if (!isDragging) {
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const x = Math.max(evt.clientX - offsetX, 0)
 | 
			
		||||
    const x = Math.max(evt.clientX - offsetX, 0);
 | 
			
		||||
    blockLeft.value = x;
 | 
			
		||||
    leftBarWidth.value = x + 32
 | 
			
		||||
  })
 | 
			
		||||
    leftBarWidth.value = x + 32;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  document.body.addEventListener('mouseup', () => {
 | 
			
		||||
  document.body.addEventListener("mouseup", () => {
 | 
			
		||||
    if (!isDragging) {
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    time.value = ((new Date().getTime() - start)/1000).toFixed(2)
 | 
			
		||||
    isDragging  = false
 | 
			
		||||
    emits('confirm', Math.floor(blockLeft.value))
 | 
			
		||||
  })
 | 
			
		||||
    time.value = ((new Date().getTime() - start) / 1000).toFixed(2);
 | 
			
		||||
    isDragging = false;
 | 
			
		||||
    emits("confirm", Math.floor(blockLeft.value));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // 触摸事件
 | 
			
		||||
  dragBlock.addEventListener('touchstart', function (e) {
 | 
			
		||||
  dragBlock.addEventListener("touchstart", function (e) {
 | 
			
		||||
    isDragging = true;
 | 
			
		||||
    blockClass.value = 'verify-move-block active'
 | 
			
		||||
    leftBarClass.value = 'verify-left-bar active'
 | 
			
		||||
    leftBarWidth.value = 32
 | 
			
		||||
    isDragging  = true
 | 
			
		||||
    verifyText.value = ""
 | 
			
		||||
    blockClass.value = "verify-move-block active";
 | 
			
		||||
    leftBarClass.value = "verify-left-bar active";
 | 
			
		||||
    leftBarWidth.value = 32;
 | 
			
		||||
    isDragging = true;
 | 
			
		||||
    verifyText.value = "";
 | 
			
		||||
    offsetX = e.touches[0].clientX - dragBlock.getBoundingClientRect().left;
 | 
			
		||||
    start = new Date().getTime()
 | 
			
		||||
    start = new Date().getTime();
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  document.addEventListener('touchmove', function (e) {
 | 
			
		||||
  document.addEventListener("touchmove", function (e) {
 | 
			
		||||
    if (!isDragging) {
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    const x = Math.max(e.touches[0].clientX - offsetX, 0)
 | 
			
		||||
    const x = Math.max(e.touches[0].clientX - offsetX, 0);
 | 
			
		||||
    blockLeft.value = x;
 | 
			
		||||
    leftBarWidth.value = x + 32
 | 
			
		||||
    leftBarWidth.value = x + 32;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  document.addEventListener('touchend', function () {
 | 
			
		||||
  document.addEventListener("touchend", function () {
 | 
			
		||||
    if (!isDragging) {
 | 
			
		||||
      return
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    time.value = ((new Date().getTime() - start)/1000).toFixed(2)
 | 
			
		||||
    isDragging  = false
 | 
			
		||||
    emits('confirm', Math.floor(blockLeft.value))
 | 
			
		||||
    time.value = ((new Date().getTime() - start) / 1000).toFixed(2);
 | 
			
		||||
    isDragging = false;
 | 
			
		||||
    emits("confirm", Math.floor(blockLeft.value));
 | 
			
		||||
  });
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 重置验证码
 | 
			
		||||
const reset = () => {
 | 
			
		||||
  blockClass.value = 'verify-move-block'
 | 
			
		||||
  leftBarClass.value = 'verify-left-bar'
 | 
			
		||||
  leftBarWidth.value = 0
 | 
			
		||||
  blockLeft.value = 0
 | 
			
		||||
  checked.value = 0
 | 
			
		||||
  verifyText.value = "向右滑动完成验证"
 | 
			
		||||
}
 | 
			
		||||
  blockClass.value = "verify-move-block";
 | 
			
		||||
  leftBarClass.value = "verify-left-bar";
 | 
			
		||||
  leftBarWidth.value = 0;
 | 
			
		||||
  blockLeft.value = 0;
 | 
			
		||||
  checked.value = 0;
 | 
			
		||||
  verifyText.value = "向右滑动完成验证";
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
@@ -177,6 +185,7 @@ const reset = () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.slide-captcha {
 | 
			
		||||
  width 310px
 | 
			
		||||
  * {
 | 
			
		||||
    margin 0
 | 
			
		||||
    padding 0
 | 
			
		||||
@@ -294,4 +303,4 @@ const reset = () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,11 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="running-job-list">
 | 
			
		||||
  <div class="running-job-list pt-4 pb-4">
 | 
			
		||||
    <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"/>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
          <div class="progress" v-if="item.progress > 0">
 | 
			
		||||
            <el-progress type="circle" :percentage="item.progress" :width="100" color="#47fff1" />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-image fit="cover" v-else>
 | 
			
		||||
          <template #error>
 | 
			
		||||
@@ -20,21 +17,22 @@
 | 
			
		||||
        </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">
 | 
			
		||||
@import "~@/assets/css/running-job-list.styl"
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								web/src/components/ThemeChange.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,52 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="theme-box" @click="toggleTheme">
 | 
			
		||||
    <span class="iconfont icon-yueliang">{{ themePage === "light" ? "" : "" }}</span>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
// 定义主题状态,初始值从 localStorage 获取
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
const themePage = ref(store.theme || "light");
 | 
			
		||||
 | 
			
		||||
// 切换主题函数
 | 
			
		||||
const toggleTheme = () => {
 | 
			
		||||
  themePage.value = themePage.value === "light" ? "dark" : "light";
 | 
			
		||||
  store.setTheme(themePage.value); // 保存主题
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
@import '@/assets/iconfont/iconfont.css'
 | 
			
		||||
.theme-box{
 | 
			
		||||
  z-index :111
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  right: 40px;
 | 
			
		||||
  bottom: 150px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  border: 1px solid #ccc;
 | 
			
		||||
  border-radius: 50%;
 | 
			
		||||
  width 35px;
 | 
			
		||||
  height: 35px;
 | 
			
		||||
  line-height: 35px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  // background-color: rgb(146, 147, 148);
 | 
			
		||||
  background: linear-gradient(135deg, rgba(134, 140, 255, 1) 0%, rgba(67, 24, 255, 1) 100%);
 | 
			
		||||
 | 
			
		||||
  transition: all 0.3s ease;
 | 
			
		||||
  &:hover{
 | 
			
		||||
    transform: scale(1.1);
 | 
			
		||||
  }
 | 
			
		||||
  &:active{
 | 
			
		||||
    transform: scale(0.9);
 | 
			
		||||
  }
 | 
			
		||||
  .iconfont{
 | 
			
		||||
    font-size: 20px;
 | 
			
		||||
    color: yellow;
 | 
			
		||||
    transition: transform 0.3s ease;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,66 +1,29 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-dialog
 | 
			
		||||
      class="config-dialog"
 | 
			
		||||
      v-model="showDialog"
 | 
			
		||||
      :close-on-click-modal="true"
 | 
			
		||||
      :before-close="close"
 | 
			
		||||
      style="max-width: 600px"
 | 
			
		||||
      title="账户信息"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="user-info" id="user-info">
 | 
			
		||||
      <el-form v-if="user.id" :model="user" label-width="150px">
 | 
			
		||||
        <el-form-item label="账户">
 | 
			
		||||
          <span>{{ user.username }}</span>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="剩余算力">
 | 
			
		||||
          <el-tag>{{ user['power'] }}</el-tag>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
        <el-form-item label="会员到期时间" v-if="user['expired_time']  > 0">
 | 
			
		||||
          <el-tag type="danger">{{ dateFormat(user['expired_time']) }}</el-tag>
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </el-form>
 | 
			
		||||
  <el-dialog class="config-dialog" v-model="showDialog" :close-on-click-modal="true" :before-close="close" style="max-width: 400px" title="账户信息">
 | 
			
		||||
    <div class="flex-center-col p-4 pt-0" id="user-info">
 | 
			
		||||
      <user-profile @hide="close" />
 | 
			
		||||
    </div>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {computed, onMounted, ref} from "vue"
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat} from "@/utils/libs";
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import UserProfile from "@/components/UserProfile.vue";
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show: Boolean,
 | 
			
		||||
  user: Object,
 | 
			
		||||
  models: Array,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const showDialog = computed(() => {
 | 
			
		||||
  return props.show
 | 
			
		||||
})
 | 
			
		||||
const user = ref({
 | 
			
		||||
  username: '',
 | 
			
		||||
  nickname: '',
 | 
			
		||||
  avatar: '',
 | 
			
		||||
  calls: 0,
 | 
			
		||||
  tokens: 0,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  // 获取最新用户信息
 | 
			
		||||
  httpGet('/api/user/profile').then(res => {
 | 
			
		||||
    user.value = res.data
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取用户信息失败:" + e.message)
 | 
			
		||||
  });
 | 
			
		||||
})
 | 
			
		||||
  return props.show;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// eslint-disable-next-line no-undef
 | 
			
		||||
const emits = defineEmits(['hide']);
 | 
			
		||||
const emits = defineEmits(["hide"]);
 | 
			
		||||
const close = function () {
 | 
			
		||||
  emits('hide', false);
 | 
			
		||||
}
 | 
			
		||||
  emits("hide", false);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@@ -68,23 +31,6 @@ const close = function () {
 | 
			
		||||
  .el-dialog {
 | 
			
		||||
    --el-dialog-width 90%;
 | 
			
		||||
    max-width 800px;
 | 
			
		||||
 | 
			
		||||
    .el-dialog__body {
 | 
			
		||||
      overflow-y auto;
 | 
			
		||||
 | 
			
		||||
      .user-info {
 | 
			
		||||
        position relative;
 | 
			
		||||
 | 
			
		||||
        .el-message {
 | 
			
		||||
          position: absolute;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .tip {
 | 
			
		||||
        color #c1c1c1
 | 
			
		||||
        font-size 12px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,95 +1,99 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <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/>
 | 
			
		||||
              <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/>
 | 
			
		||||
    <div class="pagination">
 | 
			
		||||
      <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"/>
 | 
			
		||||
 | 
			
		||||
    <el-empty :image-size="100" v-else :image="nodata" description="暂无数据" />
 | 
			
		||||
    <div class="pagination pb-5">
 | 
			
		||||
      <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()"
 | 
			
		||||
        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 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 { 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) => {
 | 
			
		||||
    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 => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
  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;
 | 
			
		||||
      }
 | 
			
		||||
      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;
 | 
			
		||||
@@ -105,4 +109,4 @@ const fetchData = () => {
 | 
			
		||||
    color #20a0ff
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,79 +1,79 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="user-info" id="user-info">
 | 
			
		||||
    <el-form :model="user" label-width="100px">
 | 
			
		||||
  <div class="user-info flex-center-col" id="user-info">
 | 
			
		||||
    <el-form :model="user" label-width="80px" label-position="left">
 | 
			
		||||
      <el-row>
 | 
			
		||||
        <el-upload
 | 
			
		||||
            class="avatar-uploader"
 | 
			
		||||
            :auto-upload="true"
 | 
			
		||||
            :show-file-list="false"
 | 
			
		||||
            :http-request="afterRead"
 | 
			
		||||
            accept=".png,.jpg,.jpeg,.bmp"
 | 
			
		||||
        >
 | 
			
		||||
          <el-avatar v-if="user.avatar" :src="user.avatar" shape="circle" :size="100"/>
 | 
			
		||||
        <el-upload class="avatar-uploader" :auto-upload="true" :show-file-list="false" :http-request="afterRead" accept=".png,.jpg,.jpeg,.bmp">
 | 
			
		||||
          <el-tooltip content="点击上传头像" placement="top" v-if="user.avatar">
 | 
			
		||||
            <el-avatar :src="user.avatar" shape="circle" :size="100" />
 | 
			
		||||
          </el-tooltip>
 | 
			
		||||
          <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="账号">
 | 
			
		||||
        <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>
 | 
			
		||||
        </el-tooltip>
 | 
			
		||||
        <div class="flex">
 | 
			
		||||
          <span>{{ user.username }}</span>
 | 
			
		||||
          <el-tooltip class="box-item" content="您已经是 VIP 会员" placement="right">
 | 
			
		||||
            <span class="vip-icon"><el-image v-if="user.vip" :src="vipImg" class="rounded-full ml-1 size-5" /></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-tag type="info" size="small" class="ml-2 cursor-pointer" @click="gotoLog">算力日志</el-tag>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="会员到期时间" v-if="user['expired_time']  > 0">
 | 
			
		||||
        <el-tag type="danger">{{ dateFormat(user['expired_time']) }}</el-tag>
 | 
			
		||||
      <el-form-item label="会员到期时间" v-if="user['expired_time'] > 0">
 | 
			
		||||
        <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";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
const user = ref({
 | 
			
		||||
  vip: false,
 | 
			
		||||
  username: '演示数据',
 | 
			
		||||
  nickname: '演示数据',
 | 
			
		||||
  avatar: '/images/vip.png',
 | 
			
		||||
  mobile: '演示数据',
 | 
			
		||||
  username: "演示数据",
 | 
			
		||||
  nickname: "演示数据",
 | 
			
		||||
  avatar: "/images/menu/member.png",
 | 
			
		||||
  mobile: "演示数据",
 | 
			
		||||
  power: 99999,
 | 
			
		||||
})
 | 
			
		||||
const vipImg = ref("/images/vip.png")
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const vipImg = ref("/images/menu/member.png");
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const emits = defineEmits(["hide"]);
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  checkSession().then(() => {
 | 
			
		||||
    // 获取最新用户信息
 | 
			
		||||
    httpGet('/api/user/profile').then(res => {
 | 
			
		||||
      user.value = res.data
 | 
			
		||||
    }).catch(e => {
 | 
			
		||||
      ElMessage.error("获取用户信息失败:" + e.message)
 | 
			
		||||
  checkSession()
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      // 获取最新用户信息
 | 
			
		||||
      httpGet("/api/user/profile")
 | 
			
		||||
        .then((res) => {
 | 
			
		||||
          user.value = res.data;
 | 
			
		||||
        })
 | 
			
		||||
        .catch((e) => {
 | 
			
		||||
          ElMessage.error("获取用户信息失败:" + e.message);
 | 
			
		||||
        });
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      console.log(e);
 | 
			
		||||
    });
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    console.log(e)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const afterRead = (file) => {
 | 
			
		||||
  // 压缩图片并上传
 | 
			
		||||
@@ -81,14 +81,16 @@ 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);
 | 
			
		||||
@@ -97,17 +99,23 @@ const afterRead = (file) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const gotoLog = () => {
 | 
			
		||||
  router.push("/powerLog");
 | 
			
		||||
  emits("hide", false);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.user-info {
 | 
			
		||||
  padding 20px 0
 | 
			
		||||
 | 
			
		||||
  .el-row {
 | 
			
		||||
    justify-content center
 | 
			
		||||
@@ -120,11 +128,10 @@ const save = () => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .opt-line {
 | 
			
		||||
    padding-top 20px
 | 
			
		||||
 | 
			
		||||
    .el-button {
 | 
			
		||||
      width 100%
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -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">
 | 
			
		||||
@@ -13,7 +13,9 @@
 | 
			
		||||
 | 
			
		||||
            <div class="list-box">
 | 
			
		||||
              <ul>
 | 
			
		||||
                <li v-for="item in samples" :key="item"><a @click="send(item)">{{ item }}</a></li>
 | 
			
		||||
                <li v-for="item in samples" :key="item">
 | 
			
		||||
                  <a @click="send(item)">{{ item }}</a>
 | 
			
		||||
                </li>
 | 
			
		||||
              </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -27,7 +29,9 @@
 | 
			
		||||
 | 
			
		||||
            <div class="list-box">
 | 
			
		||||
              <ul>
 | 
			
		||||
                <li v-for="item in plugins" :key="item.value"><a @click="send(item.value)">{{ item.text }}</a></li>
 | 
			
		||||
                <li v-for="item in plugins" :key="item.value">
 | 
			
		||||
                  <a @click="send(item.value)">{{ item.text }}</a>
 | 
			
		||||
                </li>
 | 
			
		||||
              </ul>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -54,19 +58,18 @@
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
import { onMounted, ref } from "vue";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { getSystemInfo } from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {getSystemInfo} from "@/store/cache";
 | 
			
		||||
 | 
			
		||||
const title = ref(process.env.VUE_APP_TITLE)
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION)
 | 
			
		||||
const title = ref(process.env.VUE_APP_TITLE);
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION);
 | 
			
		||||
 | 
			
		||||
const samples = ref([
 | 
			
		||||
  "用小学生都能听懂的术语解释什么是量子纠缠",
 | 
			
		||||
  "能给一位6岁男孩的生日会提供一些创造性的建议吗?",
 | 
			
		||||
  "如何用 Go 语言实现支持代理 Http client 请求?"
 | 
			
		||||
])
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const plugins = ref([
 | 
			
		||||
  {
 | 
			
		||||
@@ -81,7 +84,7 @@ const plugins = ref([
 | 
			
		||||
    value: "今日头条",
 | 
			
		||||
    text: "今日头条:给用户推荐当天的头条新闻,周榜热文"
 | 
			
		||||
  }
 | 
			
		||||
])
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const capabilities = ref([
 | 
			
		||||
  {
 | 
			
		||||
@@ -96,20 +99,22 @@ const capabilities = ref([
 | 
			
		||||
    text: "绘画:马斯克开拖拉机,20世纪,中国农村。3:2",
 | 
			
		||||
    value: "绘画:马斯克开拖拉机,20世纪,中国农村。3:2"
 | 
			
		||||
  }
 | 
			
		||||
])
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  getSystemInfo().then(res => {
 | 
			
		||||
    title.value = res.data.title
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取系统配置失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
  getSystemInfo()
 | 
			
		||||
    .then((res) => {
 | 
			
		||||
      title.value = res.data.title;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("获取系统配置失败:" + e.message);
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const emits = defineEmits(['send']);
 | 
			
		||||
const emits = defineEmits(["send"]);
 | 
			
		||||
const send = (text) => {
 | 
			
		||||
  emits('send', text)
 | 
			
		||||
}
 | 
			
		||||
  emits("send", text);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
.welcome {
 | 
			
		||||
@@ -123,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 {
 | 
			
		||||
@@ -148,10 +154,9 @@ const send = (text) => {
 | 
			
		||||
            font-size 14px;
 | 
			
		||||
            padding .75rem
 | 
			
		||||
            border-radius 5px;
 | 
			
		||||
            background-color: rgba(247, 247, 248, 1);
 | 
			
		||||
 | 
			
		||||
            background-color: var(--chat-wel-bg);
 | 
			
		||||
            color:var( --theme-text-color-secondary);
 | 
			
		||||
            line-height 1.5
 | 
			
		||||
            color #666666
 | 
			
		||||
 | 
			
		||||
            a {
 | 
			
		||||
              cursor pointer
 | 
			
		||||
@@ -165,4 +170,4 @@ const send = (text) => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,44 +1,36 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div :class="'admin-header '+theme">
 | 
			
		||||
  <div :class="'admin-header ' + theme">
 | 
			
		||||
    <!-- 折叠按钮 -->
 | 
			
		||||
    <div class="collapse-btn" @click="collapseChange">
 | 
			
		||||
      <el-icon v-if="sidebar.collapse">
 | 
			
		||||
        <Expand/>
 | 
			
		||||
        <Expand />
 | 
			
		||||
      </el-icon>
 | 
			
		||||
      <el-icon v-else>
 | 
			
		||||
        <Fold/>
 | 
			
		||||
        <Fold />
 | 
			
		||||
      </el-icon>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="breadcrumb">
 | 
			
		||||
      <el-breadcrumb :separator-icon="ArrowRight">
 | 
			
		||||
        <el-breadcrumb-item v-for="item in breadcrumb">{{ item.title }}</el-breadcrumb-item>
 | 
			
		||||
        <el-breadcrumb-item v-for="item in breadcrumb" :key="item.title">{{ item.title }}</el-breadcrumb-item>
 | 
			
		||||
      </el-breadcrumb>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="header-right">
 | 
			
		||||
      <div class="header-user-con">
 | 
			
		||||
        <!-- 切换主题 -->
 | 
			
		||||
        <el-switch
 | 
			
		||||
            style="margin-right: 10px"
 | 
			
		||||
            v-model="dark"
 | 
			
		||||
            inline-prompt
 | 
			
		||||
            :active-action-icon="Moon"
 | 
			
		||||
            :inactive-action-icon="Sunny"
 | 
			
		||||
            @change="changeTheme"
 | 
			
		||||
        />
 | 
			
		||||
        <el-switch style="margin-right: 10px" v-model="dark" inline-prompt :active-action-icon="Moon"
 | 
			
		||||
                   :inactive-action-icon="Sunny" @change="changeTheme"/>
 | 
			
		||||
        <!-- 用户名下拉菜单 -->
 | 
			
		||||
        <el-dropdown class="user-name" :hide-on-click="true" trigger="click">
 | 
			
		||||
					<span class="el-dropdown-link">
 | 
			
		||||
						<el-avatar class="user-avatar" :size="30" :src="avatar"/>
 | 
			
		||||
						<el-icon class="el-icon--right">
 | 
			
		||||
							<arrow-down/>
 | 
			
		||||
						</el-icon>
 | 
			
		||||
					</span>
 | 
			
		||||
          <span class="el-dropdown-link">
 | 
			
		||||
            <el-avatar class="user-avatar" :size="30" :src="avatar" />
 | 
			
		||||
            <el-icon class="el-icon--right">
 | 
			
		||||
              <arrow-down />
 | 
			
		||||
            </el-icon>
 | 
			
		||||
          </span>
 | 
			
		||||
          <template #dropdown>
 | 
			
		||||
            <el-dropdown-menu>
 | 
			
		||||
              <el-dropdown-item>
 | 
			
		||||
                <i class="iconfont icon-version"></i> 当前版本:{{ version }}
 | 
			
		||||
              </el-dropdown-item>
 | 
			
		||||
              <el-dropdown-item><i class="iconfont icon-version"></i> 当前版本:{{ version }}</el-dropdown-item>
 | 
			
		||||
              <el-dropdown-item divided @click="logout">
 | 
			
		||||
                <i class="iconfont icon-logout"></i>
 | 
			
		||||
                <span>退出登录</span>
 | 
			
		||||
@@ -48,12 +40,11 @@
 | 
			
		||||
        </el-dropdown>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref, watch} from 'vue';
 | 
			
		||||
import {getMenuItems, useSidebarStore} from '@/store/sidebar';
 | 
			
		||||
import {onMounted, ref, watch} from "vue";
 | 
			
		||||
import {getMenuItems, useSidebarStore} from "@/store/sidebar";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {ArrowDown, ArrowRight, Expand, Fold, Moon, Sunny} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
@@ -61,69 +52,72 @@ import {ElMessage} from "element-plus";
 | 
			
		||||
import {removeAdminToken} from "@/store/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION)
 | 
			
		||||
const avatar = ref('/images/user-info.jpg')
 | 
			
		||||
const version = ref(process.env.VUE_APP_VERSION);
 | 
			
		||||
const avatar = ref("/images/user-info.jpg");
 | 
			
		||||
const sidebar = useSidebarStore();
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const breadcrumb = ref([])
 | 
			
		||||
const breadcrumb = ref([]);
 | 
			
		||||
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
const dark = ref(store.adminTheme === 'dark')
 | 
			
		||||
const theme = ref(store.adminTheme)
 | 
			
		||||
watch(() => store.adminTheme, (val) => {
 | 
			
		||||
  theme.value = val
 | 
			
		||||
})
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
const dark = ref(store.theme === "dark");
 | 
			
		||||
const theme = ref(store.theme);
 | 
			
		||||
watch(
 | 
			
		||||
    () => store.theme,
 | 
			
		||||
  (val) => {
 | 
			
		||||
    theme.value = val;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const changeTheme = () => {
 | 
			
		||||
  store.setAdminTheme(dark.value ? 'dark' : 'light')
 | 
			
		||||
}
 | 
			
		||||
  store.setTheme(dark.value ? "dark" : "light");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
router.afterEach((to) => {
 | 
			
		||||
  initBreadCrumb(to.path)
 | 
			
		||||
  initBreadCrumb(to.path);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  initBreadCrumb(router.currentRoute.value.path)
 | 
			
		||||
})
 | 
			
		||||
  initBreadCrumb(router.currentRoute.value.path);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 初始化面包屑导航
 | 
			
		||||
const initBreadCrumb = (path) => {
 | 
			
		||||
  breadcrumb.value = [{title: "首页"}]
 | 
			
		||||
  const items = getMenuItems()
 | 
			
		||||
  breadcrumb.value = [{ title: "首页" }];
 | 
			
		||||
  const items = getMenuItems();
 | 
			
		||||
  if (items) {
 | 
			
		||||
    let bk = false
 | 
			
		||||
    let bk = false;
 | 
			
		||||
    for (let i = 0; i < items.length; i++) {
 | 
			
		||||
      if (items[i].index === path) {
 | 
			
		||||
        breadcrumb.value.push({
 | 
			
		||||
          title: items[i].title,
 | 
			
		||||
          path: items[i].index
 | 
			
		||||
        })
 | 
			
		||||
        break
 | 
			
		||||
          path: items[i].index,
 | 
			
		||||
        });
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      if (bk) {
 | 
			
		||||
        break
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (items[i]['subs']) {
 | 
			
		||||
        const subs = items[i]['subs']
 | 
			
		||||
      if (items[i]["subs"]) {
 | 
			
		||||
        const subs = items[i]["subs"];
 | 
			
		||||
        for (let j = 0; j < subs.length; j++) {
 | 
			
		||||
          if (subs[j].index === path) {
 | 
			
		||||
            breadcrumb.value.push({
 | 
			
		||||
              title: items[i].title,
 | 
			
		||||
              path: items[i].index
 | 
			
		||||
            })
 | 
			
		||||
              path: items[i].index,
 | 
			
		||||
            });
 | 
			
		||||
            breadcrumb.value.push({
 | 
			
		||||
              title: subs[j].title,
 | 
			
		||||
              path: subs[j].index
 | 
			
		||||
            })
 | 
			
		||||
            bk = true
 | 
			
		||||
            break
 | 
			
		||||
              path: subs[j].index,
 | 
			
		||||
            });
 | 
			
		||||
            bk = true;
 | 
			
		||||
            break;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 侧边栏折叠
 | 
			
		||||
const collapseChange = () => {
 | 
			
		||||
@@ -137,13 +131,15 @@ onMounted(() => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const logout = function () {
 | 
			
		||||
  httpGet("/api/admin/logout").then(() => {
 | 
			
		||||
    removeAdminToken()
 | 
			
		||||
    router.replace('/admin/login')
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error("注销失败: " + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
  httpGet("/api/admin/logout")
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      removeAdminToken();
 | 
			
		||||
      router.replace("/admin/login");
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      ElMessage.error("注销失败: " + e.message);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
.admin-header {
 | 
			
		||||
@@ -152,8 +148,8 @@ const logout = function () {
 | 
			
		||||
  overflow hidden
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  font-size: 22px;
 | 
			
		||||
  color: #303133;
 | 
			
		||||
  background-color #ffffff
 | 
			
		||||
  background-color:var(--chat-content-bg);
 | 
			
		||||
  color:var(--theme-text-color-primary);
 | 
			
		||||
 | 
			
		||||
  .collapse-btn {
 | 
			
		||||
    display: flex;
 | 
			
		||||
@@ -260,5 +256,4 @@ const logout = function () {
 | 
			
		||||
.admin-header {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,40 +1,36 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div :class="'sidebar '+theme">
 | 
			
		||||
    <a class="logo" href="/" target="_blank">
 | 
			
		||||
      <el-image :src="logo"/>
 | 
			
		||||
  <div :class="'sidebar ' + theme">
 | 
			
		||||
    <a class="logo w-full flex items-center" href="/" target="_blank">
 | 
			
		||||
      <img :src="logo" />
 | 
			
		||||
      <span class="text" v-show="!sidebar.collapse">{{ title }}</span>
 | 
			
		||||
    </a>
 | 
			
		||||
 | 
			
		||||
    <el-menu
 | 
			
		||||
        class="sidebar-el-menu"
 | 
			
		||||
        :default-active="onRoutes"
 | 
			
		||||
        :collapse="sidebar.collapse"
 | 
			
		||||
        background-color="#324157"
 | 
			
		||||
        text-color="#bfcbd9"
 | 
			
		||||
        active-text-color="#20a0ff"
 | 
			
		||||
        unique-opened
 | 
			
		||||
        router
 | 
			
		||||
      class="sidebar-el-menu"
 | 
			
		||||
      :default-active="onRoutes"
 | 
			
		||||
      :collapse="sidebar.collapse"
 | 
			
		||||
      background-color="#324157"
 | 
			
		||||
      text-color="#bfcbd9"
 | 
			
		||||
      active-text-color="#20a0ff"
 | 
			
		||||
      unique-opened
 | 
			
		||||
      router
 | 
			
		||||
    >
 | 
			
		||||
      <template v-for="item in items">
 | 
			
		||||
        <template v-if="item.subs">
 | 
			
		||||
          <el-sub-menu :index="item.index" :key="item.index">
 | 
			
		||||
            <template #title>
 | 
			
		||||
              <i :class="'iconfont icon-'+item.icon"></i>
 | 
			
		||||
              <i :class="'iconfont icon-' + item.icon"></i>
 | 
			
		||||
              <span>{{ item.title }}</span>
 | 
			
		||||
            </template>
 | 
			
		||||
            <template v-for="subItem in item.subs">
 | 
			
		||||
              <el-sub-menu
 | 
			
		||||
                  v-if="subItem.subs"
 | 
			
		||||
                  :index="subItem.index"
 | 
			
		||||
                  :key="subItem.index"
 | 
			
		||||
              >
 | 
			
		||||
              <el-sub-menu v-if="subItem.subs" :index="subItem.index" :key="subItem.index">
 | 
			
		||||
                <template #title>{{ subItem.title }}</template>
 | 
			
		||||
                <el-menu-item v-for="(threeItem, i) in subItem.subs" :key="i" :index="threeItem.index">
 | 
			
		||||
                  {{ threeItem.title }}
 | 
			
		||||
                </el-menu-item>
 | 
			
		||||
              </el-sub-menu>
 | 
			
		||||
              <el-menu-item v-else :index="subItem.index" :key="subItem.index">
 | 
			
		||||
                <i v-if="subItem.icon" :class="'iconfont icon-'+subItem.icon"></i>
 | 
			
		||||
                <i v-if="subItem.icon" :class="'iconfont icon-' + subItem.icon"></i>
 | 
			
		||||
                {{ subItem.title }}
 | 
			
		||||
              </el-menu-item>
 | 
			
		||||
            </template>
 | 
			
		||||
@@ -42,7 +38,7 @@
 | 
			
		||||
        </template>
 | 
			
		||||
        <template v-else>
 | 
			
		||||
          <el-menu-item :index="item.index" :key="item.index">
 | 
			
		||||
            <i :class="'iconfont icon-'+item.icon"></i>
 | 
			
		||||
            <i :class="'iconfont icon-' + item.icon"></i>
 | 
			
		||||
            <template #title>{{ item.title }}</template>
 | 
			
		||||
          </el-menu-item>
 | 
			
		||||
        </template>
 | 
			
		||||
@@ -52,120 +48,125 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {computed, ref, watch} from 'vue';
 | 
			
		||||
import {setMenuItems, useSidebarStore} from '@/store/sidebar';
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {useRoute} from "vue-router";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import { computed, ref, watch } from "vue";
 | 
			
		||||
import { setMenuItems, useSidebarStore } from "@/store/sidebar";
 | 
			
		||||
import { httpGet } from "@/utils/http";
 | 
			
		||||
import { ElMessage } from "element-plus";
 | 
			
		||||
import { useRoute } from "vue-router";
 | 
			
		||||
import { useSharedStore } from "@/store/sharedata";
 | 
			
		||||
 | 
			
		||||
const title = ref('')
 | 
			
		||||
const logo = ref('')
 | 
			
		||||
const title = ref("");
 | 
			
		||||
const logo = ref("");
 | 
			
		||||
 | 
			
		||||
// 加载系统配置
 | 
			
		||||
httpGet('/api/admin/config/get?key=system').then(res => {
 | 
			
		||||
  title.value = res.data.admin_title
 | 
			
		||||
  logo.value = res.data.logo
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  ElMessage.error("加载系统配置失败: " + e.message)
 | 
			
		||||
})
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
const theme = ref(store.adminTheme)
 | 
			
		||||
watch(() => store.adminTheme, (val) => {
 | 
			
		||||
  theme.value = val
 | 
			
		||||
})
 | 
			
		||||
httpGet("/api/admin/config/get?key=system")
 | 
			
		||||
  .then((res) => {
 | 
			
		||||
    title.value = res.data.admin_title;
 | 
			
		||||
    logo.value = res.data.logo;
 | 
			
		||||
  })
 | 
			
		||||
  .catch((e) => {
 | 
			
		||||
    ElMessage.error("加载系统配置失败: " + e.message);
 | 
			
		||||
  });
 | 
			
		||||
const store = useSharedStore();
 | 
			
		||||
const theme = ref(store.theme);
 | 
			
		||||
watch(
 | 
			
		||||
  () => store.theme,
 | 
			
		||||
  (val) => {
 | 
			
		||||
    theme.value = val;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
const items = [
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'home',
 | 
			
		||||
    index: '/admin/dashboard',
 | 
			
		||||
    title: '仪表盘',
 | 
			
		||||
    icon: "home",
 | 
			
		||||
    index: "/admin/dashboard",
 | 
			
		||||
    title: "仪表盘",
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'user-fill',
 | 
			
		||||
    index: '/admin/user',
 | 
			
		||||
    title: '用户管理',
 | 
			
		||||
    icon: "user-fill",
 | 
			
		||||
    index: "/admin/user",
 | 
			
		||||
    title: "用户管理",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'menu',
 | 
			
		||||
    index: '1',
 | 
			
		||||
    title: '应用管理',
 | 
			
		||||
    icon: "menu",
 | 
			
		||||
    index: "1",
 | 
			
		||||
    title: "应用管理",
 | 
			
		||||
    subs: [
 | 
			
		||||
      {
 | 
			
		||||
        index: '/admin/app',
 | 
			
		||||
        title: '应用列表',
 | 
			
		||||
        index: "/admin/app",
 | 
			
		||||
        title: "应用列表",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        index: '/admin/app/type',
 | 
			
		||||
        title: '应用分类',
 | 
			
		||||
        index: "/admin/app/type",
 | 
			
		||||
        title: "应用分类",
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'api-key',
 | 
			
		||||
    index: '/admin/apikey',
 | 
			
		||||
    title: 'API-KEY',
 | 
			
		||||
    icon: "api-key",
 | 
			
		||||
    index: "/admin/apikey",
 | 
			
		||||
    title: "API-KEY",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'model',
 | 
			
		||||
    index: '/admin/chat/model',
 | 
			
		||||
    title: '语言模型',
 | 
			
		||||
    icon: "model",
 | 
			
		||||
    index: "/admin/chat/model",
 | 
			
		||||
    title: "模型管理",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'recharge',
 | 
			
		||||
    index: '/admin/product',
 | 
			
		||||
    title: '充值产品',
 | 
			
		||||
    icon: "recharge",
 | 
			
		||||
    index: "/admin/product",
 | 
			
		||||
    title: "充值产品",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'order',
 | 
			
		||||
    index: '/admin/order',
 | 
			
		||||
    title: '充值订单',
 | 
			
		||||
    icon: "order",
 | 
			
		||||
    index: "/admin/order",
 | 
			
		||||
    title: "充值订单",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'reward',
 | 
			
		||||
    index: '/admin/redeem',
 | 
			
		||||
    title: '兑换码',
 | 
			
		||||
    icon: "reward",
 | 
			
		||||
    index: "/admin/redeem",
 | 
			
		||||
    title: "兑换码",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'control',
 | 
			
		||||
    index: '/admin/functions',
 | 
			
		||||
    title: '函数管理',
 | 
			
		||||
    icon: "control",
 | 
			
		||||
    index: "/admin/functions",
 | 
			
		||||
    title: "函数管理",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'prompt',
 | 
			
		||||
    index: '/admin/chats',
 | 
			
		||||
    title: '对话管理',
 | 
			
		||||
    icon: "prompt",
 | 
			
		||||
    index: "/admin/chats",
 | 
			
		||||
    title: "对话管理",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'image',
 | 
			
		||||
    index: '/admin/images',
 | 
			
		||||
    title: '绘图管理',
 | 
			
		||||
    icon: "image",
 | 
			
		||||
    index: "/admin/images",
 | 
			
		||||
    title: "绘图管理",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'mp3',
 | 
			
		||||
    index: '/admin/medias',
 | 
			
		||||
    title: '音视频管理',
 | 
			
		||||
    icon: "mp3",
 | 
			
		||||
    index: "/admin/medias",
 | 
			
		||||
    title: "音视频管理",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'role',
 | 
			
		||||
    index: '/admin/manger',
 | 
			
		||||
    title: '管理员',
 | 
			
		||||
    icon: "role",
 | 
			
		||||
    index: "/admin/manger",
 | 
			
		||||
    title: "管理员",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'config',
 | 
			
		||||
    index: '/admin/system',
 | 
			
		||||
    title: '系统设置',
 | 
			
		||||
    icon: "config",
 | 
			
		||||
    index: "/admin/system",
 | 
			
		||||
    title: "系统设置",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'log',
 | 
			
		||||
    index: '/admin/powerLog',
 | 
			
		||||
    title: '用户算力日志',
 | 
			
		||||
    icon: "log",
 | 
			
		||||
    index: "/admin/powerLog",
 | 
			
		||||
    title: "用户算力日志",
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'log',
 | 
			
		||||
    index: '/admin/loginLog',
 | 
			
		||||
    title: '用户登录日志',
 | 
			
		||||
    icon: "log",
 | 
			
		||||
    index: "/admin/loginLog",
 | 
			
		||||
    title: "用户登录日志",
 | 
			
		||||
  },
 | 
			
		||||
  // {
 | 
			
		||||
  //   icon: 'menu',
 | 
			
		||||
@@ -198,7 +199,7 @@ const onRoutes = computed(() => {
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const sidebar = useSidebarStore();
 | 
			
		||||
setMenuItems(items)
 | 
			
		||||
setMenuItems(items);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
@@ -212,20 +213,17 @@ setMenuItems(items)
 | 
			
		||||
 | 
			
		||||
  .logo {
 | 
			
		||||
    display flex
 | 
			
		||||
    width 219px
 | 
			
		||||
    background-color #324157
 | 
			
		||||
    padding 6px 15px;
 | 
			
		||||
    cursor pointer
 | 
			
		||||
    background-color: #324157
 | 
			
		||||
 | 
			
		||||
    .el-image {
 | 
			
		||||
      width 36px;
 | 
			
		||||
    img {
 | 
			
		||||
      height 36px;
 | 
			
		||||
      padding-top 5px;
 | 
			
		||||
      border-radius 100%
 | 
			
		||||
 | 
			
		||||
      .el-image__inner {
 | 
			
		||||
        height 40px
 | 
			
		||||
      }
 | 
			
		||||
      background #fff
 | 
			
		||||
      border 2px solid #754ff6
 | 
			
		||||
      padding 2px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .text {
 | 
			
		||||
@@ -292,5 +290,4 @@ setMenuItems(items)
 | 
			
		||||
    border-color var(--el-border-color)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,8 @@ import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import {ref, watch} from "vue";
 | 
			
		||||
 | 
			
		||||
const store = useSharedStore()
 | 
			
		||||
const theme = ref(store.adminTheme)
 | 
			
		||||
watch(() => store.adminTheme, (val) => {
 | 
			
		||||
const theme = ref(store.theme)
 | 
			
		||||
watch(() => store.theme, (val) => {
 | 
			
		||||
  theme.value = val
 | 
			
		||||
})
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="mobile-message-mj">
 | 
			
		||||
    <div class="chat-icon">
 | 
			
		||||
      <van-image :src="icon"/>
 | 
			
		||||
      <van-image :src="icon" />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="chat-item">
 | 
			
		||||
@@ -11,21 +11,30 @@
 | 
			
		||||
          <div class="content-inner">
 | 
			
		||||
            <div class="text" v-html="data.html"></div>
 | 
			
		||||
            <div class="images" v-if="data.image?.url !== ''">
 | 
			
		||||
              <el-image :src="data.image?.url"
 | 
			
		||||
                        :zoom-rate="1.2"
 | 
			
		||||
                        :preview-src-list="[data.image?.url]"
 | 
			
		||||
                        fit="cover"
 | 
			
		||||
                        :initial-index="0" loading="lazy">
 | 
			
		||||
              <el-image
 | 
			
		||||
                :src="data.image?.url"
 | 
			
		||||
                :zoom-rate="1.2"
 | 
			
		||||
                :preview-src-list="[data.image?.url]"
 | 
			
		||||
                fit="cover"
 | 
			
		||||
                :initial-index="0"
 | 
			
		||||
                loading="lazy"
 | 
			
		||||
              >
 | 
			
		||||
                <template #placeholder>
 | 
			
		||||
                  <div class="image-slot"
 | 
			
		||||
                       :style="{height: height+'px', lineHeight:height+'px'}">
 | 
			
		||||
                    正在加载图片<span class="dot">...</span></div>
 | 
			
		||||
                  <div
 | 
			
		||||
                    class="image-slot"
 | 
			
		||||
                    :style="{
 | 
			
		||||
                      height: height + 'px',
 | 
			
		||||
                      lineHeight: height + 'px'
 | 
			
		||||
                    }"
 | 
			
		||||
                  >
 | 
			
		||||
                    正在加载图片<span class="dot">...</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
 | 
			
		||||
                <template #error>
 | 
			
		||||
                  <div class="image-slot">
 | 
			
		||||
                    <el-icon>
 | 
			
		||||
                      <Picture/>
 | 
			
		||||
                      <Picture />
 | 
			
		||||
                    </el-icon>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </template>
 | 
			
		||||
@@ -33,7 +42,7 @@
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div class="opt" v-if="data.showOpt &&data.image?.hash !== ''">
 | 
			
		||||
          <div class="opt" v-if="data.showOpt && data.image?.hash !== ''">
 | 
			
		||||
            <div class="opt-line">
 | 
			
		||||
              <ul>
 | 
			
		||||
                <li><a @click="upscale(1)">U1</a></li>
 | 
			
		||||
@@ -54,17 +63,16 @@
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref, watch} from "vue";
 | 
			
		||||
import {Picture} from "@element-plus/icons-vue";
 | 
			
		||||
import {httpPost} from "@/utils/http";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {showNotify} from "vant";
 | 
			
		||||
import { ref, watch } from "vue";
 | 
			
		||||
import { Picture } from "@element-plus/icons-vue";
 | 
			
		||||
import { httpPost } from "@/utils/http";
 | 
			
		||||
import { getSessionId } from "@/store/session";
 | 
			
		||||
import { showNotify } from "vant";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  content: Object,
 | 
			
		||||
@@ -74,36 +82,40 @@ const props = defineProps({
 | 
			
		||||
  createdAt: String
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const data = ref(props.content)
 | 
			
		||||
const cacheKey = "img_placeholder_height"
 | 
			
		||||
const data = ref(props.content);
 | 
			
		||||
const cacheKey = "img_placeholder_height";
 | 
			
		||||
const item = localStorage.getItem(cacheKey);
 | 
			
		||||
const loading = ref(false)
 | 
			
		||||
const height = ref(0)
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
const height = ref(0);
 | 
			
		||||
if (item) {
 | 
			
		||||
  height.value = parseInt(item)
 | 
			
		||||
  height.value = parseInt(item);
 | 
			
		||||
}
 | 
			
		||||
if (data.value["image"]?.width > 0) {
 | 
			
		||||
  height.value = 350 * data.value["image"]?.height / data.value["image"]?.width
 | 
			
		||||
  localStorage.setItem(cacheKey, height.value)
 | 
			
		||||
  height.value =
 | 
			
		||||
    (350 * data.value["image"]?.height) / data.value["image"]?.width;
 | 
			
		||||
  localStorage.setItem(cacheKey, height.value);
 | 
			
		||||
}
 | 
			
		||||
data.value["showOpt"] = data.value["content"]?.indexOf("- Image #") === -1;
 | 
			
		||||
// console.log(data.value)
 | 
			
		||||
 | 
			
		||||
watch(() => props.content, (newVal) => {
 | 
			
		||||
  data.value = newVal;
 | 
			
		||||
});
 | 
			
		||||
const emits = defineEmits(['disable-input', 'disable-input']);
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.content,
 | 
			
		||||
  (newVal) => {
 | 
			
		||||
    data.value = newVal;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
const emits = defineEmits(["disable-input", "disable-input"]);
 | 
			
		||||
const upscale = (index) => {
 | 
			
		||||
  send('/api/mj/upscale', index)
 | 
			
		||||
}
 | 
			
		||||
  send("/api/mj/upscale", index);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const variation = (index) => {
 | 
			
		||||
  send('/api/mj/variation', index)
 | 
			
		||||
}
 | 
			
		||||
  send("/api/mj/variation", index);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const send = (url, index) => {
 | 
			
		||||
  loading.value = true
 | 
			
		||||
  emits('disable-input')
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  emits("disable-input");
 | 
			
		||||
  httpPost(url, {
 | 
			
		||||
    index: index,
 | 
			
		||||
    src: "chat",
 | 
			
		||||
@@ -114,15 +126,20 @@ const send = (url, index) => {
 | 
			
		||||
    prompt: data.value?.["prompt"],
 | 
			
		||||
    chat_id: props.chatId,
 | 
			
		||||
    role_id: props.roleId,
 | 
			
		||||
    icon: props.icon,
 | 
			
		||||
  }).then(() => {
 | 
			
		||||
    showNotify({type: "success", message: "任务推送成功,请耐心等待任务执行..."})
 | 
			
		||||
    loading.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showNotify({type: "danger", message: "任务推送失败:" + e.message})
 | 
			
		||||
    emits('disable-input')
 | 
			
		||||
    icon: props.icon
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
    .then(() => {
 | 
			
		||||
      showNotify({
 | 
			
		||||
        type: "success",
 | 
			
		||||
        message: "任务推送成功,请耐心等待任务执行..."
 | 
			
		||||
      });
 | 
			
		||||
      loading.value = false;
 | 
			
		||||
    })
 | 
			
		||||
    .catch((e) => {
 | 
			
		||||
      showNotify({ type: "danger", message: "任务推送失败:" + e.message });
 | 
			
		||||
      emits("disable-input");
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@@ -268,4 +285,4 @@ const send = (url, index) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,25 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="black-dialog">
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="showDialog"
 | 
			
		||||
        style="--el-dialog-bg-color:#414141;
 | 
			
		||||
        --el-text-color-primary:#f1f1f1;
 | 
			
		||||
        --el-border-color:#414141;
 | 
			
		||||
        --el-color-primary:#21aa93;
 | 
			
		||||
        --el-color-primary-dark-2:#41555d;
 | 
			
		||||
        --el-color-white: #e1e1e1;
 | 
			
		||||
        --el-color-primary-light-3:#549688;
 | 
			
		||||
        --el-fill-color-blank:#616161;
 | 
			
		||||
         --el-color-primary-light-7:#717171;
 | 
			
		||||
        --el-color-primary-light-9:#717171;
 | 
			
		||||
        --el-text-color-regular:#e1e1e1"
 | 
			
		||||
        :title="title"
 | 
			
		||||
        :width="width"
 | 
			
		||||
        :before-close="cancel"
 | 
			
		||||
      v-model="showDialog"
 | 
			
		||||
      :title="title"
 | 
			
		||||
      :width="width"
 | 
			
		||||
      :before-close="cancel"
 | 
			
		||||
    >
 | 
			
		||||
      <div class="dialog-body">
 | 
			
		||||
        <slot></slot>
 | 
			
		||||
      </div>
 | 
			
		||||
      <template #footer v-if="!hideFooter">
 | 
			
		||||
        <div class="dialog-footer">
 | 
			
		||||
          <el-button @click="cancel">{{cancelText}}</el-button>
 | 
			
		||||
          <el-button type="primary" @click="$emit('confirm')" v-if="!hideConfirm">{{confirmText}}</el-button>
 | 
			
		||||
          <el-button @click="cancel" style="--el-border-radius-base: 8px">{{
 | 
			
		||||
            cancelText
 | 
			
		||||
          }}</el-button>
 | 
			
		||||
          <el-button
 | 
			
		||||
            type="primary"
 | 
			
		||||
            @click="$emit('confirm')"
 | 
			
		||||
            v-if="!hideConfirm"
 | 
			
		||||
            >{{ confirmText }}</el-button
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
@@ -31,45 +27,47 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
 | 
			
		||||
import {ref, watch} from "vue";
 | 
			
		||||
import { ref, watch } from "vue";
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  show : Boolean,
 | 
			
		||||
  show: Boolean,
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'Tips',
 | 
			
		||||
    default: "Tips"
 | 
			
		||||
  },
 | 
			
		||||
  width: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: 'auto',
 | 
			
		||||
    default: "auto"
 | 
			
		||||
  },
 | 
			
		||||
  hideFooter:{
 | 
			
		||||
  hideFooter: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false
 | 
			
		||||
  },
 | 
			
		||||
  hideConfirm:{
 | 
			
		||||
  hideConfirm: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
    default: false
 | 
			
		||||
  },
 | 
			
		||||
  confirmText: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '确定',
 | 
			
		||||
    default: "确定"
 | 
			
		||||
  },
 | 
			
		||||
  cancelText: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: '取消',
 | 
			
		||||
  },
 | 
			
		||||
    default: "取消"
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
const emits = defineEmits(['confirm','cancal']);
 | 
			
		||||
const showDialog = ref(props.show)
 | 
			
		||||
const emits = defineEmits(["confirm", "cancal"]);
 | 
			
		||||
const showDialog = ref(props.show);
 | 
			
		||||
 | 
			
		||||
watch(() => props.show, (newValue) => {
 | 
			
		||||
  showDialog.value = newValue
 | 
			
		||||
})
 | 
			
		||||
watch(
 | 
			
		||||
  () => props.show,
 | 
			
		||||
  (newValue) => {
 | 
			
		||||
    showDialog.value = newValue;
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
const cancel = () => {
 | 
			
		||||
  showDialog.value = false
 | 
			
		||||
  emits('cancal')
 | 
			
		||||
}
 | 
			
		||||
  showDialog.value = false;
 | 
			
		||||
  emits("cancal");
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
@@ -110,5 +108,4 @@ const cancel = () => {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
 
 | 
			
		||||