mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-04 00:10:25 +08:00
Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adc65439e4 | ||
|
|
445cf3716b | ||
|
|
f4b59b8503 | ||
|
|
4f08975df2 | ||
|
|
570db453d7 | ||
|
|
b93984bf6f | ||
|
|
e483db1b97 | ||
|
|
17d96acceb | ||
|
|
9900b236ef | ||
|
|
4fa52412c1 | ||
|
|
0076869deb | ||
|
|
af55193591 | ||
|
|
1d858118d5 | ||
|
|
8e64ba67fa | ||
|
|
58fb11b78f | ||
|
|
f6e9076a40 | ||
|
|
2fefa43aea | ||
|
|
f43b0467ba | ||
|
|
14da4d77e0 | ||
|
|
cefad86b85 | ||
|
|
738ff86344 | ||
|
|
110abc4ac7 | ||
|
|
5f1aaa40d8 | ||
|
|
0695ad9a85 | ||
|
|
7c086bbec8 | ||
|
|
c75fe7135a | ||
|
|
edf29976dd | ||
|
|
7b5f963ec4 | ||
|
|
f22badb861 | ||
|
|
8c501c90cd | ||
|
|
11eebdfcf0 | ||
|
|
46e331783f | ||
|
|
7e33e64659 | ||
|
|
7e4152ad6d | ||
|
|
d189ee7c22 | ||
|
|
641c2abb24 | ||
|
|
3ab4ac891b | ||
|
|
70b586e45a | ||
|
|
77aa724003 | ||
|
|
db7b50d96a | ||
|
|
00fee24a85 | ||
|
|
fa0cb73ec9 | ||
|
|
6ee815b71d | ||
|
|
311a048af5 | ||
|
|
3da9ecfaa3 | ||
|
|
4c2c6f613e | ||
|
|
ba63736871 | ||
|
|
ed3dbafc35 | ||
|
|
fdeffbd495 | ||
|
|
9870812e6b | ||
|
|
46f35e14c4 | ||
|
|
e89cf96ff4 | ||
|
|
5cd19bf38d | ||
|
|
a6f69f2b62 | ||
|
|
e34b9adada | ||
|
|
9f43f731b5 | ||
|
|
594ca43505 | ||
|
|
2a91cdb67a | ||
|
|
11148e720b | ||
|
|
c5835d6d8c | ||
|
|
4a26bb3ba5 | ||
|
|
4fec38724d | ||
|
|
85349df8a1 | ||
|
|
ffe250f8a9 | ||
|
|
eeb310a1d2 | ||
|
|
a42c606d20 | ||
|
|
4e64d46cd2 | ||
|
|
9886ee6828 | ||
|
|
0e1bde09c3 | ||
|
|
dda600709b | ||
|
|
15f38491b2 | ||
|
|
4b140732f7 | ||
|
|
4cb9ff3f14 | ||
|
|
e4f3e2c4c1 | ||
|
|
a80adb7dd8 | ||
|
|
195127a9d4 | ||
|
|
f2119b2c52 | ||
|
|
f15c45793b | ||
|
|
24f543e667 | ||
|
|
772995705f | ||
|
|
3475c39fe6 | ||
|
|
e6e393379f | ||
|
|
03cc91c3e5 | ||
|
|
f82f7bec6a | ||
|
|
4afd5bbd5e | ||
|
|
86aac2bf08 | ||
|
|
70c8b25a67 | ||
|
|
231af72444 | ||
|
|
480e930385 | ||
|
|
debc34f0fb | ||
|
|
99ce3bd099 | ||
|
|
99431cf9a2 | ||
|
|
83711c69f9 | ||
|
|
9e67032280 | ||
|
|
fa37937410 | ||
|
|
1be2cad78e | ||
|
|
2b1e687ed4 | ||
|
|
881009321b | ||
|
|
aed99b63b8 | ||
|
|
dfa34ba371 | ||
|
|
20beb30dd8 | ||
|
|
4475972af3 | ||
|
|
a843c65783 | ||
|
|
c2de1d3fa2 | ||
|
|
c8d091da06 | ||
|
|
553208ba57 | ||
|
|
072028699a | ||
|
|
9cdcf145a5 | ||
|
|
4df1c19e81 | ||
|
|
ac26a214bc | ||
|
|
ad616496d1 | ||
|
|
9870582779 | ||
|
|
20cc696b33 | ||
|
|
d7263f2b3c | ||
|
|
74e5ee41fb | ||
|
|
f936331dff | ||
|
|
ba311c3504 | ||
|
|
03291594b1 | ||
|
|
a6d9a4b5ae | ||
|
|
875de022c1 | ||
|
|
2c863a2774 | ||
|
|
f2f086a82c | ||
|
|
936ca61f94 | ||
|
|
87ae2f81fa | ||
|
|
ecf67db2b1 | ||
|
|
2e5589e112 | ||
|
|
2598a60898 | ||
|
|
0de226bbf3 | ||
|
|
422f0d8491 | ||
|
|
b028708b94 | ||
|
|
812c0d0f6a | ||
|
|
46df5293dd | ||
|
|
ab42b3e90b | ||
|
|
1378259cc7 | ||
|
|
c8f0b0a83f | ||
|
|
acec760ec1 | ||
|
|
2fe70d49f6 | ||
|
|
9013fff804 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@
|
||||
|
||||
*/node_modules/
|
||||
**/vendor/
|
||||
.idea
|
||||
out
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
<img src="https://img.shields.io/github/stars/may-fly/mayfly-go.svg?style=social" alt="github star"/>
|
||||
<img src="https://img.shields.io/github/forks/may-fly/mayfly-go.svg?style=social" alt="github fork"/>
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/mayflygo/mayfly-go/tags" target="_blank">
|
||||
<img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
|
||||
</a>
|
||||
<a href="https://github.com/golang/go" target="_blank">
|
||||
<img src="https://img.shields.io/badge/Golang-1.18%2B-yellow.svg" alt="golang"/>
|
||||
</a>
|
||||
@@ -34,7 +37,7 @@ web版 **linux(终端[终端回放] 文件 脚本 进程)、数据库(mysql po
|
||||
### 系统相关资料
|
||||
- 项目文档: https://objs.gitee.io/mayfly-go-docs
|
||||
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
|
||||
- 安装包下载:https://gitee.com/objs/mayfly-go/releases
|
||||
- 部署文档:https://objs.gitee.io/mayfly-go-docs/download
|
||||
|
||||
|
||||
### 系统核心功能截图
|
||||
|
||||
@@ -44,7 +44,7 @@ function build() {
|
||||
toFolder=$1
|
||||
os=$2
|
||||
arch=$3
|
||||
copyStatic=$4
|
||||
copyDocScript=$4
|
||||
|
||||
echo_yellow "-------------------${os}-${arch}打包构建开始-------------------"
|
||||
|
||||
@@ -68,17 +68,19 @@ function build() {
|
||||
echo_green "移动二进制文件至'${toFolder}'"
|
||||
mv ${server_folder}/${execFileName} ${toFolder}
|
||||
|
||||
if [ "${copy2Server}" == "1" ] ; then
|
||||
echo_green "拷贝前端静态页面至'${toFolder}/static'"
|
||||
mkdir -p ${toFolder}/static && cp -r ${web_folder}/dist/* ${toFolder}/static
|
||||
fi
|
||||
# if [ "${copy2Server}" == "1" ] ; then
|
||||
# echo_green "拷贝前端静态页面至'${toFolder}/static'"
|
||||
# mkdir -p ${toFolder}/static && cp -r ${web_folder}/dist/* ${toFolder}/static
|
||||
# fi
|
||||
|
||||
if [ "${copyDocScript}" == "1" ] ; then
|
||||
echo_green "拷贝脚本等资源文件[config.yml、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
|
||||
cp ${server_folder}/config.yml ${toFolder}
|
||||
cp ${server_folder}/mayfly-go.sql ${toFolder}
|
||||
cp ${server_folder}/readme.txt ${toFolder}
|
||||
cp ${server_folder}/startup.sh ${toFolder}
|
||||
cp ${server_folder}/shutdown.sh ${toFolder}
|
||||
fi
|
||||
|
||||
echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
|
||||
}
|
||||
@@ -99,45 +101,100 @@ function buildMac() {
|
||||
build "$1/mayfly-go-mac" "darwin" "amd64" $2
|
||||
}
|
||||
|
||||
function buildDocker() {
|
||||
echo_yellow "-------------------构建docker镜像开始-------------------"
|
||||
imageVersion=$1
|
||||
cd ${server_folder}
|
||||
imageName="mayflygo/mayfly-go:${imageVersion}"
|
||||
docker build -t "${imageName}" .
|
||||
echo_green "docker镜像构建完成->[${imageName}]"
|
||||
echo_yellow "-------------------构建docker镜像结束-------------------"
|
||||
}
|
||||
|
||||
function buildxDocker() {
|
||||
echo_yellow "-------------------docker buildx构建镜像开始-------------------"
|
||||
imageVersion=$1
|
||||
cd ${server_folder}
|
||||
imageName="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${imageVersion}"
|
||||
docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
|
||||
echo_green "docker多版本镜像构建完成->[${imageName}]"
|
||||
echo_yellow "-------------------docker buildx构建镜像结束-------------------"
|
||||
}
|
||||
|
||||
function runBuild() {
|
||||
read -p "请选择构建版本[0|其他->除docker镜像外其他 1->linux-amd64 2->linux-arm64 3->windows 4->mac 5->docker 6->docker buildx]: " buildType
|
||||
|
||||
toPath="."
|
||||
imageVersion="latest"
|
||||
copyDocScript="1"
|
||||
|
||||
if [[ "${buildType}" != "5" ]] && [[ "${buildType}" != "6" ]] ; then
|
||||
# 构建结果的目的路径
|
||||
read -p "请输入构建产物输出目录: " toPath
|
||||
read -p "请输入构建产物输出目录[默认当前路径]: " toPath
|
||||
if [ ! -d ${toPath} ] ; then
|
||||
echo_red "构建产物输出目录不存在!"
|
||||
exit;
|
||||
fi
|
||||
if [ "${toPath}" == "" ] ; then
|
||||
toPath="."
|
||||
fi
|
||||
|
||||
read -p "是否拷贝文档&脚本[0->否 1->是][默认是]: " copyDocScript
|
||||
if [ "${copyDocScript}" == "" ] ; then
|
||||
copyDocScript="1"
|
||||
fi
|
||||
|
||||
# 进入目标路径,并赋值全路径
|
||||
cd ${toPath}
|
||||
toPath=`pwd`
|
||||
|
||||
read -p "是否构建前端[0|其他->否 1->是 2->构建并拷贝至server/static/static]: " runBuildWeb
|
||||
read -p "请选择构建版本[0|其他->全部 1->linux-amd64 2->linux-arm64 3->windows 4->mac]: " buildType
|
||||
|
||||
|
||||
if [ "${runBuildWeb}" == "1" ] || [ "${runBuildWeb}" == "2" ] ; then
|
||||
buildWeb ${runBuildWeb}
|
||||
fi
|
||||
|
||||
if [[ "${buildType}" == "5" ]] || [[ "${buildType}" == "6" ]] ; then
|
||||
read -p "请输入docker镜像版本号[默认latest]: " imageVersion
|
||||
|
||||
if [ "${imageVersion}" == "" ] ; then
|
||||
imageVersion="latest"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
# read -p "是否构建前端[0|其他->否 1->是 2->构建并拷贝至server/static/static]: " runBuildWeb
|
||||
runBuildWeb="2"
|
||||
# 编译web前端
|
||||
buildWeb ${runBuildWeb}
|
||||
|
||||
case ${buildType} in
|
||||
"1")
|
||||
buildLinuxAmd64 ${toPath} ${runBuildWeb}
|
||||
buildLinuxAmd64 ${toPath} ${copyDocScript}
|
||||
;;
|
||||
"2")
|
||||
buildLinuxArm64 ${toPath} ${runBuildWeb}
|
||||
buildLinuxArm64 ${toPath} ${copyDocScript}
|
||||
;;
|
||||
"3")
|
||||
buildWindows ${toPath} ${runBuildWeb}
|
||||
buildWindows ${toPath} ${copyDocScript}
|
||||
;;
|
||||
"4")
|
||||
buildMac ${toPath} ${runBuildWeb}
|
||||
buildMac ${toPath} ${copyDocScript}
|
||||
;;
|
||||
"5")
|
||||
buildDocker ${imageVersion}
|
||||
;;
|
||||
"6")
|
||||
buildxDocker ${imageVersion}
|
||||
;;
|
||||
*)
|
||||
buildLinuxAmd64 ${toPath} ${runBuildWeb}
|
||||
buildLinuxArm64 ${toPath} ${runBuildWeb}
|
||||
buildWindows ${toPath} ${runBuildWeb}
|
||||
buildMac ${toPath} ${runBuildWeb}
|
||||
buildLinuxAmd64 ${toPath} ${copyDocScript}
|
||||
buildLinuxArm64 ${toPath} ${copyDocScript}
|
||||
buildWindows ${toPath} ${copyDocScript}
|
||||
buildMac ${toPath} ${copyDocScript}
|
||||
;;
|
||||
esac
|
||||
|
||||
echo_green "删除['${server_folder}/static/static']下静态资源文件."
|
||||
# 删除静态资源文件,保留一个favicon.ico,否则后端启动会报错
|
||||
rm -rf ${server_folder}/static/static/assets
|
||||
rm -rf ${server_folder}/static/static/config.js
|
||||
rm -rf ${server_folder}/static/static/index.html
|
||||
}
|
||||
|
||||
runBuild
|
||||
@@ -2,4 +2,4 @@
|
||||
ENV = 'production'
|
||||
|
||||
# 线上环境接口地址
|
||||
VITE_API_URL = 'http://api.mayflygo.1yue.net/api'
|
||||
VITE_API_URL = '/api'
|
||||
@@ -11,21 +11,33 @@ module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
sourceType: 'module',
|
||||
},
|
||||
extends: ['plugin:vue/essential'],
|
||||
// plugins: ['vue', '@typescript-eslint'],
|
||||
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
|
||||
plugins: ['vue', '@typescript-eslint'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts', '*.tsx', '*.vue'],
|
||||
rules: {
|
||||
'no-undef': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
// http://eslint.cn/docs/rules/
|
||||
// https://eslint.vuejs.org/rules/
|
||||
'@type-eslint/ban-ts-ignore': 'off',
|
||||
'@type-eslint/explicit-function-return-type': 'off',
|
||||
'@type-eslint/no-explicit-any': 'off',
|
||||
'@type-eslint/no-var-requires': 'off',
|
||||
'@type-eslint/no-empty-function': 'off',
|
||||
'@type-eslint/no-use-before-define': 'off',
|
||||
'@type-eslint/ban-ts-comment': 'off',
|
||||
'@type-eslint/ban-types': 'off',
|
||||
'@type-eslint/no-non-null-assertion': 'off',
|
||||
'@type-eslint/explicit-module-boundary-types': 'off',
|
||||
// https://typescript-eslint.io/rules/no-unused-vars/
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-redeclare': 'error',
|
||||
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [2],
|
||||
'vue/custom-event-name-casing': 'off',
|
||||
'vue/attributes-order': 'off',
|
||||
'vue/one-component-per-file': 'off',
|
||||
@@ -43,6 +55,12 @@ module.exports = {
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/comment-directive': 'off',
|
||||
'vue/no-parsing-error': 'off',
|
||||
'vue/no-deprecated-v-on-native-modifier': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'no-sparse-arrays': 'off',
|
||||
'no-prototype-builtins': 'off',
|
||||
'no-constant-condition': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
@@ -51,5 +69,8 @@ module.exports = {
|
||||
'no-multiple-template-root': 'off',
|
||||
'no-unused-vars': 'error',
|
||||
'no-v-model-argument': 'off',
|
||||
'no-case-declarations': 'off',
|
||||
'no-console': 'error',
|
||||
'no-redeclare': 'off',
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="zh_CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
|
||||
5164
mayfly_go_web/package-lock.json
generated
Normal file
5164
mayfly_go_web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,31 +4,34 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"build-preview": "npm run build && npm run preview",
|
||||
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.0.9",
|
||||
"asciinema-player": "^3.0.1",
|
||||
"axios": "^1.1.2",
|
||||
"codemirror": "^5.65.5",
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"asciinema-player": "^3.3.0",
|
||||
"axios": "^1.4.0",
|
||||
"countup.js": "^2.0.7",
|
||||
"cropperjs": "^1.5.11",
|
||||
"echarts": "^5.3.3",
|
||||
"element-plus": "^2.2.18",
|
||||
"jsencrypt": "^3.2.1",
|
||||
"jsoneditor": "^9.9.2",
|
||||
"echarts": "^5.4.0",
|
||||
"element-plus": "^2.3.5",
|
||||
"jsencrypt": "^3.3.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
"monaco-editor": "^0.38.0",
|
||||
"monaco-sql-languages": "^0.11.0",
|
||||
"monaco-themes": "^0.4.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.3",
|
||||
"screenfull": "^6.0.2",
|
||||
"sortablejs": "^1.13.0",
|
||||
"sql-formatter": "^9.2.0",
|
||||
"vue": "^3.2.41",
|
||||
"sql-formatter": "^12.1.2",
|
||||
"vue": "^3.3.4",
|
||||
"vue-clipboard3": "^1.0.1",
|
||||
"vue-router": "^4.1.5",
|
||||
"vuex": "^4.0.2",
|
||||
"xterm": "^5.0.0",
|
||||
"xterm-addon-fit": "^0.6.0"
|
||||
"vue-router": "^4.2.2",
|
||||
"xterm": "^5.2.1",
|
||||
"xterm-addon-fit": "^0.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.178",
|
||||
@@ -37,17 +40,17 @@
|
||||
"@types/sortablejs": "^1.10.6",
|
||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
"@vitejs/plugin-vue": "^2.3.3",
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"@vue/compiler-sfc": "^3.0.11",
|
||||
"dotenv": "^10.0.0",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"prettier": "^2.3.0",
|
||||
"sass": "^1.45.1",
|
||||
"sass-loader": "^12.4.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^2.9.13",
|
||||
"vue-eslint-parser": "^8.0.1"
|
||||
"sass": "^1.62.0",
|
||||
"sass-loader": "^13.2.0",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.3.9",
|
||||
"vue-eslint-parser": "^9.1.1"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
||||
1
mayfly_go_web/plugins.d.ts
vendored
1
mayfly_go_web/plugins.d.ts
vendored
@@ -1 +0,0 @@
|
||||
declare module 'vue-grid-layout';
|
||||
@@ -3,3 +3,22 @@ window.globalConfig = {
|
||||
"BaseApiUrl": "",
|
||||
"BaseWsUrl": ""
|
||||
}
|
||||
|
||||
// index.html添加百秒级时间戳,防止被浏览器缓存
|
||||
!function () {
|
||||
let t = "t=" + new Date().getTime().toString().substring(0, 8)
|
||||
let search = location.search;
|
||||
let m = search && search.match(/t=\d*/g)
|
||||
|
||||
if (m[0]) {
|
||||
if (m[0] !== t) {
|
||||
location.search = search.replace(m[0], t)
|
||||
}
|
||||
} else {
|
||||
if (search.indexOf('?') > -1) {
|
||||
location.search = search + '&' + t
|
||||
} else {
|
||||
location.search = t
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
10
mayfly_go_web/shim.d.ts
vendored
10
mayfly_go_web/shim.d.ts
vendored
@@ -1,10 +0,0 @@
|
||||
/* eslint-disable */
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
declare module 'codemirror';
|
||||
declare module 'sql-formatter';
|
||||
declare module 'jsoneditor';
|
||||
declare module 'asciinema-player';
|
||||
@@ -1,34 +1,32 @@
|
||||
<template>
|
||||
<router-view v-show="getThemeConfig.lockScreenTime !== 0" />
|
||||
<LockScreen v-if="getThemeConfig.isLockScreen" />
|
||||
<Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
|
||||
<router-view v-show="themeConfig.lockScreenTime !== 0" />
|
||||
<LockScreen v-if="themeConfig.isLockScreen" />
|
||||
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
|
||||
<script setup lang="ts" name="app">
|
||||
import { ref, onBeforeMount, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { getLocal } from '@/common/utils/storage.ts';
|
||||
// import { useTagsViewRoutes } from '@/store/tagsViewRoutes';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { getLocal } from '@/common/utils/storage';
|
||||
import LockScreen from '@/views/layout/lockScreen/index.vue';
|
||||
import Setings from '@/views/layout/navBars/breadcrumb/setings.vue';
|
||||
import Watermark from '@/common/utils/wartermark.ts';
|
||||
import Watermark from '@/common/utils/wartermark';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'app',
|
||||
components: { LockScreen, Setings },
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const setingsRef = ref();
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
|
||||
const themeConfigStores = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(themeConfigStores);
|
||||
|
||||
// 布局配置弹窗打开
|
||||
const openSetingsDrawer = () => {
|
||||
setingsRef.value.openDrawer();
|
||||
};
|
||||
|
||||
// 设置初始化,防止刷新时恢复默认
|
||||
onBeforeMount(() => {
|
||||
// 设置批量第三方 icon 图标
|
||||
@@ -36,24 +34,27 @@ export default defineComponent({
|
||||
// // 设置批量第三方 js
|
||||
// setIntroduction.jsCdn();
|
||||
});
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
// 监听布局配置弹窗点击打开
|
||||
proxy.mittBus.on('openSetingsDrawer', () => {
|
||||
mittBus.on('openSetingsDrawer', () => {
|
||||
openSetingsDrawer();
|
||||
});
|
||||
// 获取缓存中的布局配置
|
||||
if (getLocal('themeConfig')) {
|
||||
store.dispatch('themeConfig/setThemeConfig', getLocal('themeConfig'));
|
||||
themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') })
|
||||
document.documentElement.style.cssText = getLocal('themeConfigStyle');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 页面销毁时,关闭监听布局配置
|
||||
onUnmounted(() => {
|
||||
proxy.mittBus.off('openSetingsDrawer', () => {});
|
||||
mittBus.off('openSetingsDrawer', () => { });
|
||||
});
|
||||
|
||||
// 监听路由的变化,设置网站标题
|
||||
watch(
|
||||
() => route.path,
|
||||
@@ -61,14 +62,8 @@ export default defineComponent({
|
||||
nextTick(() => {
|
||||
// 路由变化更新水印
|
||||
Watermark.use();
|
||||
document.title = `${route.meta.title} - ${getThemeConfig.value.globalTitle}` || getThemeConfig.value.globalTitle;
|
||||
document.title = `${route.meta.title} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
|
||||
});
|
||||
}
|
||||
);
|
||||
return {
|
||||
setingsRef,
|
||||
getThemeConfig,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
1
mayfly_go_web/src/assets/iconfont/iconfont.js
Normal file
1
mayfly_go_web/src/assets/iconfont/iconfont.js
Normal file
File diff suppressed because one or more lines are too long
51
mayfly_go_web/src/assets/iconfont/iconfont.json
Normal file
51
mayfly_go_web/src/assets/iconfont/iconfont.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"id": "3953964",
|
||||
"name": "mayfly-go",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "23957582",
|
||||
"name": "MongoDB",
|
||||
"font_class": "mongo",
|
||||
"unicode": "e646",
|
||||
"unicode_decimal": 58950
|
||||
},
|
||||
{
|
||||
"icon_id": "4969649",
|
||||
"name": "Redis",
|
||||
"font_class": "op-redis",
|
||||
"unicode": "e728",
|
||||
"unicode_decimal": 59176
|
||||
},
|
||||
{
|
||||
"icon_id": "22442993",
|
||||
"name": "PostgreSQL",
|
||||
"font_class": "op-postgres",
|
||||
"unicode": "e8b7",
|
||||
"unicode_decimal": 59575
|
||||
},
|
||||
{
|
||||
"icon_id": "10055634",
|
||||
"name": "云数据库MongoDB",
|
||||
"font_class": "op-mongo",
|
||||
"unicode": "e7d7",
|
||||
"unicode_decimal": 59351
|
||||
},
|
||||
{
|
||||
"icon_id": "10055642",
|
||||
"name": "云数据库 RDS MySQL",
|
||||
"font_class": "op-mysql",
|
||||
"unicode": "e7d8",
|
||||
"unicode_decimal": 59352
|
||||
},
|
||||
{
|
||||
"icon_id": "3876165",
|
||||
"name": "redis",
|
||||
"font_class": "redis",
|
||||
"unicode": "e619",
|
||||
"unicode_decimal": 58905
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -19,24 +19,6 @@ class Api {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置rl
|
||||
* @param {String} uri 请求url
|
||||
*/
|
||||
setUrl(url: string) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* url的请求方法
|
||||
* @param {String} method 请求方法
|
||||
*/
|
||||
setMethod(method: string) {
|
||||
this.method = method;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限的完整url
|
||||
*/
|
||||
@@ -46,7 +28,7 @@ class Api {
|
||||
|
||||
/**
|
||||
* 操作该权限,即请求对应的url
|
||||
* @param {Object} param 请求该权限的参数
|
||||
* @param {Object} param 请求该api的参数
|
||||
*/
|
||||
request(param: any = null, options: any = null): Promise<any> {
|
||||
return request.send(this, param, options);
|
||||
@@ -54,7 +36,8 @@ class Api {
|
||||
|
||||
/**
|
||||
* 操作该权限,即请求对应的url
|
||||
* @param {Object} param 请求该权限的参数
|
||||
* @param {Object} param 请求该api的参数
|
||||
* @param headers headers
|
||||
*/
|
||||
requestWithHeaders(param: any, headers: any): Promise<any> {
|
||||
return request.sendWithHeaders(this, param, headers);
|
||||
@@ -68,9 +51,41 @@ class Api {
|
||||
* @param url url
|
||||
* @param method 请求方法(get,post,put,delete...)
|
||||
*/
|
||||
static create(url: string, method: string) {
|
||||
static create(url: string, method: string) :Api {
|
||||
return new Api(url, method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建get api
|
||||
* @param url url
|
||||
*/
|
||||
static newGet(url: string): Api {
|
||||
return Api.create(url, 'get');
|
||||
}
|
||||
|
||||
/**
|
||||
* new post api
|
||||
* @param url url
|
||||
*/
|
||||
static newPost(url: string): Api {
|
||||
return Api.create(url, 'post');
|
||||
}
|
||||
|
||||
/**
|
||||
* new put api
|
||||
* @param url url
|
||||
*/
|
||||
static newPut(url: string): Api {
|
||||
return Api.create(url, 'put');
|
||||
}
|
||||
|
||||
/**
|
||||
* new delete api
|
||||
* @param url url
|
||||
*/
|
||||
static newDelete(url: string): Api {
|
||||
return Api.create(url, 'delete');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
function getBaseApiUrl() {
|
||||
let path = window.location.pathname;
|
||||
if (path == '/') {
|
||||
return window.location.host;
|
||||
}
|
||||
return window.location.host + path;
|
||||
}
|
||||
|
||||
const config = {
|
||||
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl}/api`,
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${location.host}`}/api`
|
||||
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl || location.protocol + '//' + getBaseApiUrl()}/api`,
|
||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||
|
||||
// 系统版本
|
||||
version: 'v1.4.3'
|
||||
}
|
||||
|
||||
export default config
|
||||
@@ -1,4 +1,42 @@
|
||||
import * as echarts from 'echarts'
|
||||
// import * as echarts from 'echarts'
|
||||
|
||||
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
|
||||
import * as echarts from "echarts/core";
|
||||
|
||||
/** 图表后缀都为 Chart */
|
||||
import { PieChart } from "echarts/charts";
|
||||
|
||||
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
|
||||
import {
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DatasetComponent,
|
||||
TransformComponent,
|
||||
LegendComponent,
|
||||
} from "echarts/components";
|
||||
|
||||
// 标签自动布局,全局过渡动画等特性
|
||||
import { LabelLayout, UniversalTransition } from "echarts/features";
|
||||
|
||||
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
|
||||
// 注册必须的组件
|
||||
echarts.use([
|
||||
TitleComponent,
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DatasetComponent,
|
||||
TransformComponent,
|
||||
LegendComponent,
|
||||
// BarChart,
|
||||
LabelLayout,
|
||||
UniversalTransition,
|
||||
CanvasRenderer,
|
||||
// LineChart,
|
||||
PieChart,
|
||||
]);
|
||||
|
||||
export default function(dom: any, theme: any = null, option: any) {
|
||||
let chart = echarts.init(dom, theme);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import request from './request'
|
||||
import Api from './Api'
|
||||
|
||||
export default {
|
||||
login: (param: any) => request.request('POST', '/sys/accounts/login', param),
|
||||
changePwd: (param: any) => request.request('POST', '/sys/accounts/change-pwd', param),
|
||||
getPublicKey: () => request.request('GET', '/common/public-key'),
|
||||
getConfigValue: (param: any) => request.request('GET', '/sys/configs/value', param),
|
||||
captcha: () => request.request('GET', '/sys/captcha'),
|
||||
logout: (param: any) => request.request('POST', '/sys/accounts/logout/{token}', param),
|
||||
getMenuRoute: (param: any) => request.request('Get', '/sys/resources/account', param)
|
||||
login: Api.newPost("/sys/accounts/login"),
|
||||
changePwd: Api.newPost("/sys/accounts/change-pwd"),
|
||||
getPublicKey: Api.newGet("/common/public-key"),
|
||||
getConfigValue: Api.newGet("/sys/configs/value"),
|
||||
captcha: Api.newGet("/sys/captcha"),
|
||||
logout: Api.newPost("/sys/accounts/logout/{token}"),
|
||||
getPermissions: Api.newGet("/sys/accounts/permissions")
|
||||
}
|
||||
@@ -22,7 +22,8 @@ export interface Result {
|
||||
data?: any;
|
||||
}
|
||||
|
||||
const baseUrl: string = config.baseApiUrl as string
|
||||
const baseUrl: string = config.baseApiUrl
|
||||
const baseWsUrl: string = config.baseWsUrl
|
||||
|
||||
/**
|
||||
* 通知错误消息
|
||||
@@ -115,9 +116,8 @@ function request(method: string, url: string, params: any = null, headers: any =
|
||||
query.headers = headers
|
||||
}
|
||||
|
||||
const lowMethod = method.toLowerCase();
|
||||
// post和put使用json格式传参
|
||||
if (lowMethod === 'post' || lowMethod === 'put') {
|
||||
if (method === 'post' || method === 'put') {
|
||||
query.data = params;
|
||||
} else {
|
||||
query.params = params;
|
||||
@@ -155,6 +155,7 @@ function getApiUrl(url: string) {
|
||||
return baseUrl + url + '?token=' + getSession('token');
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
request,
|
||||
send,
|
||||
|
||||
@@ -9,7 +9,7 @@ export async function getRsaPublicKey() {
|
||||
if (publicKey) {
|
||||
return publicKey
|
||||
}
|
||||
publicKey = await openApi.getPublicKey() as string
|
||||
publicKey = await openApi.getPublicKey.request() as string
|
||||
sessionStorage.setItem('RsaPublicKey', publicKey)
|
||||
return publicKey
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const UseWartermarkConfigKey = "UseWartermark"
|
||||
* @returns 配置值
|
||||
*/
|
||||
export async function getConfigValue(key: string) : Promise<string> {
|
||||
return await openApi.getConfigValue({key}) as string
|
||||
return await openApi.getConfigValue.request({key}) as string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { store } from '@/store/index.ts';
|
||||
import { judementSameArr } from '@/common/utils/arrayOperation.ts';
|
||||
|
||||
// 单个权限验证
|
||||
export function auth(value: string) {
|
||||
return store.state.userInfos.userInfos.permissions.some((v: any) => v === value);
|
||||
}
|
||||
|
||||
// 多个权限验证,满足一个则为 true
|
||||
export function auths(value: Array<string>) {
|
||||
let flag = false;
|
||||
store.state.userInfos.userInfos.permissions.map((val: any) => {
|
||||
value.map((v: any) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
});
|
||||
return flag;
|
||||
}
|
||||
|
||||
// 多个权限验证,全部满足则为 true
|
||||
export function authAll(value: Array<string>) {
|
||||
return judementSameArr(value, store.state.userInfos.userInfos.permissions);
|
||||
}
|
||||
39
mayfly_go_web/src/common/utils/export.ts
Normal file
39
mayfly_go_web/src/common/utils/export.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
export function exportCsv(filename: string, columns: string[], datas: []) {
|
||||
// 二维数组
|
||||
const cvsData = [columns];
|
||||
for (let data of datas) {
|
||||
// 数据值组成的一维数组
|
||||
let dataValueArr: any = [];
|
||||
for (let column of columns) {
|
||||
let val: any = data[column];
|
||||
if (typeof val == 'string' && val) {
|
||||
// csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
|
||||
if (val.indexOf(',') != -1) {
|
||||
// 如果还有双引号,先将双引号转义,避免两边加了双引号后转义错误
|
||||
if (val.indexOf('"') != -1) {
|
||||
val = val.replace(/\"/g, "\"\"");
|
||||
}
|
||||
// 再将逗号转义
|
||||
val = `"${val}"`;
|
||||
}
|
||||
dataValueArr.push(val);
|
||||
} else {
|
||||
dataValueArr.push(val);
|
||||
}
|
||||
|
||||
}
|
||||
cvsData.push(dataValueArr);
|
||||
}
|
||||
const csvString = cvsData.map((e) => e.join(',')).join('\n');
|
||||
// 导出
|
||||
let link = document.createElement('a');
|
||||
let exportContent = '\uFEFF';
|
||||
let blob = new Blob([exportContent + csvString], {
|
||||
type: 'text/plain;charset=utrf-8',
|
||||
});
|
||||
link.id = 'download-csv';
|
||||
link.setAttribute('href', URL.createObjectURL(blob));
|
||||
link.setAttribute('download', `${filename}.csv`);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
}
|
||||
@@ -73,3 +73,123 @@ export function formatJsonString(txt: string, compress: boolean) {
|
||||
notify('', data, isLast, indent, false);
|
||||
return draw.join('');
|
||||
}
|
||||
|
||||
/*
|
||||
* 年(Y) 可用1-4个占位符
|
||||
* 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
|
||||
* 星期(W) 可用1-3个占位符
|
||||
* 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
|
||||
*
|
||||
* let date = new Date()
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS") // 2020-02-09 14:04:23
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS Q") // 2020-02-09 14:09:03 一
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW") // 2020-02-09 14:45:12 星期日
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ") // 2020-02-09 14:09:36 第一季度
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-02-09 14:46:12 星期日 第一季度
|
||||
*/
|
||||
export function formatDate(date: Date, format: string) {
|
||||
let we = date.getDay(); // 星期
|
||||
let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
|
||||
const opt: any = {
|
||||
'Y+': date.getFullYear().toString(), // 年
|
||||
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
|
||||
'd+': date.getDate().toString(), // 日
|
||||
'H+': date.getHours().toString(), // 时
|
||||
'M+': date.getMinutes().toString(), // 分
|
||||
'S+': date.getSeconds().toString(), // 秒
|
||||
'q+': qut, // 季度
|
||||
};
|
||||
// 中文数字 (星期)
|
||||
const week: any = {
|
||||
'0': '日',
|
||||
'1': '一',
|
||||
'2': '二',
|
||||
'3': '三',
|
||||
'4': '四',
|
||||
'5': '五',
|
||||
'6': '六',
|
||||
};
|
||||
// 中文数字(季度)
|
||||
const quarter: any = {
|
||||
'1': '一',
|
||||
'2': '二',
|
||||
'3': '三',
|
||||
'4': '四',
|
||||
};
|
||||
if (/(W+)/.test(format))
|
||||
format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
|
||||
if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
|
||||
for (let k in opt) {
|
||||
let r = new RegExp('(' + k + ')').exec(format);
|
||||
// 若输入的长度不为1,则前面补零
|
||||
if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* 10秒: 10 * 1000
|
||||
* 1分: 60 * 1000
|
||||
* 1小时: 60 * 60 * 1000
|
||||
* 24小时:60 * 60 * 24 * 1000
|
||||
* 3天: 60 * 60* 24 * 1000 * 3
|
||||
*
|
||||
* let data = new Date()
|
||||
* formatPast(data) // 刚刚
|
||||
* formatPast(data - 11 * 1000) // 11秒前
|
||||
* formatPast(data - 2 * 60 * 1000) // 2分钟前
|
||||
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
|
||||
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
|
||||
* formatPast(data - 60 * 60 * 71 * 1000) // 2天前
|
||||
* formatPast("2020-06-01") // 2020-06-01
|
||||
* formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-06-01 08:00:00 星期一 第二季度
|
||||
*/
|
||||
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
|
||||
// 传入格式处理、存储转换值
|
||||
let t: any, s: any;
|
||||
// 获取js 时间戳
|
||||
let time: any = new Date().getTime();
|
||||
// 是否是对象
|
||||
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
|
||||
// 当前时间戳 - 传入时间戳
|
||||
time = Number.parseInt(`${time - t}`);
|
||||
if (time < 10000) {
|
||||
// 10秒内
|
||||
return '刚刚';
|
||||
} else if (time < 60000 && time >= 10000) {
|
||||
// 超过10秒少于1分钟内
|
||||
s = Math.floor(time / 1000);
|
||||
return `${s}秒前`;
|
||||
} else if (time < 3600000 && time >= 60000) {
|
||||
// 超过1分钟少于1小时
|
||||
s = Math.floor(time / 60000);
|
||||
return `${s}分钟前`;
|
||||
} else if (time < 86400000 && time >= 3600000) {
|
||||
// 超过1小时少于24小时
|
||||
s = Math.floor(time / 3600000);
|
||||
return `${s}小时前`;
|
||||
} else if (time < 259200000 && time >= 86400000) {
|
||||
// 超过1天少于3天内
|
||||
s = Math.floor(time / 86400000);
|
||||
return `${s}天前`;
|
||||
} else {
|
||||
// 超过3天
|
||||
let date = typeof param === 'string' || 'object' ? new Date(param) : param;
|
||||
return formatDate(date, format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* formatAxis(new Date()) // 上午好
|
||||
*/
|
||||
export function formatAxis(param: any) {
|
||||
let hour: number = new Date(param).getHours();
|
||||
if (hour < 6) return '凌晨好';
|
||||
else if (hour < 9) return '早上好';
|
||||
else if (hour < 12) return '上午好';
|
||||
else if (hour < 14) return '中午好';
|
||||
else if (hour < 17) return '下午好';
|
||||
else if (hour < 19) return '傍晚好';
|
||||
else if (hour < 22) return '晚上好';
|
||||
else return '夜里好';
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
/*
|
||||
* 年(Y) 可用1-4个占位符
|
||||
* 月(m)、日(d)、小时(H)、分(M)、秒(S) 可用1-2个占位符
|
||||
* 星期(W) 可用1-3个占位符
|
||||
* 季度(q为阿拉伯数字,Q为中文数字)可用1或4个占位符
|
||||
*
|
||||
* let date = new Date()
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS") // 2020-02-09 14:04:23
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS Q") // 2020-02-09 14:09:03 一
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW") // 2020-02-09 14:45:12 星期日
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS QQQQ") // 2020-02-09 14:09:36 第一季度
|
||||
* formatDate(date, "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-02-09 14:46:12 星期日 第一季度
|
||||
*/
|
||||
export function formatDate(date: Date, format: string) {
|
||||
let we = date.getDay(); // 星期
|
||||
let qut = Math.floor((date.getMonth() + 3) / 3).toString(); // 季度
|
||||
const opt: any = {
|
||||
'Y+': date.getFullYear().toString(), // 年
|
||||
'm+': (date.getMonth() + 1).toString(), // 月(月份从0开始,要+1)
|
||||
'd+': date.getDate().toString(), // 日
|
||||
'H+': date.getHours().toString(), // 时
|
||||
'M+': date.getMinutes().toString(), // 分
|
||||
'S+': date.getSeconds().toString(), // 秒
|
||||
'q+': qut, // 季度
|
||||
};
|
||||
// 中文数字 (星期)
|
||||
const week: any = {
|
||||
'0': '日',
|
||||
'1': '一',
|
||||
'2': '二',
|
||||
'3': '三',
|
||||
'4': '四',
|
||||
'5': '五',
|
||||
'6': '六',
|
||||
};
|
||||
// 中文数字(季度)
|
||||
const quarter: any = {
|
||||
'1': '一',
|
||||
'2': '二',
|
||||
'3': '三',
|
||||
'4': '四',
|
||||
};
|
||||
if (/(W+)/.test(format))
|
||||
format = format.replace(RegExp.$1, RegExp.$1.length > 1 ? (RegExp.$1.length > 2 ? '星期' + week[we] : '周' + week[we]) : week[we]);
|
||||
if (/(Q+)/.test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 4 ? '第' + quarter[qut] + '季度' : quarter[qut]);
|
||||
for (let k in opt) {
|
||||
let r = new RegExp('(' + k + ')').exec(format);
|
||||
// 若输入的长度不为1,则前面补零
|
||||
if (r) format = format.replace(r[1], RegExp.$1.length == 1 ? opt[k] : opt[k].padStart(RegExp.$1.length, '0'));
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* 10秒: 10 * 1000
|
||||
* 1分: 60 * 1000
|
||||
* 1小时: 60 * 60 * 1000
|
||||
* 24小时:60 * 60 * 24 * 1000
|
||||
* 3天: 60 * 60* 24 * 1000 * 3
|
||||
*
|
||||
* let data = new Date()
|
||||
* formatPast(data) // 刚刚
|
||||
* formatPast(data - 11 * 1000) // 11秒前
|
||||
* formatPast(data - 2 * 60 * 1000) // 2分钟前
|
||||
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
|
||||
* formatPast(data - 60 * 60 * 2 * 1000) // 2小时前
|
||||
* formatPast(data - 60 * 60 * 71 * 1000) // 2天前
|
||||
* formatPast("2020-06-01") // 2020-06-01
|
||||
* formatPast("2020-06-01", "YYYY-mm-dd HH:MM:SS WWW QQQQ") // 2020-06-01 08:00:00 星期一 第二季度
|
||||
*/
|
||||
export function formatPast(param: any, format: string = 'YYYY-mm-dd') {
|
||||
// 传入格式处理、存储转换值
|
||||
let t: any, s: any;
|
||||
// 获取js 时间戳
|
||||
let time: any = new Date().getTime();
|
||||
// 是否是对象
|
||||
typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param);
|
||||
// 当前时间戳 - 传入时间戳
|
||||
time = Number.parseInt(`${time - t}`);
|
||||
if (time < 10000) {
|
||||
// 10秒内
|
||||
return '刚刚';
|
||||
} else if (time < 60000 && time >= 10000) {
|
||||
// 超过10秒少于1分钟内
|
||||
s = Math.floor(time / 1000);
|
||||
return `${s}秒前`;
|
||||
} else if (time < 3600000 && time >= 60000) {
|
||||
// 超过1分钟少于1小时
|
||||
s = Math.floor(time / 60000);
|
||||
return `${s}分钟前`;
|
||||
} else if (time < 86400000 && time >= 3600000) {
|
||||
// 超过1小时少于24小时
|
||||
s = Math.floor(time / 3600000);
|
||||
return `${s}小时前`;
|
||||
} else if (time < 259200000 && time >= 86400000) {
|
||||
// 超过1天少于3天内
|
||||
s = Math.floor(time / 86400000);
|
||||
return `${s}天前`;
|
||||
} else {
|
||||
// 超过3天
|
||||
let date = typeof param === 'string' || 'object' ? new Date(param) : param;
|
||||
return formatDate(date, format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* formatAxis(new Date()) // 上午好
|
||||
*/
|
||||
export function formatAxis(param: any) {
|
||||
let hour: number = new Date(param).getHours();
|
||||
if (hour < 6) return '凌晨好';
|
||||
else if (hour < 9) return '早上好';
|
||||
else if (hour < 12) return '上午好';
|
||||
else if (hour < 14) return '中午好';
|
||||
else if (hour < 17) return '下午好';
|
||||
else if (hour < 19) return '傍晚好';
|
||||
else if (hour < 22) return '晚上好';
|
||||
else return '夜里好';
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { nextTick } from 'vue';
|
||||
import loadingCss from '@/theme/loading.scss';
|
||||
import loadingCss from "@/theme/loading.scss?inline"
|
||||
|
||||
// 定义方法
|
||||
export const NextLoading = {
|
||||
|
||||
8
mayfly_go_web/src/common/utils/mitt.ts
Normal file
8
mayfly_go_web/src/common/utils/mitt.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// https://www.npmjs.com/package/mitt
|
||||
import mitt, { Emitter } from 'mitt';
|
||||
|
||||
// 类型
|
||||
const emitter: Emitter<any> = mitt<any>();
|
||||
|
||||
// 导出
|
||||
export default emitter;
|
||||
@@ -1,12 +1,11 @@
|
||||
// 字体图标 url
|
||||
const cssCdnUrlList: Array<string> = [
|
||||
'//at.alicdn.com/t/font_2298093_ysc3z187xhh.css',
|
||||
'//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css',
|
||||
|
||||
];
|
||||
// 第三方 js url
|
||||
const jsCdnUrlList: Array<string> = [];
|
||||
|
||||
// 动态设置字体图标
|
||||
// 动态批量设置字体图标
|
||||
export function setCssCdn() {
|
||||
if (cssCdnUrlList.length <= 0) return false;
|
||||
cssCdnUrlList.map((v) => {
|
||||
@@ -18,7 +17,7 @@ export function setCssCdn() {
|
||||
});
|
||||
}
|
||||
|
||||
// 批量设置第三方js
|
||||
// 动态批量设置第三方js
|
||||
export function setJsCdn() {
|
||||
if (jsCdnUrlList.length <= 0) return false;
|
||||
jsCdnUrlList.map((v) => {
|
||||
@@ -28,11 +27,17 @@ export function setJsCdn() {
|
||||
});
|
||||
}
|
||||
|
||||
// 设置执行函数
|
||||
/**
|
||||
* 批量设置字体图标、动态js
|
||||
* @method cssCdn 动态批量设置字体图标
|
||||
* @method jsCdn 动态批量设置第三方js
|
||||
*/
|
||||
const setIntroduction = {
|
||||
// 设置css
|
||||
cssCdn: () => {
|
||||
setCssCdn();
|
||||
},
|
||||
// 设置js
|
||||
jsCdn: () => {
|
||||
setJsCdn();
|
||||
},
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import { nextTick } from 'vue';
|
||||
import * as svg from '@element-plus/icons-vue';
|
||||
import iconfontJson from '@/assets/iconfont/iconfont.json'
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
|
||||
/**
|
||||
* 导出全局注册 element plus svg 图标
|
||||
* @param app vue 实例
|
||||
* @description 使用:https://element-plus.gitee.io/zh-CN/component/icon.html
|
||||
*/
|
||||
export function registElSvgIcon(app: any) {
|
||||
const icons = svg as any;
|
||||
for (const i in icons) {
|
||||
app.component(`${icons[i].name}`, icons[i]);
|
||||
}
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
}
|
||||
|
||||
// 获取阿里字体图标
|
||||
const getAlicdnIconfont = () => {
|
||||
@@ -9,7 +24,8 @@ const getAlicdnIconfont = () => {
|
||||
let sheetsList = [];
|
||||
let sheetsIconList = [];
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
if (styles[i].href && styles[i].href.indexOf('at.alicdn.com') > -1) {
|
||||
console.log(styles[i]);
|
||||
if (styles[i].href && styles[i].href.indexOf('iconfont') > -1) {
|
||||
sheetsList.push(styles[i]);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +44,16 @@ const getAlicdnIconfont = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// 获取本地阿里icons
|
||||
const getLocalAliIconfont = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
nextTick(() => {
|
||||
const prefix = iconfontJson.css_prefix_text;
|
||||
resolve(iconfontJson.glyphs.map((x: any) => prefix + x.font_class));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化获取 css 样式,获取 element plus 自带图标
|
||||
const elementPlusIconfont = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -76,9 +102,9 @@ const awesomeIconfont = () => {
|
||||
|
||||
// 定义导出方法集合
|
||||
const initIconfont = {
|
||||
// ali: () => {
|
||||
// return getAlicdnIconfont();
|
||||
// },
|
||||
ali: () => {
|
||||
return getLocalAliIconfont();
|
||||
},
|
||||
ele: () => {
|
||||
return elementPlusIconfont();
|
||||
},
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from '/@/store/index.ts';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
export default {
|
||||
name: 'auth',
|
||||
props: {
|
||||
@@ -16,10 +16,9 @@ export default {
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
// 获取 vuex 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
return store.state.userInfos.userInfos.authBtnList.some((v: any) => v === props.value);
|
||||
return useUserInfo().userInfo.authBtnList.some((v: any) => v === props.value);
|
||||
});
|
||||
return {
|
||||
getUserAuthBtnList,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from '/@/store/index.ts';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { judementSameArr } from '/@/utils/arrayOperation.ts';
|
||||
export default {
|
||||
name: 'authAll',
|
||||
@@ -17,10 +17,9 @@ export default {
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
// 获取 vuex 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
return judementSameArr(props.value, store.state.userInfos.userInfos.authBtnList);
|
||||
return judementSameArr(props.value, useUserInfo().userInfo.authBtnList);
|
||||
});
|
||||
return {
|
||||
getUserAuthBtnList,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from '/@/store/index.ts';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
export default {
|
||||
name: 'auths',
|
||||
props: {
|
||||
@@ -16,11 +16,10 @@ export default {
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = useStore();
|
||||
// 获取 vuex 中的用户权限
|
||||
const getUserAuthBtnList = computed(() => {
|
||||
let flag = false;
|
||||
store.state.userInfos.userInfos.authBtnList.map((val: any) => {
|
||||
useUserInfo().userInfo.authBtnList.map((val: any) => {
|
||||
props.value.map((v) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
|
||||
@@ -1,360 +0,0 @@
|
||||
<template>
|
||||
<div class="in-coder-panel">
|
||||
<textarea ref="textarea"></textarea>
|
||||
<el-select v-if="canChangeMode" class="code-mode-select" v-model="mode" @change="changeMode">
|
||||
<el-option v-for="mode in modes" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, nextTick, toRefs, reactive, watch, onMounted, defineComponent } from 'vue';
|
||||
// 引入全局实例
|
||||
import _CodeMirror from 'codemirror';
|
||||
|
||||
// 核心样式
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
// 引入主题后还需要在 options 中指定主题才会生效
|
||||
import 'codemirror/theme/cobalt.css';
|
||||
import 'codemirror/addon/selection/active-line.js';
|
||||
// 匹配括号
|
||||
import 'codemirror/addon/edit/matchbrackets.js';
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import 'codemirror/addon/comment/comment';
|
||||
|
||||
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
|
||||
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
|
||||
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
|
||||
import 'codemirror/mode/yaml/yaml.js';
|
||||
import 'codemirror/mode/dockerfile/dockerfile.js';
|
||||
import 'codemirror/mode/nginx/nginx.js';
|
||||
import 'codemirror/mode/javascript/javascript.js';
|
||||
import 'codemirror/mode/css/css.js';
|
||||
import 'codemirror/mode/xml/xml.js';
|
||||
import 'codemirror/mode/markdown/markdown.js';
|
||||
import 'codemirror/mode/python/python.js';
|
||||
import 'codemirror/mode/shell/shell.js';
|
||||
import 'codemirror/mode/sql/sql.js';
|
||||
import 'codemirror/mode/vue/vue.js';
|
||||
import 'codemirror/mode/textile/textile.js';
|
||||
import 'codemirror/addon/hint/show-hint.css';
|
||||
import 'codemirror/addon/hint/show-hint.js';
|
||||
|
||||
import { ElOption, ElSelect } from 'element-plus';
|
||||
|
||||
// 尝试获取全局实例
|
||||
const CodeMirror = (window as any).CodeMirror || _CodeMirror;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CodeMirror',
|
||||
components: {
|
||||
ElOption,
|
||||
ElSelect,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '500px',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto',
|
||||
},
|
||||
canChangeMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
let { modelValue, language } = toRefs(props);
|
||||
const textarea: any = ref(null);
|
||||
// 编辑器实例
|
||||
let coder = null as any;
|
||||
|
||||
const state = reactive({
|
||||
coder: null as any,
|
||||
content: '',
|
||||
// 默认的语法类型
|
||||
mode: 'x-sh',
|
||||
// 默认配置
|
||||
options: {
|
||||
// 缩进格式
|
||||
tabSize: 2,
|
||||
// 主题,对应主题库 JS 需要提前引入
|
||||
theme: 'cobalt',
|
||||
// 显示行号
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
indentWithTabs: true,
|
||||
smartIndent: true,
|
||||
matchBrackets: true,
|
||||
autofocus: true,
|
||||
styleSelectedText: true,
|
||||
styleActiveLine: true, // 高亮选中行
|
||||
foldGutter: true, // 块槽
|
||||
// extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
|
||||
hintOptions: {
|
||||
// 当匹配只有一项的时候是否自动补全
|
||||
completeSingle: false,
|
||||
},
|
||||
},
|
||||
// 支持切换的语法高亮类型,对应 JS 已经提前引入
|
||||
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
|
||||
modes: [
|
||||
{
|
||||
value: 'x-sh',
|
||||
label: 'Shell',
|
||||
},
|
||||
{
|
||||
value: 'x-yaml',
|
||||
label: 'Yaml',
|
||||
},
|
||||
{
|
||||
value: 'x-dockerfile',
|
||||
label: 'Dockerfile',
|
||||
},
|
||||
{
|
||||
value: 'x-nginx-conf',
|
||||
label: 'Nginx',
|
||||
},
|
||||
{
|
||||
value: 'html',
|
||||
label: 'XML/HTML',
|
||||
},
|
||||
{
|
||||
value: 'x-python',
|
||||
label: 'Python',
|
||||
},
|
||||
{
|
||||
value: 'x-sql',
|
||||
label: 'SQL',
|
||||
},
|
||||
{
|
||||
value: 'css',
|
||||
label: 'CSS',
|
||||
},
|
||||
{
|
||||
value: 'javascript',
|
||||
label: 'Javascript',
|
||||
},
|
||||
{
|
||||
value: 'x-java',
|
||||
label: 'Java',
|
||||
},
|
||||
{
|
||||
value: 'x-vue',
|
||||
label: 'Vue',
|
||||
},
|
||||
{
|
||||
value: 'markdown',
|
||||
label: 'Markdown',
|
||||
},
|
||||
{
|
||||
value: 'text/x-textile',
|
||||
label: 'text',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
handerCodeChange(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
// watch(
|
||||
// () => props.options,
|
||||
// (newValue, oldValue) => {
|
||||
// for (const key in newValue) {
|
||||
// coder.setOption(key, newValue[key]);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
const init = () => {
|
||||
if (props.options) {
|
||||
state.options = props.options;
|
||||
}
|
||||
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
|
||||
coder = CodeMirror.fromTextArea(textarea.value, state.options);
|
||||
coder.setValue(modelValue.value || state.content);
|
||||
|
||||
// 支持双向绑定
|
||||
coder.on('change', (coder: any) => {
|
||||
state.content = coder.getDoc().getValue();
|
||||
emit('update:modelValue', state.content);
|
||||
});
|
||||
|
||||
coder.on('inputRead', (instance: any, changeObj: any) => {
|
||||
if (/^[a-zA-Z]/.test(changeObj.text[0])) {
|
||||
instance.showHint();
|
||||
}
|
||||
});
|
||||
|
||||
coder.setSize(props.width, props.height);
|
||||
// editor.setSize('width','height');
|
||||
|
||||
// 修改编辑器的语法配置
|
||||
setMode(language.value);
|
||||
|
||||
[
|
||||
'scroll',
|
||||
'changes',
|
||||
'beforeChange',
|
||||
'cursorActivity',
|
||||
'keyHandled',
|
||||
'inputRead',
|
||||
'electricInput',
|
||||
'beforeSelectionChange',
|
||||
'viewportChange',
|
||||
'swapDoc',
|
||||
'gutterClick',
|
||||
'gutterContextMenu',
|
||||
'focus',
|
||||
'blur',
|
||||
'refresh',
|
||||
'optionChange',
|
||||
'scrollCursorIntoView',
|
||||
'update',
|
||||
].forEach((event) => {
|
||||
// 循环事件,并兼容 run-time 事件命名
|
||||
coder.on(event, (...args: any) => {
|
||||
// console.log('当有事件触发了', event, args);
|
||||
emit(event, ...args);
|
||||
const lowerCaseEvent = event.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||
if (lowerCaseEvent !== event) {
|
||||
emit(lowerCaseEvent, ...args);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
state.coder = coder;
|
||||
// 不加无法显示内容,需点击后才可显示
|
||||
refresh();
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
nextTick(() => {
|
||||
coder.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
// 设置模式
|
||||
const setMode = (val: string) => {
|
||||
if (val) {
|
||||
// 获取具体的语法类型对象
|
||||
let modeObj = getLanguage(val);
|
||||
// 判断父容器传入的语法是否被支持
|
||||
if (modeObj) {
|
||||
state.mode = modeObj.value;
|
||||
}
|
||||
}
|
||||
// 修改编辑器的语法配置
|
||||
coder.setOption('mode', `text/${state.mode}`);
|
||||
};
|
||||
|
||||
// 获取当前语法类型
|
||||
const getLanguage = (language: string) => {
|
||||
// 在支持的语法类型列表中寻找传入的语法类型
|
||||
return state.modes.find((mode: any) => {
|
||||
// 所有的值都忽略大小写,方便比较
|
||||
let currentLanguage = language.toLowerCase();
|
||||
let currentLabel = mode.label.toLowerCase();
|
||||
let currentValue = mode.value.toLowerCase();
|
||||
|
||||
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
|
||||
return currentLabel === currentLanguage || currentValue === currentLanguage;
|
||||
});
|
||||
};
|
||||
|
||||
// 更改模式
|
||||
const changeMode = (val: string) => {
|
||||
setMode(val);
|
||||
// 获取修改后的语法
|
||||
let label = (getLanguage(val) as any).label.toLowerCase();
|
||||
|
||||
// 允许父容器通过以下函数监听当前的语法值
|
||||
emit('language-change', label);
|
||||
};
|
||||
|
||||
const handerCodeChange = (newVal: string) => {
|
||||
const cm_value = coder.getValue();
|
||||
if (newVal !== cm_value) {
|
||||
const scrollInfo = coder.getScrollInfo();
|
||||
coder.setValue(newVal);
|
||||
state.content = newVal;
|
||||
coder.scrollTo(scrollInfo.left, scrollInfo.top);
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
textarea,
|
||||
changeMode,
|
||||
refresh,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.in-coder-panel {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
.CodeMirror {
|
||||
flex-grow: 1;
|
||||
z-index: 1;
|
||||
.CodeMirror-code {
|
||||
line-height: 19px;
|
||||
}
|
||||
font-family: 'JetBrainsMono';
|
||||
}
|
||||
.code-mode-select {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
max-width: 130px;
|
||||
}
|
||||
}
|
||||
.CodeMirror-hints {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
overflow: hidden;
|
||||
list-style: none;
|
||||
|
||||
margin: 0;
|
||||
padding: 2px;
|
||||
|
||||
-webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
border: 1px solid silver;
|
||||
|
||||
background: white;
|
||||
font-size: 90%;
|
||||
font-family: 'JetBrainsMono';
|
||||
|
||||
max-height: 20em;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +0,0 @@
|
||||
import _CodeMirror from 'codemirror'
|
||||
import codemirror from './codemirror.vue'
|
||||
|
||||
const CodeMirror = window.CodeMirror || _CodeMirror
|
||||
const install = (Vue, config) => {
|
||||
if (config) {
|
||||
if (config.options) {
|
||||
codemirror.props.globalOptions.default = () => config.options
|
||||
}
|
||||
if (config.events) {
|
||||
codemirror.props.globalEvents.default = () => config.events
|
||||
}
|
||||
}
|
||||
Vue.component(codemirror.name, codemirror)
|
||||
}
|
||||
|
||||
const VueCodemirror = { CodeMirror, codemirror, install }
|
||||
|
||||
export default VueCodemirror
|
||||
export { CodeMirror, codemirror, install }
|
||||
132
mayfly_go_web/src/components/contextmenu/index.vue
Normal file
132
mayfly_go_web/src/components/contextmenu/index.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<transition name="el-zoom-in-center">
|
||||
<div aria-hidden="true" class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu" role="tooltip"
|
||||
data-popper-placement="bottom" :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`" :key="Math.random()"
|
||||
v-show="state.isShow">
|
||||
<ul class="el-dropdown-menu">
|
||||
<template v-for="(v, k) in state.dropdownList">
|
||||
<li class="el-dropdown-menu__item" aria-disabled="false" tabindex="-1" :key="k" v-if="!v.affix"
|
||||
@click="onCurrentContextmenuClick(v.contextMenuClickId)">
|
||||
<SvgIcon :name="v.icon" />
|
||||
<span>{{ v.txt }}</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
dropdown: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
items: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
});
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['currentContextmenuClick']);
|
||||
|
||||
// 定义变量内容
|
||||
const state = reactive({
|
||||
isShow: false,
|
||||
dropdownList: [
|
||||
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
|
||||
],
|
||||
item: {} as any,
|
||||
arrowLeft: 10,
|
||||
});
|
||||
|
||||
// 父级传过来的坐标 x,y 值
|
||||
const dropdowns = computed(() => {
|
||||
// 117 为 `Dropdown 下拉菜单` 的宽度
|
||||
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
|
||||
return {
|
||||
x: document.documentElement.clientWidth - 117 - 5,
|
||||
y: props.dropdown.y,
|
||||
};
|
||||
} else {
|
||||
return props.dropdown;
|
||||
}
|
||||
});
|
||||
// 当前项菜单点击
|
||||
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
|
||||
emit('currentContextmenuClick', { id: contextMenuClickId, item: state.item });
|
||||
};
|
||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||
const openContextmenu = (item: any) => {
|
||||
state.item = item;
|
||||
closeContextmenu();
|
||||
setTimeout(() => {
|
||||
state.isShow = true;
|
||||
}, 10);
|
||||
};
|
||||
// 关闭右键菜单
|
||||
const closeContextmenu = () => {
|
||||
state.isShow = false;
|
||||
};
|
||||
// 监听页面监听进行右键菜单的关闭
|
||||
onMounted(() => {
|
||||
document.body.addEventListener('click', closeContextmenu);
|
||||
});
|
||||
// 页面卸载时,移除右键菜单监听事件
|
||||
onUnmounted(() => {
|
||||
document.body.removeEventListener('click', closeContextmenu);
|
||||
});
|
||||
// 监听下拉菜单位置
|
||||
watch(
|
||||
() => props.dropdown,
|
||||
({ x }) => {
|
||||
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
|
||||
else state.arrowLeft = 10;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => props.items,
|
||||
(x: any) => {
|
||||
state.dropdownList = x
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openContextmenu,
|
||||
closeContextmenu,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-contextmenu {
|
||||
transform-origin: center top;
|
||||
z-index: 2190;
|
||||
position: fixed;
|
||||
|
||||
.el-dropdown-menu__item {
|
||||
font-size: 12px !important;
|
||||
white-space: nowrap;
|
||||
|
||||
i {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,8 @@
|
||||
<template>
|
||||
<div class="icon-selector">
|
||||
<el-popover placement="bottom" :width="450" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
|
||||
<template #reference>
|
||||
<div class="icon-selector w100 h100">
|
||||
<el-input
|
||||
v-model="fontIconSearch"
|
||||
:placeholder="fontIconPlaceholder"
|
||||
v-model="state.fontIconSearch"
|
||||
:placeholder="state.fontIconPlaceholder"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
@@ -14,72 +12,49 @@
|
||||
@blur="onIconBlur"
|
||||
>
|
||||
<template #prepend>
|
||||
<SvgIcon :name="prepend" class="font14" />
|
||||
<SvgIcon
|
||||
:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
|
||||
class="font14"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
:width="state.fontIconWidth"
|
||||
transition="el-zoom-in-top"
|
||||
popper-class="icon-selector-popper"
|
||||
trigger="click"
|
||||
:virtual-ref="inputWidthRef"
|
||||
virtual-triggering
|
||||
>
|
||||
<template #default>
|
||||
<div class="icon-selector-warp">
|
||||
<div class="icon-selector-warp-title">{{ title }}</div>
|
||||
<el-tabs v-model="state.fontIconTabActive" @tab-click="onIconClick">
|
||||
<el-tab-pane lazy label="ele" name="ele">
|
||||
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane lazy label="ali" name="ali">
|
||||
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane lazy label="awe" name="awe">
|
||||
<IconList :list="fontIconSheetsFilterList" :empty="emptyDescription" :prefix="state.fontIconPrefix" @get-icon="onColClick" />
|
||||
</el-tab-pane> -->
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<transition name="el-zoom-in-top">
|
||||
<div class="icon-selector-warp" v-show="fontIconVisible">
|
||||
<div class="icon-selector-warp-title flex">
|
||||
<div class="flex-auto">{{ title }}</div>
|
||||
<div class="icon-selector-warp-title-tab" v-if="type === 'all'">
|
||||
<span :class="{ 'span-active': fontIconType === 'ali' }" @click="onIconChange('ali')" class="ml10" title="iconfont 图标"
|
||||
>ali</span
|
||||
>
|
||||
<span
|
||||
:class="{ 'span-active': fontIconType === 'ele' }"
|
||||
@click="onIconChange('ele')"
|
||||
class="ml10"
|
||||
title="elementPlus 图标"
|
||||
>ele</span
|
||||
>
|
||||
<span
|
||||
:class="{ 'span-active': fontIconType === 'awe' }"
|
||||
@click="onIconChange('awe')"
|
||||
class="ml10"
|
||||
title="fontawesome 图标"
|
||||
>awe</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon-selector-warp-row">
|
||||
<el-scrollbar ref="selectorScrollbarRef">
|
||||
<el-row :gutter="10" v-if="fontIconSheetsFilterList.length > 0">
|
||||
<el-col
|
||||
:xs="6"
|
||||
:sm="4"
|
||||
:md="4"
|
||||
:lg="4"
|
||||
:xl="4"
|
||||
@click="onColClick(v)"
|
||||
v-for="(v, k) in fontIconSheetsFilterList"
|
||||
:key="k"
|
||||
>
|
||||
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': fontIconPrefix === v }">
|
||||
<div class="flex-margin">
|
||||
<div class="icon-selector-warp-item-value">
|
||||
<SvgIcon :name="v" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty :image-size="100" v-if="fontIconSheetsFilterList.length <= 0" :description="emptyDescription"></el-empty>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch } from 'vue';
|
||||
import initIconfont from '@/common/utils/getStyleSheets';
|
||||
export default {
|
||||
name: 'iconSelector',
|
||||
emits: ['update:modelValue', 'get', 'clear'],
|
||||
props: {
|
||||
<script setup lang="ts" name="iconSelector">
|
||||
import { defineAsyncComponent, ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
|
||||
import type { TabsPaneContext } from 'element-plus';
|
||||
import initIconfont from '@/common/utils/svgIcons';
|
||||
import '@/theme/iconSelector.scss';
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
// 输入框前置内容
|
||||
prepend: {
|
||||
type: String,
|
||||
@@ -100,11 +75,6 @@ export default {
|
||||
type: String,
|
||||
default: () => '请选择图标',
|
||||
},
|
||||
// icon 图标类型
|
||||
type: {
|
||||
type: String,
|
||||
default: () => 'ele',
|
||||
},
|
||||
// 禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
@@ -120,53 +90,129 @@ export default {
|
||||
type: String,
|
||||
default: () => '无相关图标',
|
||||
},
|
||||
// 双向绑定值,字段名为固定,改了之后将不生效
|
||||
// 双向绑定值,默认为 modelValue,
|
||||
// 参考:https://v3.cn.vuejs.org/guide/migration/v-model.html#%E8%BF%81%E7%A7%BB%E7%AD%96%E7%95%A5
|
||||
// 参考:https://v3.cn.vuejs.org/guide/component-custom-events.html#%E5%A4%9A%E4%B8%AA-v-model-%E7%BB%91%E5%AE%9A
|
||||
modelValue: String,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
});
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['update:modelValue', 'get', 'clear']);
|
||||
|
||||
// 引入组件
|
||||
const IconList = defineAsyncComponent(() => import('@/components/iconSelector/list.vue'));
|
||||
|
||||
// 定义变量内容
|
||||
const inputWidthRef = ref();
|
||||
const selectorScrollbarRef = ref();
|
||||
const state: any = reactive({
|
||||
const state = reactive({
|
||||
fontIconPrefix: '',
|
||||
fontIconVisible: false,
|
||||
fontIconWidth: 0,
|
||||
fontIconSearch: '',
|
||||
fontIconTabsIndex: 0,
|
||||
fontIconSheetsList: [],
|
||||
fontIconPlaceholder: '',
|
||||
fontIconType: 'ali',
|
||||
fontIconShow: true,
|
||||
fontIconTabActive: 'ele',
|
||||
fontIconList: {
|
||||
ali: [],
|
||||
ele: [],
|
||||
awe: [],
|
||||
},
|
||||
});
|
||||
|
||||
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
|
||||
const onIconFocus = () => {
|
||||
state.fontIconVisible = true;
|
||||
if (!props.modelValue) return false;
|
||||
state.fontIconSearch = '';
|
||||
state.fontIconPlaceholder = props.modelValue;
|
||||
};
|
||||
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
|
||||
const onIconBlur = () => {
|
||||
state.fontIconVisible = false;
|
||||
const list = fontIconTabNameList();
|
||||
setTimeout(() => {
|
||||
const icon = state.fontIconSheetsList.filter((icon: string) => icon === state.fontIconSearch);
|
||||
const icon = list.filter((icon: string) => icon === state.fontIconSearch);
|
||||
if (icon.length <= 0) state.fontIconSearch = '';
|
||||
}, 300);
|
||||
};
|
||||
// 处理 icon 双向绑定数值回显
|
||||
const initModeValueEcho = () => {
|
||||
if (props.modelValue === '') return false;
|
||||
state.fontIconPlaceholder = props.modelValue;
|
||||
state.fontIconPrefix = props.modelValue;
|
||||
};
|
||||
// 图标搜索及图标数据显示
|
||||
const fontIconSheetsFilterList = computed(() => {
|
||||
if (!state.fontIconSearch) return state.fontIconSheetsList;
|
||||
const list = fontIconTabNameList();
|
||||
if (!state.fontIconSearch) return list;
|
||||
let search = state.fontIconSearch.trim().toLowerCase();
|
||||
return state.fontIconSheetsList.filter((item: any) => {
|
||||
return list.filter((item: string) => {
|
||||
if (item.toLowerCase().indexOf(search) !== -1) return item;
|
||||
});
|
||||
});
|
||||
// 根据 tab name 类型设置图标
|
||||
const fontIconTabNameList = () => {
|
||||
let iconList: any = [];
|
||||
if (state.fontIconTabActive === 'ali') iconList = state.fontIconList.ali;
|
||||
else if (state.fontIconTabActive === 'ele') iconList = state.fontIconList.ele;
|
||||
else if (state.fontIconTabActive === 'awe') iconList = state.fontIconList.awe;
|
||||
return iconList;
|
||||
};
|
||||
// 处理 icon 双向绑定数值回显
|
||||
const initModeValueEcho = () => {
|
||||
if (props.modelValue === '') return ((<string | undefined>state.fontIconPlaceholder) = props.placeholder);
|
||||
(<string | undefined>state.fontIconPlaceholder) = props.modelValue;
|
||||
(<string | undefined>state.fontIconPrefix) = props.modelValue;
|
||||
};
|
||||
// 处理 icon 类型,用于回显时,tab 高亮与初始化数据
|
||||
const initFontIconName = () => {
|
||||
let name = 'ele';
|
||||
if (props.modelValue!.indexOf('iconfont') > -1) {
|
||||
name = 'ali';
|
||||
} else {
|
||||
name = 'ele';
|
||||
}
|
||||
// else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
|
||||
// else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
|
||||
// 初始化 tab 高亮回显
|
||||
state.fontIconTabActive = name;
|
||||
return name;
|
||||
};
|
||||
// 初始化数据
|
||||
const initFontIconData = async (name: string) => {
|
||||
if (name === 'ali') {
|
||||
// 阿里字体图标使用 `iconfont xxx`
|
||||
if (state.fontIconList.ali.length > 0) return;
|
||||
const res: any = await initIconfont.ali();
|
||||
state.fontIconList.ali = res.map((i: string) => `iconfont ${i}`);
|
||||
} else if (name === 'ele') {
|
||||
// element plus 图标
|
||||
if (state.fontIconList.ele.length > 0) return;
|
||||
await initIconfont.ele().then((res: any) => {
|
||||
state.fontIconList.ele = res;
|
||||
});
|
||||
} else if (name === 'awe') {
|
||||
// fontawesome字体图标使用 `fa xxx`
|
||||
// if (state.fontIconList.awe.length > 0) return;
|
||||
// await initIconfont.awe().then((res: any) => {
|
||||
// state.fontIconList.awe = res.map((i: string) => `fa ${i}`);
|
||||
// });
|
||||
}
|
||||
// 初始化 input 的 placeholder
|
||||
// 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
|
||||
state.fontIconPlaceholder = props.placeholder;
|
||||
// 初始化双向绑定回显
|
||||
initModeValueEcho();
|
||||
};
|
||||
// 图标点击切换
|
||||
const onIconClick = (pane: TabsPaneContext) => {
|
||||
initFontIconData(pane.paneName as string);
|
||||
inputWidthRef.value.focus();
|
||||
};
|
||||
// 获取当前点击的 icon 图标
|
||||
const onColClick = (v: string) => {
|
||||
state.fontIconPlaceholder = v;
|
||||
state.fontIconPrefix = v;
|
||||
emit('get', state.fontIconPrefix);
|
||||
emit('update:modelValue', state.fontIconPrefix);
|
||||
inputWidthRef.value.focus();
|
||||
};
|
||||
// 清空当前点击的 icon 图标
|
||||
const onClearFontIcon = () => {
|
||||
state.fontIconPrefix = '';
|
||||
emit('clear', state.fontIconPrefix);
|
||||
emit('update:modelValue', state.fontIconPrefix);
|
||||
};
|
||||
// 获取 input 的宽度
|
||||
const getInputWidth = () => {
|
||||
nextTick(() => {
|
||||
@@ -179,62 +225,9 @@ export default {
|
||||
getInputWidth();
|
||||
});
|
||||
};
|
||||
// 初始化数据
|
||||
const initFontIconData = async (type: string) => {
|
||||
state.fontIconSheetsList = [];
|
||||
if (type === 'ali') {
|
||||
// await initIconfont.ali().then((res: any) => {
|
||||
// // 阿里字体图标使用 `iconfont xxx`
|
||||
// state.fontIconSheetsList = res.map((i) => `iconfont ${i}`);
|
||||
// });
|
||||
} else if (type === 'ele') {
|
||||
await initIconfont.ele().then((res: any) => {
|
||||
state.fontIconSheetsList = res;
|
||||
});
|
||||
} else if (type === 'awe') {
|
||||
// await initIconfont.awe().then((res: any) => {
|
||||
// // fontawesome字体图标使用 `fa xxx`
|
||||
// state.fontIconSheetsList = res.map((i) => `fa ${i}`);
|
||||
// });
|
||||
}
|
||||
// 初始化 input 的 placeholder
|
||||
// 参考(单项数据流):https://cn.vuejs.org/v2/guide/components-props.html?#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81
|
||||
state.fontIconPlaceholder = props.placeholder;
|
||||
// 初始化双向绑定回显
|
||||
initModeValueEcho();
|
||||
// 切换时,滚动条置顶。感兴趣可以使用 keep-alive <component :is="xxx"/> 进行缓存
|
||||
selectorScrollbarRef.value.wrap$.scrollTop = 0;
|
||||
};
|
||||
// 图标点击切换
|
||||
const onIconChange = (type: string) => {
|
||||
state.fontIconType = type;
|
||||
initFontIconData(type);
|
||||
};
|
||||
// 获取当前点击的 icon 图标
|
||||
const onColClick = (v: any) => {
|
||||
state.fontIconPlaceholder = v;
|
||||
state.fontIconVisible = false;
|
||||
state.fontIconPrefix = v;
|
||||
emit('get', state.fontIconPrefix);
|
||||
emit('update:modelValue', state.fontIconPrefix);
|
||||
};
|
||||
// 清空当前点击的 icon 图标
|
||||
const onClearFontIcon = () => {
|
||||
state.fontIconPrefix = '';
|
||||
emit('clear', state.fontIconPrefix);
|
||||
emit('update:modelValue', state.fontIconPrefix);
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
// 判断默认进来是什么类型图标,进行 tab 回显
|
||||
if (props.type === 'all') {
|
||||
// if (props.modelValue?.indexOf('iconfont') > -1) onIconChange('ali');
|
||||
// else if (props.modelValue?.indexOf('element') > -1) onIconChange('ele');
|
||||
// else if (props.modelValue?.indexOf('fa') > -1) onIconChange('awe');
|
||||
// else onIconChange('ali');
|
||||
} else {
|
||||
onIconChange(props.type);
|
||||
}
|
||||
initFontIconData(initFontIconName());
|
||||
initResize();
|
||||
getInputWidth();
|
||||
});
|
||||
@@ -243,19 +236,7 @@ export default {
|
||||
() => props.modelValue,
|
||||
() => {
|
||||
initModeValueEcho();
|
||||
initFontIconName();
|
||||
}
|
||||
);
|
||||
return {
|
||||
inputWidthRef,
|
||||
selectorScrollbarRef,
|
||||
fontIconSheetsFilterList,
|
||||
onColClick,
|
||||
onIconChange,
|
||||
onClearFontIcon,
|
||||
onIconFocus,
|
||||
onIconBlur,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
84
mayfly_go_web/src/components/iconSelector/list.vue
Normal file
84
mayfly_go_web/src/components/iconSelector/list.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="icon-selector-warp-row">
|
||||
<el-scrollbar ref="selectorScrollbarRef">
|
||||
<el-row :gutter="10" v-if="props.list.length > 0">
|
||||
<el-col :xs="6" :sm="4" :md="4" :lg="4" :xl="4" v-for="(v, k) in list" :key="k" @click="onColClick(v)">
|
||||
<div class="icon-selector-warp-item" :class="{ 'icon-selector-active': prefix === v }">
|
||||
<SvgIcon :name="v" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty :image-size="100" v-if="list.length <= 0" :description="empty"></el-empty>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="iconSelectorList">
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
// 图标列表数据
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
// 自定义空状态描述文字
|
||||
empty: {
|
||||
type: String,
|
||||
default: () => '无相关图标',
|
||||
},
|
||||
// 高亮当前选中图标
|
||||
prefix: {
|
||||
type: String,
|
||||
default: () => '',
|
||||
},
|
||||
});
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['get-icon']);
|
||||
|
||||
// 当前 icon 图标点击时
|
||||
const onColClick = (v: unknown | string) => {
|
||||
emit('get-icon', v);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon-selector-warp-row {
|
||||
height: 230px;
|
||||
overflow: hidden;
|
||||
.el-row {
|
||||
padding: 15px;
|
||||
}
|
||||
.el-scrollbar__bar.is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
.icon-selector-warp-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
height: 30px;
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary-light-5);
|
||||
i {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-selector-active {
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
border: 1px solid var(--el-color-primary-light-5);
|
||||
i {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,133 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div ref="jsoneditorVue" :style="{ height: height, width: width }"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, toRefs, reactive, nextTick, watch, onMounted, onUnmounted, defineComponent } from 'vue';
|
||||
import JSONEditor from 'jsoneditor';
|
||||
import 'jsoneditor/dist/jsoneditor.min.css';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'JsonEdit',
|
||||
components: {},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Object],
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '500px',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto',
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
currentMode: {
|
||||
type: String,
|
||||
default: 'tree',
|
||||
},
|
||||
modeList: {
|
||||
type: Array,
|
||||
default() {
|
||||
return ['tree', 'code', 'form', 'text', 'view'];
|
||||
},
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
let { modelValue, options, modeList, currentMode } = toRefs(props);
|
||||
|
||||
const jsoneditorVue = ref(null)
|
||||
// 编辑器实例
|
||||
let editor = null as any;
|
||||
// 值类型
|
||||
let valueType = 'string';
|
||||
// 是否内部改变(即onChange事件双向绑定),内部改变则不需要重新赋值给editor
|
||||
let internalChange = false;
|
||||
|
||||
const state = reactive({
|
||||
height: '500px',
|
||||
width: 'auto',
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
state.width = props.width;
|
||||
state.height = props.height;
|
||||
|
||||
init();
|
||||
setJson(modelValue.value);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
editor?.destroy();
|
||||
editor = null;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
if (!editor) {
|
||||
init();
|
||||
}
|
||||
setJson(newValue);
|
||||
}
|
||||
);
|
||||
|
||||
const setJson = (value: any) => {
|
||||
if (internalChange) {
|
||||
return;
|
||||
}
|
||||
if (typeof value == 'string') {
|
||||
valueType = 'string';
|
||||
editor.set(JSON.parse(value));
|
||||
} else {
|
||||
valueType = 'object';
|
||||
editor.set(value);
|
||||
}
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
try {
|
||||
const json = editor.get();
|
||||
if (valueType == 'string') {
|
||||
emit('update:modelValue', JSON.stringify(json));
|
||||
} else {
|
||||
emit('update:modelValue', json);
|
||||
}
|
||||
emit('onChange', json);
|
||||
internalChange = true;
|
||||
nextTick(() => {
|
||||
internalChange = false;
|
||||
});
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
console.log('init json editor');
|
||||
const finalOptions = {
|
||||
...options.value,
|
||||
mode: currentMode.value,
|
||||
modes: modeList.value,
|
||||
onChange,
|
||||
};
|
||||
editor = new JSONEditor(jsoneditorVue.value, finalOptions);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
jsoneditorVue,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
div.jsoneditor-menu a.jsoneditor-poweredBy {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
280
mayfly_go_web/src/components/monaco/MonacoEditor.vue
Normal file
280
mayfly_go_web/src/components/monaco/MonacoEditor.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="monaco-editor" style="border: 1px solid #ccc;">
|
||||
<div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
|
||||
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
|
||||
<el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, toRefs, reactive, onMounted, onBeforeUnmount } from 'vue';
|
||||
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { editor, languages } from 'monaco-editor';
|
||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
||||
// 主题仓库 https://github.com/brijeshb42/monaco-themes
|
||||
// 主题例子 https://editor.bitwiser.in/
|
||||
// import Monokai from 'monaco-themes/themes/Monokai.json'
|
||||
// import Active4D from 'monaco-themes/themes/Active4D.json'
|
||||
// import ahe from 'monaco-themes/themes/All Hallows Eve.json'
|
||||
// import bop from 'monaco-themes/themes/Birds of Paradise.json'
|
||||
// import krTheme from 'monaco-themes/themes/krTheme.json'
|
||||
// import Dracula from 'monaco-themes/themes/Dracula.json'
|
||||
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json'
|
||||
import { language as shellLan } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
|
||||
import { ElOption, ElSelect } from 'element-plus';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: String,
|
||||
},
|
||||
language: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '500px',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: 'auto',
|
||||
},
|
||||
canChangeMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const languageArr = [
|
||||
{
|
||||
value: 'shell',
|
||||
label: 'Shell',
|
||||
},
|
||||
{
|
||||
value: 'json',
|
||||
label: 'JSON',
|
||||
},
|
||||
{
|
||||
value: 'yaml',
|
||||
label: 'Yaml',
|
||||
},
|
||||
{
|
||||
value: 'dockerfile',
|
||||
label: 'Dockerfile',
|
||||
},
|
||||
{
|
||||
value: 'html',
|
||||
label: 'XML/HTML',
|
||||
},
|
||||
{
|
||||
value: 'python',
|
||||
label: 'Python',
|
||||
},
|
||||
{
|
||||
value: 'sql',
|
||||
label: 'SQL',
|
||||
},
|
||||
{
|
||||
value: 'css',
|
||||
label: 'CSS',
|
||||
},
|
||||
{
|
||||
value: 'javascript',
|
||||
label: 'Javascript',
|
||||
},
|
||||
{
|
||||
value: 'java',
|
||||
label: 'Java',
|
||||
},
|
||||
{
|
||||
value: 'markdown',
|
||||
label: 'Markdown',
|
||||
},
|
||||
{
|
||||
value: 'text',
|
||||
label: 'text',
|
||||
},
|
||||
];
|
||||
|
||||
const options = {
|
||||
language: 'shell',
|
||||
theme: 'SolarizedLight',
|
||||
automaticLayout: true, //自适应宽高布局
|
||||
foldingStrategy: 'indentation',//代码可分小段折叠
|
||||
roundedSelection: false, // 禁用选择文本背景的圆角
|
||||
matchBrackets: 'near',
|
||||
linkedEditing: true,
|
||||
cursorBlinking: 'smooth',// 光标闪烁样式
|
||||
mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
|
||||
overviewRulerBorder: false, // 不要滚动条的边框
|
||||
tabSize: 4, // tab 缩进长度
|
||||
// fontFamily: 'JetBrainsMono', // 字体 暂时不要设置,否则光标容易错位
|
||||
fontWeight: 'bold',
|
||||
// fontSize: 12,
|
||||
// letterSpacing: 1, 字符间距
|
||||
// quickSuggestions:false, // 禁用代码提示
|
||||
minimap: {
|
||||
enabled: false, // 不要小地图
|
||||
},
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
languageMode: 'shell',
|
||||
})
|
||||
|
||||
const {
|
||||
languageMode,
|
||||
} = toRefs(state)
|
||||
|
||||
onMounted(() => {
|
||||
state.languageMode = props.language;
|
||||
initMonacoEditorIns();
|
||||
setEditorValue(props.modelValue);
|
||||
registerCompletionItemProvider();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (monacoEditorIns) {
|
||||
monacoEditorIns.dispose();
|
||||
}
|
||||
if (completionItemProvider) {
|
||||
completionItemProvider.dispose();
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (newValue: any) => {
|
||||
if (!monacoEditorIns.hasTextFocus()) {
|
||||
state.languageMode = props.language;
|
||||
monacoEditorIns?.setValue(newValue);
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.language, (newValue: any) => {
|
||||
changeLanguage(newValue);
|
||||
})
|
||||
|
||||
|
||||
const monacoTextarea: any = ref(null);
|
||||
|
||||
let monacoEditorIns: editor.IStandaloneCodeEditor = null as any;
|
||||
let completionItemProvider: any = null;
|
||||
|
||||
self.MonacoEnvironment = {
|
||||
getWorker(_: any, label: string) {
|
||||
if (label === 'json') {
|
||||
return new JsonWorker()
|
||||
}
|
||||
return new EditorWorker();
|
||||
}
|
||||
};
|
||||
|
||||
const initMonacoEditorIns = () => {
|
||||
console.log('初始化monaco编辑器')
|
||||
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
|
||||
// 初始化一些主题
|
||||
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
|
||||
options.language = state.languageMode;
|
||||
// 从localStorage中获取,通过store可能存在父子组件都使用store报错
|
||||
options.theme = JSON.parse(localStorage.getItem('themeConfig') as string).editorTheme || 'vs';
|
||||
monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(options, props.options as any));
|
||||
|
||||
// 监听内容改变,双向绑定
|
||||
monacoEditorIns.onDidChangeModelContent(() => {
|
||||
emit('update:modelValue', monacoEditorIns.getModel()?.getValue());
|
||||
})
|
||||
|
||||
// 动态设置主题
|
||||
// monaco.editor.setTheme('hc-black');
|
||||
};
|
||||
|
||||
const changeLanguage = (value: any) => {
|
||||
console.log('change lan');
|
||||
// 获取当前的文档模型
|
||||
let oldModel = monacoEditorIns.getModel()
|
||||
if (!oldModel) {
|
||||
return;
|
||||
}
|
||||
// 创建一个新的文档模型
|
||||
let newModel = monaco.editor.createModel(oldModel.getValue(), value)
|
||||
// 设置成新的
|
||||
monacoEditorIns.setModel(newModel)
|
||||
// 销毁旧的模型
|
||||
if (oldModel) {
|
||||
oldModel.dispose()
|
||||
}
|
||||
|
||||
registerCompletionItemProvider();
|
||||
}
|
||||
|
||||
const setEditorValue = (value: any) => {
|
||||
monacoEditorIns.getModel()?.setValue(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册联想补全提示
|
||||
*/
|
||||
const registerCompletionItemProvider = () => {
|
||||
if (completionItemProvider) {
|
||||
completionItemProvider.dispose();
|
||||
}
|
||||
if (state.languageMode == 'shell') {
|
||||
registeShell()
|
||||
}
|
||||
}
|
||||
|
||||
const registeShell = () => {
|
||||
completionItemProvider = monaco.languages.registerCompletionItemProvider('shell', {
|
||||
provideCompletionItems: async () => {
|
||||
let suggestions: languages.CompletionItem[] = []
|
||||
shellLan.keywords.forEach((item: any) => {
|
||||
suggestions.push({
|
||||
label: item,
|
||||
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||
insertText: item,
|
||||
} as any);
|
||||
})
|
||||
shellLan.builtins.forEach((item: any) => {
|
||||
suggestions.push({
|
||||
label: item,
|
||||
kind: monaco.languages.CompletionItemKind.Property,
|
||||
insertText: item,
|
||||
} as any);
|
||||
})
|
||||
return {
|
||||
suggestions: suggestions
|
||||
};
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
const format = () => {
|
||||
/*
|
||||
触发自动格式化;
|
||||
*/
|
||||
monacoEditorIns.trigger('', 'editor.action.formatDocument', '')
|
||||
}
|
||||
|
||||
defineExpose({ format })
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.monaco-editor {
|
||||
.code-mode-select {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
max-width: 130px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,24 @@
|
||||
<script lang="ts">
|
||||
// 渲染函数:https://v3.cn.vuejs.org/guide/render-function.html
|
||||
import { h, resolveComponent, defineComponent } from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'svgIcon',
|
||||
props: {
|
||||
<template>
|
||||
<i v-if="isShowIconSvg" class="el-icon icon-middle" :style="setIconSvgStyle">
|
||||
<component :is="getIconName" :style="setIconSvgStyle" />
|
||||
</i>
|
||||
|
||||
<svg v-else-if="isIconfont()" class="el-icon iconfont-icon icon-middle" aria-hidden="true" :style="setIconSvgStyle">
|
||||
<use :xlink:href="'#' + getIconfontName()"></use>
|
||||
</svg>
|
||||
|
||||
<div v-else-if="isShowIconImg" :style="setIconImgOutStyle">
|
||||
<img :src="getIconName" :style="setIconSvgInsStyle" />
|
||||
</div>
|
||||
|
||||
<i v-else :class="getIconName" :style="setIconSvgStyle" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="svgIcon">
|
||||
import { computed } from 'vue';
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
// svg 图标组件名字
|
||||
name: {
|
||||
type: String,
|
||||
@@ -11,14 +26,80 @@ export default defineComponent({
|
||||
// svg 大小
|
||||
size: {
|
||||
type: Number,
|
||||
default: () => 14,
|
||||
},
|
||||
// svg 颜色
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any) {
|
||||
return () => h('i', { class: 'el-icon', style: `--font-size: ${props.size};--color: ${props.color}` }, [h(resolveComponent(`${props.name}`))]);
|
||||
},
|
||||
isEle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
});
|
||||
|
||||
// 在线链接、本地引入地址前缀
|
||||
const linesString = ['https', 'http', '/src', '/assets', 'data:image', import.meta.env.VITE_PUBLIC_PATH];
|
||||
|
||||
// 获取 icon 图标名称
|
||||
const getIconName = computed(() => {
|
||||
return props?.name as any;
|
||||
});
|
||||
|
||||
// 用于判断 element plus 自带 svg 图标的显示、隐藏。不存在 空格分隔的icon name即为element plus自带icon
|
||||
const isShowIconSvg = computed(() => {
|
||||
const ss = props?.name?.split(" ")
|
||||
if (!ss) {
|
||||
return true;
|
||||
}
|
||||
return ss.length == 1;
|
||||
});
|
||||
|
||||
const isIconfont = () => {
|
||||
return props?.name?.startsWith("iconfont")
|
||||
}
|
||||
|
||||
const getIconfontName = () => {
|
||||
// iconfont icon-xxxx 获取icon-xxx即可
|
||||
return props?.name?.split(" ")[1]
|
||||
}
|
||||
|
||||
// 用于判断在线链接、本地引入等图标显示、隐藏
|
||||
const isShowIconImg = computed(() => {
|
||||
return linesString.find((str) => props.name?.startsWith(str));
|
||||
});
|
||||
|
||||
// 设置图标样式
|
||||
const setIconSvgStyle = computed(() => {
|
||||
return `font-size: ${props.size}px;color: ${props.color};width: ${props.size}px;height: ${props.size}px;`;
|
||||
});
|
||||
|
||||
// 设置图片样式
|
||||
const setIconImgOutStyle = computed(() => {
|
||||
return `width: ${props.size}px;height: ${props.size}px;display: inline-block;overflow: hidden;`;
|
||||
});
|
||||
|
||||
// 设置图片样式
|
||||
const setIconSvgInsStyle = computed(() => {
|
||||
const filterStyle: string[] = [];
|
||||
const compatibles: string[] = ['-webkit', '-ms', '-o', '-moz'];
|
||||
compatibles.forEach((j) => filterStyle.push(`${j}-filter: drop-shadow(${props.color} 30px 0);`));
|
||||
return `width: ${props.size}px;height: ${props.size}px;position: relative;left: -${props.size}px;${filterStyle.join('')}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style type="text/css">
|
||||
.iconfont-icon {
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon-middle {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { App } from 'vue';
|
||||
import { auth, auths, authAll } from './authFunction'
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { judementSameArr } from '@/common/utils/arrayOperation';
|
||||
|
||||
// 用户权限指令
|
||||
export function authDirective(app: App) {
|
||||
// 单个权限验证(v-auth="xxx")
|
||||
app.directive('auth', {
|
||||
mounted(el, binding) {
|
||||
if (!auth(binding.value)) {
|
||||
if (!useUserInfo().userInfo.permissions.some((v: any) => v === binding.value)) {
|
||||
parseNoAuth(el, binding);
|
||||
};
|
||||
},
|
||||
@@ -14,7 +15,14 @@ export function authDirective(app: App) {
|
||||
// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
|
||||
app.directive('auths', {
|
||||
mounted(el, binding) {
|
||||
if (!auths(binding.value)) {
|
||||
const value = binding.value
|
||||
let flag = false;
|
||||
useUserInfo().userInfo.permissions.map((val: any) => {
|
||||
value.map((v: any) => {
|
||||
if (val === v) flag = true;
|
||||
});
|
||||
});
|
||||
if (!flag) {
|
||||
parseNoAuth(el, binding);
|
||||
}
|
||||
},
|
||||
@@ -22,7 +30,7 @@ export function authDirective(app: App) {
|
||||
// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
|
||||
app.directive('auth-all', {
|
||||
mounted(el, binding) {
|
||||
if (!authAll(binding.value)) {
|
||||
if (!judementSameArr(binding.value, useUserInfo().userInfo.permissions)) {
|
||||
parseNoAuth(el, binding);
|
||||
};
|
||||
},
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { App } from 'vue';
|
||||
import { authDirective } from '@/common/utils/authDirective.ts';
|
||||
import { wavesDirective } from '@/common/utils/customDirective.ts';
|
||||
import { authDirective } from './auth';
|
||||
import { wavesDirective } from './waves';
|
||||
|
||||
// 导出指令方法
|
||||
export function directive(app: App) {
|
||||
@@ -1,56 +1,33 @@
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import App from '@/App.vue';
|
||||
|
||||
import router from './router';
|
||||
import { store, key } from './store';
|
||||
import { directive } from '@/common/utils/directive.ts';
|
||||
import { globalComponentSize } from '@/common/utils/componentSize.ts';
|
||||
import { dateStrFormat } from '@/common/utils/date.ts'
|
||||
import pinia from '@/store/index';
|
||||
import { directive } from '@/directive/index';
|
||||
import { globalComponentSize } from '@/common/utils/componentSize';
|
||||
import { registElSvgIcon } from '@/common/utils/svgIcons';
|
||||
|
||||
import ElementPlus from 'element-plus';
|
||||
import 'element-plus/dist/index.css';
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import '@/theme/index.scss';
|
||||
import mitt from 'mitt';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
import * as svg from '@element-plus/icons-vue';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
import '@/theme/index.scss';
|
||||
import '@/assets/font/font.css'
|
||||
import '@/assets/iconfont/iconfont.js'
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
/**
|
||||
* 导出全局注册 element plus svg 图标
|
||||
* @param app vue 实例
|
||||
* @description 使用:https://element-plus.gitee.io/zh-CN/component/icon.html
|
||||
*/
|
||||
function elSvg(app: any) {
|
||||
const icons = svg as any;
|
||||
for (const i in icons) {
|
||||
app.component(`${icons[i].name}`, icons[i]);
|
||||
}
|
||||
app.component('SvgIcon', SvgIcon);
|
||||
}
|
||||
|
||||
elSvg(app);
|
||||
registElSvgIcon(app);
|
||||
directive(app);
|
||||
|
||||
app.use(router)
|
||||
.use(store, key)
|
||||
app.use(pinia)
|
||||
.use(router)
|
||||
.use(ElementPlus, { size: globalComponentSize, locale: zhCn })
|
||||
.mount('#app');
|
||||
|
||||
|
||||
// 自定义全局过滤器
|
||||
app.config.globalProperties.$filters = {
|
||||
dateFormat(value: any) {
|
||||
if (!value) {
|
||||
return ""
|
||||
}
|
||||
return dateStrFormat('yyyy-MM-dd HH:mm:ss', value)
|
||||
}
|
||||
}
|
||||
|
||||
// 屏蔽警告信息
|
||||
app.config.warnHandler = () => null;
|
||||
// 全局error处理
|
||||
app.config.errorHandler = function (err: any, vm, info) {
|
||||
// 如果是断言错误,则进行提示即可
|
||||
@@ -60,5 +37,3 @@ app.config.errorHandler = function (err: any, vm, info) {
|
||||
console.error(err, info)
|
||||
}
|
||||
}
|
||||
|
||||
app.config.globalProperties.mittBus = mitt();
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import RouterParent from '@/views/layout/routerView/parent.vue';
|
||||
|
||||
export const imports = {
|
||||
'RouterParent': RouterParent,
|
||||
|
||||
"Home": () => import('@/views/home/index.vue'),
|
||||
'Personal': () => import('@/views/personal/index.vue'),
|
||||
// machine
|
||||
"MachineList": () => import('@/views/ops/machine'),
|
||||
// sys
|
||||
"ResourceList": () => import('@/views/system/resource'),
|
||||
"RoleList": () => import('@/views/system/role'),
|
||||
"AccountList": () => import('@/views/system/account'),
|
||||
"SyslogList": () => import('@/views/system/syslog/SyslogList.vue'),
|
||||
"ConfigList": () => import('@/views/system/config/ConfigList.vue'),
|
||||
|
||||
// tag
|
||||
"TagTreeList": () => import('@/views/ops/tag/TagTreeList.vue'),
|
||||
"TeamList": () => import('@/views/ops/tag/TeamList.vue'),
|
||||
// db
|
||||
"DbList": () => import('@/views/ops/db/DbList.vue'),
|
||||
"SqlExec": () => import('@/views/ops/db'),
|
||||
// redis
|
||||
"RedisList": () => import('@/views/ops/redis'),
|
||||
"DataOperation": () => import('@/views/ops/redis/DataOperation.vue'),
|
||||
// mongo
|
||||
"MongoDataOp": () => import('@/views/ops/mongo/MongoDataOp.vue'),
|
||||
// redis
|
||||
"MongoList": () => import('@/views/ops/mongo/MongoList.vue'),
|
||||
}
|
||||
@@ -1,14 +1,25 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||
import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { store } from '@/store/index.ts';
|
||||
import { getSession, clearSession } from '@/common/utils/storage.ts';
|
||||
import { templateResolve } from '@/common/utils/string.ts'
|
||||
import { NextLoading } from '@/common/utils/loading.ts';
|
||||
import { dynamicRoutes, staticRoutes, pathMatch } from './route.ts'
|
||||
import { imports } from './imports';
|
||||
import { getSession, clearSession } from '@/common/utils/storage';
|
||||
import { templateResolve } from '@/common/utils/string'
|
||||
import { NextLoading } from '@/common/utils/loading';
|
||||
import { dynamicRoutes, staticRoutes, pathMatch } from './route'
|
||||
import openApi from '@/common/openApi';
|
||||
import sockets from '@/common/sockets';
|
||||
import pinia from '@/store/index';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||
|
||||
/**
|
||||
* 获取目录下的 .vue、.tsx 全部文件
|
||||
* @method import.meta.glob
|
||||
* @link 参考:https://cn.vitejs.dev/guide/features.html#json
|
||||
*/
|
||||
const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}');
|
||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...viewsModules });
|
||||
|
||||
// 添加静态路由
|
||||
const router = createRouter({
|
||||
@@ -24,7 +35,7 @@ export function initAllFun() {
|
||||
// 无 token 停止执行下一步
|
||||
return false
|
||||
}
|
||||
store.dispatch('userInfos/setUserInfos'); // 触发初始化用户信息
|
||||
useUserInfo().setUserInfo({});
|
||||
router.addRoute(pathMatch); // 添加404界面
|
||||
resetRoute(); // 删除/重置路由
|
||||
// 添加动态路由
|
||||
@@ -32,23 +43,20 @@ export function initAllFun() {
|
||||
router.addRoute((route as unknown) as RouteRecordRaw);
|
||||
});
|
||||
// 过滤权限菜单
|
||||
store.dispatch('routesList/setRoutesList', setFilterMenuFun(dynamicRoutes[0].children, store.state.userInfos.userInfos.menus));
|
||||
useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus))
|
||||
}
|
||||
|
||||
// 后端控制路由:模拟执行路由数据初始化
|
||||
export function initBackEndControlRoutesFun() {
|
||||
// 后端控制路由:执行路由数据初始化
|
||||
export async function initBackEndControlRoutesFun() {
|
||||
NextLoading.start(); // 界面 loading 动画开始执行
|
||||
const token = getSession('token'); // 获取浏览器缓存 token 值
|
||||
if (!token) {
|
||||
// 无 token 停止执行下一步
|
||||
return false
|
||||
}
|
||||
store.dispatch('userInfos/setUserInfos'); // 触发初始化用户信息
|
||||
let menuRoute = getSession('menus')
|
||||
if (!menuRoute) {
|
||||
menuRoute = getBackEndControlRoutes(); // 获取路由
|
||||
// const oldRoutes = res; // 获取接口原始路由(未处理component)
|
||||
}
|
||||
useUserInfo().setUserInfo({});
|
||||
// 获取路由
|
||||
let menuRoute = await getBackEndControlRoutes();
|
||||
dynamicRoutes[0].children = backEndRouterConverter(menuRoute); // 处理路由(component)
|
||||
// 添加404界面
|
||||
router.addRoute(pathMatch);
|
||||
@@ -57,12 +65,20 @@ export function initBackEndControlRoutesFun() {
|
||||
formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
|
||||
router.addRoute((route as unknown) as RouteRecordRaw);
|
||||
});
|
||||
store.dispatch('routesList/setRoutesList', dynamicRoutes[0].children);
|
||||
useRoutesList().setRoutesList(dynamicRoutes[0].children)
|
||||
}
|
||||
|
||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
export function getBackEndControlRoutes() {
|
||||
return openApi.getMenuRoute({});
|
||||
export async function getBackEndControlRoutes() {
|
||||
try {
|
||||
const menuAndPermission = await openApi.getPermissions.request();
|
||||
// 赋值权限码,用于控制按钮等
|
||||
useUserInfo().userInfo.permissions = menuAndPermission.permissions;
|
||||
return menuAndPermission.menus;
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// 后端控制路由,后端返回路由 转换为vue route
|
||||
@@ -76,7 +92,7 @@ export function backEndRouterConverter(routes: any, parentPath: string = "/") {
|
||||
item.meta = JSON.parse(item.meta)
|
||||
// 将meta.comoponet 解析为route.component
|
||||
if (item.meta.component) {
|
||||
item.component = imports[item.meta.component as string]
|
||||
item.component = dynamicImport(dynamicViewsModules, item.meta.component)
|
||||
delete item.meta['component']
|
||||
}
|
||||
// route.path == resource.code
|
||||
@@ -106,6 +122,27 @@ export function backEndRouterConverter(routes: any, parentPath: string = "/") {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 后端路由 component 转换函数
|
||||
* @param dynamicViewsModules 获取目录下的 .vue、.tsx 全部文件
|
||||
* @param component 当前要处理项 component
|
||||
* @returns 返回处理成函数后的 component
|
||||
*/
|
||||
export function dynamicImport(dynamicViewsModules: Record<string, Function>, component: string) {
|
||||
const keys = Object.keys(dynamicViewsModules);
|
||||
const matchKeys = keys.filter((key) => {
|
||||
const k = key.replace(/..\/views|../, '');
|
||||
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
|
||||
});
|
||||
if (matchKeys?.length === 1) {
|
||||
const matchKey = matchKeys[0];
|
||||
return dynamicViewsModules[matchKey];
|
||||
}
|
||||
if (matchKeys?.length > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 多级嵌套数组处理成一维数组
|
||||
export function formatFlatteningRoutes(arr: any) {
|
||||
if (arr.length <= 0) return false;
|
||||
@@ -134,7 +171,7 @@ export function formatTwoStageRoutes(arr: any) {
|
||||
}
|
||||
}
|
||||
});
|
||||
store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
|
||||
useKeepALiveNames().setCacheKeepAlive(cacheList);
|
||||
return newArr;
|
||||
}
|
||||
|
||||
@@ -167,7 +204,7 @@ export function setFilterRoute(chil: any) {
|
||||
chil.forEach((route: any) => {
|
||||
// 如果路由需要拥有指定code才可访问,则校验该用户菜单是否存在该code
|
||||
if (route.meta.code) {
|
||||
store.state.userInfos.userInfos.menus.forEach((m: any) => {
|
||||
useUserInfo().userInfo.menus.forEach((m: any) => {
|
||||
if (route.meta.code == m) {
|
||||
filterRoute.push({ ...route })
|
||||
}
|
||||
@@ -188,33 +225,35 @@ export function setFilterRouteEnd() {
|
||||
|
||||
// 删除/重置路由
|
||||
export function resetRoute() {
|
||||
store.state.routesList.routesList.forEach((route: any) => {
|
||||
useRoutesList().routesList.forEach((route: any) => {
|
||||
const { name } = route;
|
||||
router.hasRoute(name) && router.removeRoute(name);
|
||||
});
|
||||
}
|
||||
|
||||
export async function initRouter() {
|
||||
// 初始化方法执行
|
||||
const { isRequestRoutes } = store.state.themeConfig.themeConfig;
|
||||
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
|
||||
if (!isRequestRoutes) {
|
||||
// 未开启后端控制路由
|
||||
initAllFun();
|
||||
} else if (isRequestRoutes) {
|
||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
initBackEndControlRoutesFun();
|
||||
await initBackEndControlRoutesFun();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let SysWs: any;
|
||||
let loadRouter = false;
|
||||
|
||||
// 路由加载前
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.configure({ showSpinner: false });
|
||||
if (to.meta.title) NProgress.start();
|
||||
|
||||
// 如果有标题参数,则再原标题后加上参数来区别
|
||||
if (to.meta.titleRename) {
|
||||
to.meta.title = templateResolve(to.meta.title, to.query)
|
||||
to.meta.title = templateResolve(to.meta.title as string, to.query)
|
||||
}
|
||||
|
||||
const token = getSession('token');
|
||||
@@ -245,7 +284,12 @@ router.beforeEach((to, from, next) => {
|
||||
if (!SysWs && to.path != '/machine/terminal') {
|
||||
SysWs = sockets.sysMsgSocket();
|
||||
}
|
||||
if (store.state.routesList.routesList.length > 0) {
|
||||
// 不存在路由(避免刷新页面找不到路由)并且未加载过(避免token过期,导致获取权限接口报权限不足,无限获取),则重新初始化路由
|
||||
if (useRoutesList().routesList.length == 0 && !loadRouter) {
|
||||
await initRouter();
|
||||
loadRouter = true;
|
||||
next({ path: to.path, query: to.query });
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import Layout from '@/views/layout/index.vue'
|
||||
// import RouterParent from '@/views/layout/routerView/parent.vue';
|
||||
|
||||
// 定义动态路由
|
||||
export const dynamicRoutes = [
|
||||
@@ -12,6 +11,7 @@ export const dynamicRoutes = [
|
||||
meta: {
|
||||
isKeepAlive: true,
|
||||
},
|
||||
children: []
|
||||
// children: [
|
||||
// {
|
||||
// path: '/home',
|
||||
@@ -124,7 +124,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
title: '登陆',
|
||||
title: '登录',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
import { InjectionKey } from 'vue';
|
||||
import { createStore, useStore as baseUseStore, Store } from 'vuex';
|
||||
import { RootStateTypes } from '@/store/interface/index';
|
||||
import themeConfig from '@/store/modules/themeConfig.ts';
|
||||
import routesList from '@/store/modules/routesList.ts';
|
||||
import keepAliveNames from '@/store/modules/keepAliveNames.ts';
|
||||
import userInfos from '@/store/modules/userInfos.ts';
|
||||
// https://pinia.vuejs.org/
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
export const key: InjectionKey<Store<RootStateTypes>> = Symbol();
|
||||
// 创建
|
||||
const pinia = createPinia();
|
||||
|
||||
export const store = createStore<RootStateTypes>({
|
||||
modules: {
|
||||
themeConfig,
|
||||
routesList,
|
||||
keepAliveNames,
|
||||
userInfos,
|
||||
},
|
||||
});
|
||||
|
||||
export function useStore() {
|
||||
return baseUseStore(key);
|
||||
}
|
||||
// 导出
|
||||
export default pinia;
|
||||
|
||||
35
mayfly_go_web/src/store/keepAliveNames.ts
Normal file
35
mayfly_go_web/src/store/keepAliveNames.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
/**
|
||||
* 路由缓存列表
|
||||
* @methods setCacheKeepAlive 设置要缓存的路由 names(开启 Tagsview)
|
||||
* @methods addCachedView 添加要缓存的路由 names(关闭 Tagsview)
|
||||
* @methods delCachedView 删除要缓存的路由 names(关闭 Tagsview)
|
||||
* @methods delOthersCachedViews 右键菜单`关闭其它`,删除要缓存的路由 names(关闭 Tagsview)
|
||||
* @methods delAllCachedViews 右键菜单`全部关闭`,删除要缓存的路由 names(关闭 Tagsview)
|
||||
*/
|
||||
export const useKeepALiveNames = defineStore('keepALiveNames', {
|
||||
state: (): KeepAliveNamesState => ({
|
||||
keepAliveNames: [],
|
||||
cachedViews: [],
|
||||
}),
|
||||
actions: {
|
||||
async setCacheKeepAlive(data: Array<string>) {
|
||||
this.keepAliveNames = data;
|
||||
},
|
||||
async addCachedView(view: any) {
|
||||
if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
|
||||
},
|
||||
async delCachedView(view: any) {
|
||||
const index = this.cachedViews.indexOf(view.name);
|
||||
index > -1 && this.cachedViews.splice(index, 1);
|
||||
},
|
||||
async delOthersCachedViews(view: any) {
|
||||
if (view.meta.isKeepAlive) this.cachedViews = [view.name];
|
||||
else this.cachedViews = [];
|
||||
},
|
||||
async delAllCachedViews() {
|
||||
this.cachedViews = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Module } from 'vuex';
|
||||
// 此处加上 `.ts` 后缀报错,具体原因不详
|
||||
import { KeepAliveNamesState, RootStateTypes } from '@/store/interface/index';
|
||||
|
||||
const keepAliveNamesModule: Module<KeepAliveNamesState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
keepAliveNames: [],
|
||||
},
|
||||
mutations: {
|
||||
// 设置路由缓存(name字段)
|
||||
getCacheKeepAlive(state: any, data: Array<string>) {
|
||||
state.keepAliveNames = data;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 设置路由缓存(name字段)
|
||||
async setCacheKeepAlive({ commit }, data: Array<string>) {
|
||||
commit('getCacheKeepAlive', data);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default keepAliveNamesModule;
|
||||
@@ -1,24 +0,0 @@
|
||||
import { Module } from 'vuex';
|
||||
// 此处加上 `.ts` 后缀报错,具体原因不详
|
||||
import { RoutesListState, RootStateTypes } from '@/store/interface/index';
|
||||
|
||||
const routesListModule: Module<RoutesListState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
routesList: [],
|
||||
},
|
||||
mutations: {
|
||||
// 设置路由,菜单中使用到
|
||||
getRoutesList(state: any, data: Array<object>) {
|
||||
state.routesList = data;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 设置路由,菜单中使用到
|
||||
async setRoutesList({ commit }, data: any) {
|
||||
commit('getRoutesList', data);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default routesListModule;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Module } from 'vuex';
|
||||
import { getSession } from '@/common/utils/storage.ts';
|
||||
// 此处加上 `.ts` 后缀报错,具体原因不详
|
||||
import { UserInfosState, RootStateTypes } from '@/store/interface/index';
|
||||
|
||||
const userInfosModule: Module<UserInfosState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
userInfos: {},
|
||||
},
|
||||
mutations: {
|
||||
// 设置用户信息
|
||||
getUserInfos(state: any, data: object) {
|
||||
state.userInfos = data;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// 设置用户信息
|
||||
async setUserInfos({ commit }, data: object) {
|
||||
if (data) {
|
||||
commit('getUserInfos', data);
|
||||
} else {
|
||||
if (getSession('userInfo')) commit('getUserInfos', getSession('userInfo'));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default userInfosModule;
|
||||
18
mayfly_go_web/src/store/routesList.ts
Normal file
18
mayfly_go_web/src/store/routesList.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
/**
|
||||
* 路由列表
|
||||
* @methods setRoutesList 设置路由数据
|
||||
* @methods setColumnsMenuHover 设置分栏布局菜单鼠标移入 boolean
|
||||
* @methods setColumnsNavHover 设置分栏布局最左侧导航鼠标移入 boolean
|
||||
*/
|
||||
export const useRoutesList = defineStore('routesList', {
|
||||
state: (): RoutesListState => ({
|
||||
routesList: [],
|
||||
}),
|
||||
actions: {
|
||||
async setRoutesList(data: Array<string>) {
|
||||
this.routesList = data;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,10 +1,7 @@
|
||||
import { Module } from 'vuex';
|
||||
// 此处加上 `.ts` 后缀报错,具体原因不详
|
||||
import { ThemeConfigState, RootStateTypes } from '@/store/interface/index';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
export const useThemeConfig = defineStore('themeConfig', {
|
||||
state: (): ThemeConfigState => ({
|
||||
themeConfig: {
|
||||
// 是否开启布局配置抽屉
|
||||
isDrawer: false,
|
||||
@@ -73,12 +70,13 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
isBreadcrumb: true,
|
||||
// 是否开启 Tagsview
|
||||
isTagsview: true,
|
||||
isShareTagsView: false,
|
||||
// 是否开启 Breadcrumb 图标
|
||||
isBreadcrumbIcon: true,
|
||||
// 是否开启 Tagsview 图标
|
||||
isTagsviewIcon: true,
|
||||
// 是否开启 TagsView 缓存
|
||||
isCacheTagsView: false,
|
||||
isCacheTagsView: true,
|
||||
// 是否开启 TagsView 拖拽
|
||||
isSortableTagsView: true,
|
||||
// 是否开启 Footer 底部版权信息
|
||||
@@ -94,8 +92,8 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
|
||||
/* 其它设置
|
||||
------------------------------- */
|
||||
// 默认 Tagsview 风格,可选 1、 tags-style-one 2、 tags-style-two 3、 tags-style-three 4、 tags-style-four
|
||||
tagsStyle: 'tags-style-one',
|
||||
// 默认 Tagsview 风格,可选 1、 tags-style-one 2、 tags-style-two 3、 tags-style-three
|
||||
tagsStyle: 'tags-style-three',
|
||||
// 默认主页面切换动画,可选 1、 slide-right 2、 slide-left 3、 opacitys
|
||||
animation: 'slide-right',
|
||||
// 默认分栏高亮风格,可选 1、 圆角 columns-round 2、 卡片 columns-card
|
||||
@@ -107,13 +105,16 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
layout: 'classic',
|
||||
|
||||
// ssh终端字体颜色
|
||||
terminalForeground: '#7e9192',
|
||||
terminalForeground: '#C5C8C6',
|
||||
// ssh终端背景色
|
||||
terminalBackground: '#002833',
|
||||
terminalBackground: '#121212',
|
||||
// ssh终端cursor色
|
||||
terminalCursor: '#268F81',
|
||||
terminalFontSize: 15,
|
||||
terminalFontWeight: 'normal',
|
||||
terminalCursor: '#F0CC09',
|
||||
terminalFontSize: 14,
|
||||
terminalFontWeight: 'bold',
|
||||
|
||||
// 编辑器主题
|
||||
editorTheme: 'vs',
|
||||
|
||||
|
||||
/* 后端控制路由
|
||||
@@ -132,19 +133,11 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
||||
// 默认全局组件大小,可选值"<|large|default|small>",默认 ''
|
||||
globalComponentSize: '',
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
// 设置布局配置
|
||||
getThemeConfig(state: any, data: object) {
|
||||
state.themeConfig = data;
|
||||
},
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
// 设置布局配置
|
||||
setThemeConfig({ commit }, data: object) {
|
||||
commit('getThemeConfig', data);
|
||||
setThemeConfig(data: ThemeConfigState) {
|
||||
this.themeConfig = data.themeConfig;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default themeConfigModule;
|
||||
})
|
||||
19
mayfly_go_web/src/store/userInfo.ts
Normal file
19
mayfly_go_web/src/store/userInfo.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { getSession } from '@/common/utils/storage';
|
||||
|
||||
export const useUserInfo = defineStore('userInfo', {
|
||||
state: (): UserInfoState => ({
|
||||
userInfo: {},
|
||||
}),
|
||||
actions: {
|
||||
// 设置用户信息
|
||||
async setUserInfo(data: object) {
|
||||
const ui = getSession('userInfo')
|
||||
if (ui) {
|
||||
this.userInfo = ui;
|
||||
} else {
|
||||
this.userInfo = data;
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -29,6 +29,7 @@ body,
|
||||
.layout-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.layout-aside {
|
||||
background: var(--bg-menuBar);
|
||||
box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
|
||||
@@ -38,22 +39,27 @@ body,
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden !important;
|
||||
|
||||
.el-scrollbar__view {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.layout-main {
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.layout-view-bg-white {
|
||||
background: white;
|
||||
width: 100%;
|
||||
@@ -61,33 +67,41 @@ body,
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.layout-el-aside-br-color {
|
||||
border-right: 1px solid rgb(238, 238, 238);
|
||||
}
|
||||
|
||||
.layout-aside-width-default {
|
||||
width: 220px !important;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.layout-aside-width64 {
|
||||
width: 64px !important;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.layout-aside-width1 {
|
||||
width: 1px !important;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.layout-scrollbar {
|
||||
@extend .el-scrollbar;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.layout-mian-height-50 {
|
||||
height: calc(100vh - 50px);
|
||||
}
|
||||
|
||||
.layout-columns-warp {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.layout-hide {
|
||||
display: none;
|
||||
}
|
||||
@@ -104,6 +118,7 @@ body,
|
||||
margin-bottom: 0 !important;
|
||||
border-bottom: 1px solid rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
.el-divider {
|
||||
background-color: rgb(230, 230, 230);
|
||||
}
|
||||
@@ -123,25 +138,31 @@ body,
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-auto {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
@extend .flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.flex-margin {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.flex-warp {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
margin: 0 -5px;
|
||||
|
||||
.flex-warp-item {
|
||||
padding: 5px;
|
||||
|
||||
.flex-warp-item-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -154,15 +175,19 @@ body,
|
||||
.w100 {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.h100 {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.vh100 {
|
||||
height: 100vh !important;
|
||||
}
|
||||
|
||||
.max100vh {
|
||||
max-height: 100vh !important;
|
||||
}
|
||||
|
||||
.min100vh {
|
||||
min-height: 100vh !important;
|
||||
}
|
||||
@@ -172,15 +197,19 @@ body,
|
||||
.color-primary {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.color-success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.color-warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.color-danger {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.color-info {
|
||||
color: var(--color-info);
|
||||
}
|
||||
@@ -199,24 +228,31 @@ body,
|
||||
.mt#{$i} {
|
||||
margin-top: #{$i}px !important;
|
||||
}
|
||||
|
||||
.mr#{$i} {
|
||||
margin-right: #{$i}px !important;
|
||||
}
|
||||
|
||||
.mb#{$i} {
|
||||
margin-bottom: #{$i}px !important;
|
||||
}
|
||||
|
||||
.ml#{$i} {
|
||||
margin-left: #{$i}px !important;
|
||||
}
|
||||
|
||||
.pt#{$i} {
|
||||
padding-top: #{$i}px !important;
|
||||
}
|
||||
|
||||
.pr#{$i} {
|
||||
padding-right: #{$i}px !important;
|
||||
}
|
||||
|
||||
.pb#{$i} {
|
||||
padding-bottom: #{$i}px !important;
|
||||
}
|
||||
|
||||
.pl#{$i} {
|
||||
padding-left: #{$i}px !important;
|
||||
}
|
||||
@@ -249,15 +285,22 @@ body,
|
||||
.el-menu .fa:not(.is-children) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.gray-mode {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .2s ease-in-out;
|
||||
}
|
||||
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to
|
||||
|
||||
/* .fade-leave-active below version 2.1.8 */
|
||||
{
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@@ -285,8 +328,20 @@ body,
|
||||
float: left;
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
.el-form-item {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-table-z-index-inherit .el-table .el-table__cell {
|
||||
z-index: inherit !important;
|
||||
}
|
||||
|
||||
.f12 {
|
||||
font-size: 12px
|
||||
}
|
||||
@@ -3,83 +3,27 @@
|
||||
.icon-selector-popper {
|
||||
padding: 0 !important;
|
||||
.icon-selector-warp {
|
||||
height: 260px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.icon-selector-warp-title {
|
||||
position: absolute;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
padding: 0 15px;
|
||||
left: 15px;
|
||||
}
|
||||
.icon-selector-warp-row {
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
padding: 15px 15px 5px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
.ele-col:nth-last-child(1),
|
||||
.ele-col:nth-last-child(2) {
|
||||
display: none;
|
||||
}
|
||||
.awe-col:nth-child(-n + 24) {
|
||||
display: none;
|
||||
}
|
||||
.icon-selector-warp-item {
|
||||
.el-tabs__header {
|
||||
display: flex;
|
||||
border: 1px solid #ebeef5;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.3s ease;
|
||||
.icon-selector-warp-item-value {
|
||||
transition: all 0.3s ease;
|
||||
i {
|
||||
font-size: 20px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid var(--color-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
.icon-selector-warp-item-value {
|
||||
i {
|
||||
color: var(--color-primary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-selector-active {
|
||||
border: 1px solid var(--color-primary);
|
||||
.icon-selector-warp-item-value {
|
||||
i {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-selector-all {
|
||||
.el-input {
|
||||
justify-content: flex-end;
|
||||
padding: 0 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
&-tabs {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 15px;
|
||||
margin-bottom: 5px;
|
||||
&-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
&-active {
|
||||
background: var(--color-primary);
|
||||
border-radius: 5px;
|
||||
.label {
|
||||
color: #ffffff;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
margin: 0 !important;
|
||||
.el-tabs__nav-wrap {
|
||||
&::after {
|
||||
height: 0 !important;
|
||||
}
|
||||
.el-tabs__item {
|
||||
padding: 0 5px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
mayfly_go_web/src/types/env.d.ts
vendored
Normal file
5
mayfly_go_web/src/types/env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue';
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
// 接口类型声明
|
||||
declare interface UserInfoState<T = any> {
|
||||
userInfo: any
|
||||
}
|
||||
|
||||
// 布局配置
|
||||
export interface ThemeConfigState {
|
||||
declare interface ThemeConfigState {
|
||||
themeConfig: {
|
||||
isDrawer: boolean;
|
||||
primary: string;
|
||||
@@ -30,6 +31,7 @@ export interface ThemeConfigState {
|
||||
isShowLogoChange: boolean;
|
||||
isBreadcrumb: boolean;
|
||||
isTagsview: boolean;
|
||||
isShareTagsView: boolean;
|
||||
isBreadcrumbIcon: boolean;
|
||||
isTagsviewIcon: boolean;
|
||||
isCacheTagsView: boolean;
|
||||
@@ -52,35 +54,24 @@ export interface ThemeConfigState {
|
||||
terminalBackground: string;
|
||||
terminalCursor: string;
|
||||
terminalFontSize: number;
|
||||
terminalFontWeight: string;
|
||||
terminalFontWeight: string | any;
|
||||
editorTheme: string;
|
||||
};
|
||||
}
|
||||
|
||||
// TagsView 路由列表
|
||||
declare interface TagsViewRoutesState<T = any> {
|
||||
tagsViewRoutes: T[];
|
||||
isTagsViewCurrenFull: Boolean;
|
||||
}
|
||||
|
||||
// 路由列表
|
||||
export interface RoutesListState {
|
||||
routesList: Array<object>;
|
||||
declare interface RoutesListState {
|
||||
routesList: T[];
|
||||
}
|
||||
|
||||
// 路由缓存列表
|
||||
export interface KeepAliveNamesState {
|
||||
keepAliveNames: Array<string>;
|
||||
}
|
||||
|
||||
// 用户信息
|
||||
export interface UserInfosState {
|
||||
userInfos: object;
|
||||
}
|
||||
|
||||
// 后端返回原始路由(未处理时)
|
||||
// export interface RequestOldRoutesState {
|
||||
// requestOldRoutes: Array<object>;
|
||||
// }
|
||||
|
||||
// 主接口(顶级类型声明)
|
||||
export interface RootStateTypes {
|
||||
themeConfig: ThemeConfigState;
|
||||
routesList: RoutesListState;
|
||||
keepAliveNames: KeepAliveNamesState;
|
||||
userInfos: UserInfosState;
|
||||
// requestOldRoutes: RequestOldRoutesState;
|
||||
declare interface KeepAliveNamesState {
|
||||
keepAliveNames: string[];
|
||||
cachedViews: string[];
|
||||
}
|
||||
14
mayfly_go_web/src/types/shim.d.ts
vendored
Normal file
14
mayfly_go_web/src/types/shim.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
/* eslint-disable */
|
||||
import {IDisposable} from 'monaco-editor';
|
||||
declare global {
|
||||
interface Window {
|
||||
completionItemProvider?: IDisposable | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 申明外部 npm 插件模块
|
||||
declare module 'sql-formatter';
|
||||
declare module 'jsoneditor';
|
||||
declare module 'asciinema-player';
|
||||
declare module 'monaco-editor';
|
||||
declare module 'vue-grid-layout';
|
||||
@@ -1,3 +1,4 @@
|
||||
// 声明一个模块,防止引入文件时报错
|
||||
declare module '*.json';
|
||||
declare module '*.png';
|
||||
declare module '*.jpg';
|
||||
@@ -4,16 +4,17 @@
|
||||
<el-col :sm="6" class="mb15">
|
||||
<div @click="toPage({ id: 'personal' })" class="home-card-item home-card-first">
|
||||
<div class="flex-margin flex">
|
||||
<img :src="getUserInfos.photo" />
|
||||
<img :src="userInfo.photo" />
|
||||
<div class="home-card-first-right ml15">
|
||||
<div class="flex-margin">
|
||||
<div class="home-card-first-right-title">{{ `${currentTime}, ${getUserInfos.username}` }}</div>
|
||||
<div class="home-card-first-right-title">{{ `${currentTime}, ${userInfo.username}`
|
||||
}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList" :key="k">
|
||||
<el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList as any" :key="k">
|
||||
<div @click="toPage(v)" class="home-card-item home-card-item-box" :style="{ background: v.color }">
|
||||
<div class="home-card-item-flex">
|
||||
<div class="home-card-item-title pb3">{{ v.title }}</div>
|
||||
@@ -26,20 +27,19 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
// import * as echarts from 'echarts';
|
||||
import { CountUp } from 'countup.js';
|
||||
import { formatAxis } from '@/common/utils/formatTime.ts';
|
||||
import { formatAxis } from '@/common/utils/format.ts';
|
||||
import { indexApi } from './api';
|
||||
import { useRouter } from 'vue-router';
|
||||
export default {
|
||||
name: 'HomePage',
|
||||
setup() {
|
||||
// const { proxy } = getCurrentInstance() as any;
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const { userInfo } = storeToRefs(useUserInfo());
|
||||
|
||||
const state = reactive({
|
||||
topCardItemList: [
|
||||
{
|
||||
@@ -65,6 +65,10 @@ export default {
|
||||
],
|
||||
});
|
||||
|
||||
const {
|
||||
topCardItemList,
|
||||
} = toRefs(state)
|
||||
|
||||
// 当前时间提示语
|
||||
const currentTime = computed(() => {
|
||||
return formatAxis(new Date());
|
||||
@@ -112,25 +116,12 @@ export default {
|
||||
// initHomeLaboratory();
|
||||
// initHomeOvertime();
|
||||
});
|
||||
|
||||
// 获取用户信息 vuex
|
||||
const getUserInfos = computed(() => {
|
||||
return store.state.userInfos.userInfos;
|
||||
});
|
||||
|
||||
return {
|
||||
getUserInfos,
|
||||
currentTime,
|
||||
toPage,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.home-container {
|
||||
overflow-x: hidden;
|
||||
|
||||
.home-card-item {
|
||||
width: 100%;
|
||||
height: 103px;
|
||||
@@ -138,16 +129,19 @@ export default {
|
||||
border-radius: 4px;
|
||||
transition: all ease 0.3s;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
.home-card-item-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
i {
|
||||
right: 0px !important;
|
||||
@@ -155,6 +149,7 @@ export default {
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
@@ -163,48 +158,59 @@ export default {
|
||||
transform: rotate(-30deg);
|
||||
transition: all ease 0.3s;
|
||||
}
|
||||
|
||||
.home-card-item-flex {
|
||||
padding: 0 20px;
|
||||
color: white;
|
||||
|
||||
.home-card-item-title,
|
||||
.home-card-item-tip {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.home-card-item-title-num {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.home-card-item-tip-num {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-card-first {
|
||||
background: white;
|
||||
border: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 100%;
|
||||
border: 2px solid var(--color-primary-light-5);
|
||||
}
|
||||
|
||||
.home-card-first-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.home-card-first-right-msg {
|
||||
font-size: 13px;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-monitor {
|
||||
height: 200px;
|
||||
|
||||
.flex-warp-item {
|
||||
width: 50%;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
|
||||
.flex-warp-item-box {
|
||||
margin: auto;
|
||||
height: auto;
|
||||
@@ -212,19 +218,24 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-warning-card {
|
||||
height: 292px;
|
||||
|
||||
::v-deep(.el-card) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic {
|
||||
height: 200px;
|
||||
|
||||
.home-dynamic-item {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
overflow: hidden;
|
||||
|
||||
&:first-of-type {
|
||||
.home-dynamic-item-line {
|
||||
i {
|
||||
@@ -232,20 +243,24 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic-item-left {
|
||||
text-align: right;
|
||||
.home-dynamic-item-left-time1 {
|
||||
}
|
||||
|
||||
.home-dynamic-item-left-time1 {}
|
||||
|
||||
.home-dynamic-item-left-time2 {
|
||||
font-size: 13px;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic-item-line {
|
||||
height: 60px;
|
||||
border-right: 2px dashed #dfdfdf;
|
||||
margin: 0 20px;
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
color: var(--color-primary);
|
||||
font-size: 12px;
|
||||
@@ -256,8 +271,10 @@ export default {
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic-item-right {
|
||||
flex: 1;
|
||||
|
||||
.home-dynamic-item-right-title {
|
||||
i {
|
||||
margin-right: 5px;
|
||||
@@ -270,6 +287,7 @@ export default {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.home-dynamic-item-right-label {
|
||||
font-size: 13px;
|
||||
color: gray;
|
||||
@@ -1,6 +1,6 @@
|
||||
import Api from '@/common/Api';
|
||||
|
||||
export const indexApi = {
|
||||
getIndexCount: Api.create("/common/index/count", 'get'),
|
||||
getIndexCount: Api.newGet("/common/index/count"),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +1,43 @@
|
||||
<template>
|
||||
<el-aside class="layout-aside" :class="setCollapseWidth" v-if="clientWidth > 1000">
|
||||
<el-aside class="layout-aside" :class="setCollapseWidth" v-if="state.clientWidth > 1000">
|
||||
<Logo v-if="setShowLogo" />
|
||||
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
|
||||
<Vertical :menuList="menuList" :class="setCollapseWidth" />
|
||||
<Vertical :menuList="state.menuList" :class="setCollapseWidth" />
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
<el-drawer v-model="getThemeConfig.isCollapse" :with-header="false" direction="ltr" size="220px" v-else>
|
||||
<el-drawer v-model="themeConfig.isCollapse" :with-header="false" direction="ltr" size="220px" v-else>
|
||||
<el-aside class="layout-aside w100 h100">
|
||||
<Logo v-if="setShowLogo" />
|
||||
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
|
||||
<Vertical :menuList="menuList" />
|
||||
<Vertical :menuList="state.menuList" />
|
||||
</el-scrollbar>
|
||||
</el-aside>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, onUnmounted } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
<script lang="ts" setup name="layoutAside">
|
||||
import { reactive, computed, watch, getCurrentInstance, onBeforeMount, onUnmounted } from 'vue';
|
||||
import pinia from '@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import Logo from '@/views/layout/logo/index.vue';
|
||||
import Vertical from '@/views/layout/navMenu/vertical.vue';
|
||||
export default {
|
||||
name: 'layoutAside',
|
||||
components: { Logo, Vertical },
|
||||
setup() {
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const { routesList } = storeToRefs(useRoutesList());
|
||||
|
||||
const state: any = reactive({
|
||||
menuList: [],
|
||||
clientWidth: '',
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
|
||||
// 设置菜单展开/收起时的宽度
|
||||
const setCollapseWidth = computed(() => {
|
||||
let { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isCollapse, menuBar } = themeConfig.value;
|
||||
let asideBrColor =
|
||||
menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
|
||||
if (layout === 'columns') {
|
||||
@@ -55,16 +56,19 @@ export default {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 设置显示/隐藏 logo
|
||||
const setShowLogo = computed(() => {
|
||||
let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isShowLogo } = themeConfig.value;
|
||||
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
|
||||
});
|
||||
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
if (store.state.themeConfig.themeConfig.layout === 'columns') return false;
|
||||
state.menuList = filterRoutesFun(store.state.routesList.routesList);
|
||||
if (themeConfig.value.layout === 'columns') return false;
|
||||
state.menuList = filterRoutesFun(routesList.value);
|
||||
};
|
||||
|
||||
// 路由过滤递归函数
|
||||
const filterRoutesFun = (arr: Array<object>) => {
|
||||
return arr
|
||||
@@ -79,54 +83,49 @@ export default {
|
||||
const initMenuFixed = (clientWidth: number) => {
|
||||
state.clientWidth = clientWidth;
|
||||
};
|
||||
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(store.state.themeConfig.themeConfig, (val) => {
|
||||
watch(themeConfig.value, (val) => {
|
||||
if (val.isShowLogoChange !== val.isShowLogo) {
|
||||
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
|
||||
proxy.$refs.layoutAsideScrollbarRef.update();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听路由的变化,动态赋值给菜单中
|
||||
watch(store.state, (val) => {
|
||||
watch(pinia.state, (val) => {
|
||||
if (val.routesList.routesList.length === state.menuList.length) return false;
|
||||
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
|
||||
if (layout === 'classic' && isClassicSplitMenu) return false;
|
||||
setFilterRoutes();
|
||||
});
|
||||
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
initMenuFixed(document.body.clientWidth);
|
||||
setFilterRoutes();
|
||||
proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
|
||||
mittBus.on('setSendColumnsChildren', (res: any) => {
|
||||
state.menuList = res.children;
|
||||
});
|
||||
proxy.mittBus.on('setSendClassicChildren', (res: any) => {
|
||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
||||
mittBus.on('setSendClassicChildren', (res: any) => {
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
state.menuList = [];
|
||||
state.menuList = res.children;
|
||||
}
|
||||
});
|
||||
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||
setFilterRoutes();
|
||||
});
|
||||
proxy.mittBus.on('layoutMobileResize', (res: any) => {
|
||||
mittBus.on('layoutMobileResize', (res: any) => {
|
||||
initMenuFixed(res.clientWidth);
|
||||
});
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
proxy.mittBus.off('setSendColumnsChildren');
|
||||
proxy.mittBus.off('setSendClassicChildren');
|
||||
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
||||
proxy.mittBus.off('layoutMobileResize');
|
||||
mittBus.off('setSendColumnsChildren');
|
||||
mittBus.off('setSendClassicChildren');
|
||||
mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
||||
mittBus.off('layoutMobileResize');
|
||||
});
|
||||
return {
|
||||
setCollapseWidth,
|
||||
setShowLogo,
|
||||
getThemeConfig,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -2,19 +2,12 @@
|
||||
<div class="layout-columns-aside">
|
||||
<el-scrollbar>
|
||||
<ul>
|
||||
<li
|
||||
v-for="(v, k) in columnsAsideList"
|
||||
:key="k"
|
||||
@click="onColumnsAsideMenuClick(v, k)"
|
||||
:ref="
|
||||
<li v-for="(v, k) in state.columnsAsideList" :key="k" @click="onColumnsAsideMenuClick(v, k)" :ref="
|
||||
(el) => {
|
||||
if (el) columnsAsideOffsetTopRefs[k] = el;
|
||||
}
|
||||
"
|
||||
:class="{ 'layout-columns-active': liIndex === k }"
|
||||
:title="v.meta.title"
|
||||
>
|
||||
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.isIframe)">
|
||||
" :class="{ 'layout-columns-active': state.liIndex === k }" :title="v.meta.title">
|
||||
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
|
||||
<i :class="v.meta.icon"></i>
|
||||
<div class="layout-columns-aside-li-box-title font12">
|
||||
{{ v.meta.title && v.meta.title.length >= 4 ? v.meta.title.substr(0, 4) : v.meta.title }}
|
||||
@@ -35,17 +28,16 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch } from 'vue';
|
||||
<script lang="ts" setup name="layoutColumnsAside">
|
||||
import { reactive, ref, computed, onMounted, nextTick, getCurrentInstance, watch } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
export default {
|
||||
name: 'layoutColumnsAside',
|
||||
setup() {
|
||||
import pinia from '@/store/index';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const columnsAsideOffsetTopRefs: any = ref([]);
|
||||
const columnsAsideActiveRef = ref();
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state: any = reactive({
|
||||
@@ -56,7 +48,7 @@ export default {
|
||||
});
|
||||
// 设置高亮样式
|
||||
const setColumnsAsideStyle = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.columnsAsideStyle;
|
||||
return useThemeConfig().themeConfig.columnsAsideStyle;
|
||||
});
|
||||
// 设置菜单高亮位置移动
|
||||
const setColumnsAsideMove = (k: number) => {
|
||||
@@ -78,10 +70,10 @@ export default {
|
||||
};
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
state.columnsAsideList = filterRoutesFun(store.state.routesList.routesList);
|
||||
state.columnsAsideList = filterRoutesFun(useRoutesList().routesList);
|
||||
const resData: any = setSendChildren(route.path);
|
||||
onColumnsAsideDown(resData.item[0].k);
|
||||
proxy.mittBus.emit('setSendColumnsChildren', resData);
|
||||
mittBus.emit('setSendColumnsChildren', resData);
|
||||
};
|
||||
// 传送当前子级数据到菜单中
|
||||
const setSendChildren = (path: string) => {
|
||||
@@ -119,7 +111,7 @@ export default {
|
||||
}, 0);
|
||||
};
|
||||
// 监听路由的变化,动态赋值给菜单中
|
||||
watch(store.state, (val) => {
|
||||
watch(pinia.state, (val) => {
|
||||
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
|
||||
if (val.routesList.routesList.length === state.columnsAsideList.length) return false;
|
||||
setFilterRoutes();
|
||||
@@ -131,18 +123,8 @@ export default {
|
||||
// 路由更新时
|
||||
onBeforeRouteUpdate((to) => {
|
||||
setColumnsMenuHighlight(to.path);
|
||||
proxy.mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
|
||||
mittBus.emit('setSendColumnsChildren', setSendChildren(to.path));
|
||||
});
|
||||
return {
|
||||
columnsAsideOffsetTopRefs,
|
||||
columnsAsideActiveRef,
|
||||
onColumnsAsideDown,
|
||||
setColumnsAsideStyle,
|
||||
onColumnsAsideMenuClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -150,8 +132,10 @@ export default {
|
||||
width: 64px;
|
||||
height: 100%;
|
||||
background: var(--bg-columnsMenuBar);
|
||||
|
||||
ul {
|
||||
position: relative;
|
||||
|
||||
li {
|
||||
color: var(--bg-columnsMenuBarColor);
|
||||
width: 100%;
|
||||
@@ -161,21 +145,26 @@ export default {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
.layout-columns-aside-li-box {
|
||||
margin: auto;
|
||||
|
||||
.layout-columns-aside-li-box-title {
|
||||
padding-top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--bg-columnsMenuBarColor);
|
||||
}
|
||||
}
|
||||
|
||||
.layout-columns-active {
|
||||
color: #ffffff;
|
||||
transition: 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.columns-round {
|
||||
background: var(--color-primary);
|
||||
color: #ffffff;
|
||||
@@ -189,6 +178,7 @@ export default {
|
||||
transition: 0.3s ease-in-out;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.columns-card {
|
||||
@extend .columns-round;
|
||||
top: 0;
|
||||
|
||||
@@ -6,16 +6,15 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import NavBarsIndex from '@/views/layout/navBars/index.vue';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
export default {
|
||||
name: 'layoutHeader',
|
||||
components: { NavBarsIndex },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
// 设置 header 的高度
|
||||
const setHeaderHeight = computed(() => {
|
||||
let { isTagsview, layout } = store.state.themeConfig.themeConfig;
|
||||
let { isTagsview, layout } = useThemeConfig().themeConfig;
|
||||
if (isTagsview && layout !== 'classic') return '84px';
|
||||
else return '50px';
|
||||
});
|
||||
|
||||
@@ -1,52 +1,38 @@
|
||||
<template>
|
||||
<el-main class="layout-main">
|
||||
<el-scrollbar
|
||||
class="layout-scrollbar"
|
||||
ref="layoutScrollbarRef"
|
||||
v-show="!currentRouteMeta.link && !currentRouteMeta.isIframe"
|
||||
:style="{ minHeight: `calc(100vh - ${headerHeight}` }"
|
||||
>
|
||||
<el-scrollbar class="layout-scrollbar" ref="layoutScrollbarRef"
|
||||
v-show="!state.currentRouteMeta.link && state.currentRouteMeta.linkType != 1"
|
||||
:style="{ minHeight: `calc(100vh - ${state.headerHeight}` }">
|
||||
<LayoutParentView />
|
||||
<Footer v-if="getThemeConfig.isFooter" />
|
||||
<Footer v-if="themeConfig.isFooter" />
|
||||
</el-scrollbar>
|
||||
<Link
|
||||
:style="{ height: `calc(100vh - ${headerHeight}` }"
|
||||
:meta="currentRouteMeta"
|
||||
v-if="currentRouteMeta.link && !currentRouteMeta.isIframe"
|
||||
/>
|
||||
<Iframes
|
||||
:style="{ height: `calc(100vh - ${headerHeight}` }"
|
||||
:meta="currentRouteMeta"
|
||||
v-if="currentRouteMeta.link && currentRouteMeta.isIframe && isShowLink"
|
||||
@getCurrentRouteMeta="onGetCurrentRouteMeta"
|
||||
/>
|
||||
<Link :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
|
||||
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 2" />
|
||||
<Iframes :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
|
||||
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 1 && state.isShowLink"
|
||||
@getCurrentRouteMeta="onGetCurrentRouteMeta" />
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
|
||||
<script setup lang="ts" name="layoutMain">
|
||||
import { reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import LayoutParentView from '@/views/layout/routerView/parent.vue';
|
||||
import Footer from '@/views/layout/footer/index.vue';
|
||||
import Link from '@/views/layout/routerView/link.vue';
|
||||
import Iframes from '@/views/layout/routerView/iframes.vue';
|
||||
export default defineComponent({
|
||||
name: 'layoutMain',
|
||||
components: { LayoutParentView, Footer, Link, Iframes },
|
||||
setup() {
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const route = useRoute();
|
||||
const state = reactive({
|
||||
headerHeight: '',
|
||||
currentRouteMeta: {},
|
||||
currentRouteMeta: {} as any,
|
||||
isShowLink: false,
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
|
||||
// 子组件触发更新
|
||||
const onGetCurrentRouteMeta = () => {
|
||||
initCurrentRouteMeta(route.meta);
|
||||
@@ -61,7 +47,7 @@ export default defineComponent({
|
||||
};
|
||||
// 设置 main 的高度
|
||||
const initHeaderHeight = () => {
|
||||
let { isTagsview } = store.state.themeConfig.themeConfig;
|
||||
let { isTagsview } = themeConfig.value;
|
||||
if (isTagsview) return (state.headerHeight = `84px`);
|
||||
else return (state.headerHeight = `50px`);
|
||||
};
|
||||
@@ -71,7 +57,7 @@ export default defineComponent({
|
||||
initHeaderHeight();
|
||||
});
|
||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||
watch(store.state.themeConfig.themeConfig, (val) => {
|
||||
watch(themeConfig.value, (val) => {
|
||||
state.headerHeight = val.isTagsview ? '84px' : '50px';
|
||||
if (val.isFixedHeaderChange !== val.isFixedHeader) {
|
||||
if (!proxy.$refs.layoutScrollbarRef) return false;
|
||||
@@ -83,15 +69,7 @@ export default defineComponent({
|
||||
() => route.path,
|
||||
() => {
|
||||
initCurrentRouteMeta(route.meta);
|
||||
proxy.$refs.layoutScrollbarRef.wrap$.scrollTop = 0;
|
||||
proxy.$refs.layoutScrollbarRef.wrapRef.scrollTop = 0;
|
||||
}
|
||||
);
|
||||
return {
|
||||
getThemeConfig,
|
||||
initCurrentRouteMeta,
|
||||
onGetCurrentRouteMeta,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="layout-footer mt15" v-show="isDelayFooter">
|
||||
<div class="layout-footer-warp">
|
||||
<div>vue-next-admin,Made by lyt with ❤️</div>
|
||||
<div class="mt5">mayfly</div>
|
||||
<div>Made by mayfly with ❤️</div>
|
||||
<div class="mt5">mayfly-go</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,40 +1,35 @@
|
||||
<template>
|
||||
<Defaults v-if="getThemeConfig.layout === 'defaults'" />
|
||||
<Classic v-else-if="getThemeConfig.layout === 'classic'" />
|
||||
<Transverse v-else-if="getThemeConfig.layout === 'transverse'" />
|
||||
<Columns v-else-if="getThemeConfig.layout === 'columns'" />
|
||||
<Defaults v-if="themeConfig.layout === 'defaults'" />
|
||||
<Classic v-else-if="themeConfig.layout === 'classic'" />
|
||||
<Transverse v-else-if="themeConfig.layout === 'transverse'" />
|
||||
<Columns v-else-if="themeConfig.layout === 'columns'" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, onBeforeMount, onUnmounted, getCurrentInstance } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { getLocal, setLocal } from '@/common/utils/storage.ts';
|
||||
<script setup lang="ts" name="layout">
|
||||
import { onBeforeMount, onUnmounted } from 'vue';
|
||||
import { getLocal, setLocal } from '@/common/utils/storage';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import Defaults from '@/views/layout/main/defaults.vue';
|
||||
import Classic from '@/views/layout/main/classic.vue';
|
||||
import Transverse from '@/views/layout/main/transverse.vue';
|
||||
import Columns from '@/views/layout/main/columns.vue';
|
||||
export default {
|
||||
name: 'layout',
|
||||
components: { Defaults, Classic, Transverse, Columns },
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
|
||||
// 窗口大小改变时(适配移动端)
|
||||
const onLayoutResize = () => {
|
||||
if (!getLocal('oldLayout')) setLocal('oldLayout', getThemeConfig.value.layout);
|
||||
if (!getLocal('oldLayout')) setLocal('oldLayout', themeConfig.value.layout);
|
||||
const clientWidth = document.body.clientWidth;
|
||||
if (clientWidth < 1000) {
|
||||
getThemeConfig.value.isCollapse = false;
|
||||
proxy.mittBus.emit('layoutMobileResize', {
|
||||
themeConfig.value.isCollapse = false;
|
||||
mittBus.emit('layoutMobileResize', {
|
||||
layout: 'defaults',
|
||||
clientWidth,
|
||||
});
|
||||
} else {
|
||||
proxy.mittBus.emit('layoutMobileResize', {
|
||||
mittBus.emit('layoutMobileResize', {
|
||||
layout: getLocal('oldLayout') ? getLocal('oldLayout') : 'defaults',
|
||||
clientWidth,
|
||||
});
|
||||
@@ -49,9 +44,4 @@ export default {
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', onLayoutResize);
|
||||
});
|
||||
return {
|
||||
getThemeConfig,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,49 +1,57 @@
|
||||
<template>
|
||||
<div v-show="isShowLockScreen">
|
||||
<div v-show="state.isShowLockScreen">
|
||||
<div class="layout-lock-screen-mask"></div>
|
||||
<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': isShowLoockLogin }"></div>
|
||||
<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': state.isShowLoockLogin }"></div>
|
||||
<div class="layout-lock-screen">
|
||||
<div
|
||||
class="layout-lock-screen-date"
|
||||
ref="layoutLockScreenDateRef"
|
||||
@mousedown="onDown"
|
||||
@mousemove="onMove"
|
||||
@mousedown="onDownPc"
|
||||
@mousemove="onMovePc"
|
||||
@mouseup="onEnd"
|
||||
@touchstart.stop="onDown"
|
||||
@touchmove.stop="onMove"
|
||||
@touchstart.stop="onDownApp"
|
||||
@touchmove.stop="onMoveApp"
|
||||
@touchend.stop="onEnd"
|
||||
>
|
||||
<div class="layout-lock-screen-date-box">
|
||||
<div class="layout-lock-screen-date-box-time">
|
||||
{{ time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ time.s }}</span>
|
||||
{{ state.time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ state.time.s }}</span>
|
||||
</div>
|
||||
<div class="layout-lock-screen-date-box-info">{{ time.mdq }}</div>
|
||||
<div class="layout-lock-screen-date-box-info">{{ state.time.mdq }}</div>
|
||||
</div>
|
||||
<div class="layout-lock-screen-date-top">
|
||||
<SvgIcon name="ele-Top" />
|
||||
<div class="layout-lock-screen-date-top-text">上滑解锁</div>
|
||||
</div>
|
||||
</div>
|
||||
<transition name="el-zoom-in-center">
|
||||
<div v-show="isShowLoockLogin" class="layout-lock-screen-login">
|
||||
<div v-show="state.isShowLoockLogin" class="layout-lock-screen-login">
|
||||
<div class="layout-lock-screen-login-box">
|
||||
<div class="layout-lock-screen-login-box-img">
|
||||
<img src="https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1813762643,1914315241&fm=26&gp=0.jpg" />
|
||||
<img src="https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500" />
|
||||
</div>
|
||||
<div class="layout-lock-screen-login-box-name">Administrator</div>
|
||||
<div class="layout-lock-screen-login-box-value">
|
||||
<el-input
|
||||
placeholder="请输入密码"
|
||||
ref="layoutLockScreenInputRef"
|
||||
v-model="lockScreenPassword"
|
||||
@keyup.enter.stop="onLockScreenSubmit()"
|
||||
v-model="state.lockScreenPassword"
|
||||
@keyup.enter.native.stop="onLockScreenSubmit()"
|
||||
>
|
||||
<template #append>
|
||||
<el-button icon="el-icon-right" @click="onLockScreenSubmit"></el-button>
|
||||
<el-button @click="onLockScreenSubmit">
|
||||
<el-icon class="el-input__icon">
|
||||
<ele-Right />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-lock-screen-login-icon">
|
||||
<i class="el-icon-microphone"></i>
|
||||
<i class="el-icon-alarm-clock"></i>
|
||||
<i class="el-icon-switch-button"></i>
|
||||
<SvgIcon name="ele-Microphone" :size="20" />
|
||||
<SvgIcon name="ele-AlarmClock" :size="20" />
|
||||
<SvgIcon name="ele-SwitchButton" :size="20" />
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -51,24 +59,25 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { formatDate } from '@/common/utils/formatTime.ts';
|
||||
import { setLocal } from '@/common/utils/storage.ts';
|
||||
export default {
|
||||
name: 'layoutLockScreen',
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
<script setup lang="ts" name="layoutLockScreen">
|
||||
import { nextTick, onMounted, reactive, ref, onUnmounted } from 'vue';
|
||||
import { formatDate } from '@/common/utils/format';
|
||||
import { setLocal } from '@/common/utils/storage';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
|
||||
// 定义变量内容
|
||||
const layoutLockScreenDateRef = ref<null>();
|
||||
const layoutLockScreenInputRef = ref();
|
||||
const store = useStore();
|
||||
const state: any = reactive({
|
||||
const storesThemeConfig = useThemeConfig();
|
||||
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||
const state = reactive({
|
||||
transparency: 1,
|
||||
downClientY: 0,
|
||||
moveDifference: 0,
|
||||
isShowLoockLogin: false,
|
||||
isFlags: false,
|
||||
querySelectorEl: '',
|
||||
querySelectorEl: '' as any,
|
||||
time: {
|
||||
hm: '',
|
||||
s: '',
|
||||
@@ -79,21 +88,32 @@ export default {
|
||||
isShowLockScreenIntervalTime: 0,
|
||||
lockScreenPassword: '',
|
||||
});
|
||||
// 鼠标按下
|
||||
const onDown = (down: any) => {
|
||||
|
||||
// 鼠标按下 pc
|
||||
const onDownPc = (down: MouseEvent) => {
|
||||
state.isFlags = true;
|
||||
state.downClientY = down.touches ? down.touches[0].clientY : down.clientY;
|
||||
state.downClientY = down.clientY;
|
||||
};
|
||||
// 鼠标移动
|
||||
const onMove = (move: any) => {
|
||||
if (state.isFlags) {
|
||||
const el = state.querySelectorEl;
|
||||
const opacitys = (state.transparency -= 1 / 200);
|
||||
if (move.touches) {
|
||||
state.moveDifference = move.touches[0].clientY - state.downClientY;
|
||||
} else {
|
||||
// 鼠标按下 app
|
||||
const onDownApp = (down: TouchEvent) => {
|
||||
state.isFlags = true;
|
||||
state.downClientY = down.touches[0].clientY;
|
||||
};
|
||||
// 鼠标移动 pc
|
||||
const onMovePc = (move: MouseEvent) => {
|
||||
state.moveDifference = move.clientY - state.downClientY;
|
||||
}
|
||||
onMove();
|
||||
};
|
||||
// 鼠标移动 app
|
||||
const onMoveApp = (move: TouchEvent) => {
|
||||
state.moveDifference = move.touches[0].clientY - state.downClientY;
|
||||
onMove();
|
||||
};
|
||||
// 鼠标移动事件
|
||||
const onMove = () => {
|
||||
if (state.isFlags) {
|
||||
const el = <HTMLElement>state.querySelectorEl;
|
||||
const opacitys = (state.transparency -= 1 / 200);
|
||||
if (state.moveDifference >= 0) return false;
|
||||
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
|
||||
if (state.moveDifference < -400) {
|
||||
@@ -114,13 +134,13 @@ export default {
|
||||
state.isFlags = false;
|
||||
state.transparency = 1;
|
||||
if (state.moveDifference >= -400) {
|
||||
state.querySelectorEl.setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
|
||||
(<HTMLElement>state.querySelectorEl).setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
|
||||
}
|
||||
};
|
||||
// 获取要拖拽的初始元素
|
||||
const initGetElement = () => {
|
||||
nextTick(() => {
|
||||
state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
|
||||
state.querySelectorEl = layoutLockScreenDateRef.value;
|
||||
});
|
||||
};
|
||||
// 时间初始化
|
||||
@@ -138,14 +158,14 @@ export default {
|
||||
};
|
||||
// 锁屏时间定时器
|
||||
const initLockScreen = () => {
|
||||
if (store.state.themeConfig.themeConfig.isLockScreen) {
|
||||
if (themeConfig.value.isLockScreen) {
|
||||
state.isShowLockScreenIntervalTime = window.setInterval(() => {
|
||||
if (store.state.themeConfig.themeConfig.lockScreenTime <= 0) {
|
||||
if (themeConfig.value.lockScreenTime <= 1) {
|
||||
state.isShowLockScreen = true;
|
||||
setLocalThemeConfig();
|
||||
return false;
|
||||
}
|
||||
store.state.themeConfig.themeConfig.lockScreenTime--;
|
||||
themeConfig.value.lockScreenTime--;
|
||||
}, 1000);
|
||||
} else {
|
||||
clearInterval(state.isShowLockScreenIntervalTime);
|
||||
@@ -153,13 +173,13 @@ export default {
|
||||
};
|
||||
// 存储布局配置
|
||||
const setLocalThemeConfig = () => {
|
||||
store.state.themeConfig.themeConfig.isDrawer = false;
|
||||
setLocal('themeConfig', store.state.themeConfig.themeConfig);
|
||||
themeConfig.value.isDrawer = false;
|
||||
setLocal('themeConfig', themeConfig.value);
|
||||
};
|
||||
// 密码输入点击事件
|
||||
const onLockScreenSubmit = () => {
|
||||
store.state.themeConfig.themeConfig.isLockScreen = false;
|
||||
store.state.themeConfig.themeConfig.lockScreenTime = 30;
|
||||
themeConfig.value.isLockScreen = false;
|
||||
themeConfig.value.lockScreenTime = 30;
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 页面加载时
|
||||
@@ -173,16 +193,6 @@ export default {
|
||||
window.clearInterval(state.setIntervalTime);
|
||||
window.clearInterval(state.isShowLockScreenIntervalTime);
|
||||
});
|
||||
return {
|
||||
layoutLockScreenInputRef,
|
||||
onDown,
|
||||
onMove,
|
||||
onEnd,
|
||||
onLockScreenSubmit,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -194,20 +204,18 @@ export default {
|
||||
height: 100%;
|
||||
}
|
||||
.layout-lock-screen-filter {
|
||||
filter: blur(5px);
|
||||
transform: scale(1.2);
|
||||
filter: blur(1px);
|
||||
}
|
||||
.layout-lock-screen-mask {
|
||||
background: rgba(255, 255, 255, 1);
|
||||
background: var(--el-color-white);
|
||||
@extend .layout-lock-screen-fixed;
|
||||
z-index: 9999990;
|
||||
}
|
||||
.layout-lock-screen-img {
|
||||
@extend .layout-lock-screen-fixed;
|
||||
background-image: url('https://img6.bdstatic.com/img/image/pcindex/sunjunpchuazhoutu.JPG');
|
||||
background-image: url('@/assets/image/bg-login.png');
|
||||
background-size: 100% 100%;
|
||||
z-index: 9999991;
|
||||
transition: all ease 0.3s 0.3s;
|
||||
}
|
||||
.layout-lock-screen {
|
||||
@extend .layout-lock-screen-fixed;
|
||||
@@ -218,7 +226,7 @@ export default {
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: #ffffff;
|
||||
color: var(--el-color-white);
|
||||
z-index: 9999993;
|
||||
user-select: none;
|
||||
&-box {
|
||||
@@ -227,14 +235,64 @@ export default {
|
||||
bottom: 50px;
|
||||
&-time {
|
||||
font-size: 100px;
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
&-info {
|
||||
font-size: 40px;
|
||||
color: var(--el-color-white);
|
||||
}
|
||||
&-minutes {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
&-top {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
border-radius: 100%;
|
||||
border: 1px solid var(--el-border-color-light, #ebeef5);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: var(--el-color-white);
|
||||
opacity: 0.8;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 50px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
i {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
&-text {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 150%;
|
||||
font-size: 12px;
|
||||
color: var(--el-color-white);
|
||||
left: 50%;
|
||||
line-height: 1.2;
|
||||
transform: translate(-50%, -50%);
|
||||
transition: all 0.3s ease;
|
||||
width: 35px;
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 0 12px 0 rgba(255, 255, 255, 0.5);
|
||||
color: var(--el-color-white);
|
||||
opacity: 1;
|
||||
transition: all 0.3s ease;
|
||||
i {
|
||||
transform: translateY(-40px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.layout-lock-screen-date-top-text {
|
||||
opacity: 1;
|
||||
top: 50%;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-login {
|
||||
position: relative;
|
||||
@@ -246,7 +304,7 @@ export default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
color: var(--el-color-white);
|
||||
&-box {
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
@@ -281,14 +339,14 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
::v-deep(.el-input-group__append) {
|
||||
background: #ffffff;
|
||||
:deep(.el-input-group__append) {
|
||||
background: var(--el-color-white);
|
||||
padding: 0px 15px;
|
||||
}
|
||||
::v-deep(.el-input__inner) {
|
||||
border-right-color: #f6f6f6;
|
||||
:deep(.el-input__inner) {
|
||||
border-right-color: var(--el-border-color-extra-light);
|
||||
&:hover {
|
||||
border-color: #f6f6f6;
|
||||
border-color: var(--el-border-color-extra-light);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,42 +1,35 @@
|
||||
<template>
|
||||
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
|
||||
<img src="@/assets/image/logo.svg" class="layout-logo-medium-img" />
|
||||
<span>{{ getThemeConfig.globalTitle }}</span>
|
||||
<span>
|
||||
{{ `${themeConfig.globalTitle}` }}
|
||||
<sub><span style="font-size: 10px;color:goldenrod">{{ ` ${config.version}` }}</span></sub>
|
||||
</span>
|
||||
</div>
|
||||
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
|
||||
<img src="@/assets/image/logo.svg" class="layout-logo-size-img" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, getCurrentInstance } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
export default {
|
||||
name: 'layoutLogo',
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
<script setup lang="ts" name="layoutLogo">
|
||||
import { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import config from '@/common/config';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
|
||||
// 设置 logo 的显示。classic 经典布局默认显示 logo
|
||||
const setShowLogo = computed(() => {
|
||||
let { isCollapse, layout } = store.state.themeConfig.themeConfig;
|
||||
let { isCollapse, layout } = themeConfig.value;
|
||||
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
|
||||
});
|
||||
// logo 点击实现菜单展开/收起
|
||||
const onThemeConfigChange = () => {
|
||||
if (store.state.themeConfig.themeConfig.layout === 'transverse') return false;
|
||||
proxy.mittBus.emit('onMenuClick');
|
||||
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
|
||||
};
|
||||
return {
|
||||
setShowLogo,
|
||||
getThemeConfig,
|
||||
onThemeConfigChange,
|
||||
};
|
||||
},
|
||||
if (themeConfig.value.layout === 'transverse') return false;
|
||||
mittBus.emit('onMenuClick');
|
||||
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -52,26 +45,31 @@ export default {
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
span {
|
||||
color: var(--color-primary-light-2);
|
||||
}
|
||||
}
|
||||
|
||||
&-medium-img {
|
||||
width: 20px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-logo-size {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
|
||||
&-img {
|
||||
width: 20px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<el-container class="layout-mian-height-50">
|
||||
<Aside />
|
||||
<div class="flex-center layout-backtop">
|
||||
<TagsView v-if="getThemeConfig.isTagsview" />
|
||||
<TagsView v-if="themeConfig.isTagsview" />
|
||||
<Main />
|
||||
</div>
|
||||
</el-container>
|
||||
@@ -12,25 +12,13 @@
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
<script lang="ts" setup name="layoutClassic">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import Aside from '@/views/layout/component/aside.vue';
|
||||
import Header from '@/views/layout/component/header.vue';
|
||||
import Main from '@/views/layout/component/main.vue';
|
||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
||||
export default {
|
||||
name: 'layoutClassic',
|
||||
components: { Aside, Header, Main, TagsView },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
return {
|
||||
getThemeConfig,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
</script>
|
||||
|
||||
@@ -17,18 +17,17 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import Aside from '@/views/layout/component/aside.vue';
|
||||
import Header from '@/views/layout/component/header.vue';
|
||||
import Main from '@/views/layout/component/main.vue';
|
||||
import ColumnsAside from '@/views/layout/component/columnsAside.vue';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
export default {
|
||||
name: 'layoutColumns',
|
||||
components: { Aside, Header, Main, ColumnsAside },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const isFixedHeader = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.isFixedHeader;
|
||||
return useThemeConfig().themeConfig.isFixedHeader;
|
||||
});
|
||||
return {
|
||||
isFixedHeader,
|
||||
|
||||
@@ -15,19 +15,18 @@
|
||||
<script lang="ts">
|
||||
import { computed, getCurrentInstance, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import Aside from '@/views/layout/component/aside.vue';
|
||||
import Header from '@/views/layout/component/header.vue';
|
||||
import Main from '@/views/layout/component/main.vue';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
export default {
|
||||
name: 'layoutDefaults',
|
||||
components: { Aside, Header, Main },
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const isFixedHeader = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.isFixedHeader;
|
||||
return useThemeConfig().themeConfig.isFixedHeader;
|
||||
});
|
||||
// 监听路由的变化
|
||||
watch(
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
<template>
|
||||
<div class="layout-navbars-breadcrumb" v-show="getThemeConfig.isBreadcrumb">
|
||||
<SvgIcon class="layout-navbars-breadcrumb-icon" :name="getThemeConfig.isCollapse ? 'expand' : 'fold'" @click="onThemeConfigChange" />
|
||||
<div class="layout-navbars-breadcrumb" v-show="themeConfig.isBreadcrumb">
|
||||
<SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'"
|
||||
@click="onThemeConfigChange" />
|
||||
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
|
||||
<transition-group name="breadcrumb" mode="out-in">
|
||||
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="v.meta.title">
|
||||
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />
|
||||
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
|
||||
<span v-if="k === state.breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
|
||||
v-if="themeConfig.isBreadcrumbIcon" />
|
||||
{{ v.meta.title }}
|
||||
</span>
|
||||
<a v-else @click.prevent="onBreadcrumbClick(v)">
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont" v-if="getThemeConfig.isBreadcrumbIcon" />
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
|
||||
v-if="themeConfig.isBreadcrumbIcon" />
|
||||
{{ v.meta.title }}
|
||||
</a>
|
||||
</el-breadcrumb-item>
|
||||
@@ -18,15 +21,16 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, getCurrentInstance, onMounted } from 'vue';
|
||||
<script lang="ts" setup name="layoutBreadcrumb">
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
export default {
|
||||
name: 'layoutBreadcrumb',
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const { routesList } = storeToRefs(useRoutesList());
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state: any = reactive({
|
||||
@@ -35,10 +39,7 @@ export default {
|
||||
routeSplitFirst: '',
|
||||
routeSplitIndex: 1,
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
|
||||
// 面包屑点击时
|
||||
const onBreadcrumbClick = (v: any) => {
|
||||
const { redirect, path } = v;
|
||||
@@ -47,8 +48,8 @@ export default {
|
||||
};
|
||||
// 展开/收起左侧菜单点击
|
||||
const onThemeConfigChange = () => {
|
||||
proxy.mittBus.emit('onMenuClick');
|
||||
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
|
||||
mittBus.emit('onMenuClick');
|
||||
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
|
||||
};
|
||||
// 处理面包屑数据
|
||||
const getBreadcrumbList = (arr: Array<object>) => {
|
||||
@@ -65,13 +66,13 @@ export default {
|
||||
};
|
||||
// 当前路由字符串切割成数组,并删除第一项空内容
|
||||
const initRouteSplit = (path: string) => {
|
||||
if (!store.state.themeConfig.themeConfig.isBreadcrumb) return false;
|
||||
state.breadcrumbList = [store.state.routesList.routesList[0]];
|
||||
if (!themeConfig.value.isBreadcrumb) return false;
|
||||
state.breadcrumbList = [routesList.value[0]];
|
||||
state.routeSplit = path.split('/');
|
||||
state.routeSplit.shift();
|
||||
state.routeSplitFirst = `/${state.routeSplit[0]}`;
|
||||
state.routeSplitIndex = 1;
|
||||
getBreadcrumbList(store.state.routesList.routesList);
|
||||
getBreadcrumbList(routesList.value);
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@@ -81,14 +82,6 @@ export default {
|
||||
onBeforeRouteUpdate((to) => {
|
||||
initRouteSplit(to.path);
|
||||
});
|
||||
return {
|
||||
onThemeConfigChange,
|
||||
getThemeConfig,
|
||||
onBreadcrumbClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -98,20 +91,24 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 15px;
|
||||
|
||||
.layout-navbars-breadcrumb-icon {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
margin-right: 15px;
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
|
||||
.layout-navbars-breadcrumb-span {
|
||||
opacity: 0.7;
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
|
||||
.layout-navbars-breadcrumb-iconfont {
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
::v-deep(.el-breadcrumb__separator) {
|
||||
opacity: 0.7;
|
||||
color: var(--bg-topBarColor);
|
||||
|
||||
@@ -2,52 +2,51 @@
|
||||
<div class="layout-navbars-breadcrumb-index">
|
||||
<Logo v-if="setIsShowLogo" />
|
||||
<Breadcrumb />
|
||||
<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
|
||||
<Horizontal :menuList="state.menuList" v-if="isLayoutTransverse" />
|
||||
<User />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance, watch } from 'vue';
|
||||
<script lang="ts" setup name="layoutBreadcrumbIndex">
|
||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import pinia from '@/store/index';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import Breadcrumb from '@/views/layout/navBars/breadcrumb/breadcrumb.vue';
|
||||
import User from '@/views/layout/navBars/breadcrumb/user.vue';
|
||||
import Logo from '@/views/layout/logo/index.vue';
|
||||
import Horizontal from '@/views/layout/navMenu/horizontal.vue';
|
||||
export default {
|
||||
name: 'layoutBreadcrumbIndex',
|
||||
components: { Breadcrumb, User, Logo, Horizontal },
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const { routesList } = storeToRefs(useRoutesList());
|
||||
const route = useRoute();
|
||||
const state: any = reactive({
|
||||
menuList: [],
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
|
||||
// 设置 logo 显示/隐藏
|
||||
const setIsShowLogo = computed(() => {
|
||||
let { isShowLogo, layout } = store.state.themeConfig.themeConfig;
|
||||
let { isShowLogo, layout } = themeConfig.value;
|
||||
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
|
||||
});
|
||||
// 设置是否显示横向导航菜单
|
||||
const isLayoutTransverse = computed(() => {
|
||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
|
||||
});
|
||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||
const setFilterRoutes = () => {
|
||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
if (layout === 'classic' && isClassicSplitMenu) {
|
||||
state.menuList = delClassicChildren(filterRoutesFun(store.state.routesList.routesList));
|
||||
state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
|
||||
const resData = setSendClassicChildren(route.path);
|
||||
proxy.mittBus.emit('setSendClassicChildren', resData);
|
||||
mittBus.emit('setSendClassicChildren', resData);
|
||||
} else {
|
||||
state.menuList = filterRoutesFun(store.state.routesList.routesList);
|
||||
state.menuList = filterRoutesFun(routesList.value);
|
||||
}
|
||||
};
|
||||
// 设置了分割菜单时,删除底下 children
|
||||
@@ -71,7 +70,7 @@ export default {
|
||||
const setSendClassicChildren = (path: string) => {
|
||||
const currentPathSplit = path.split('/');
|
||||
let currentData: any = {};
|
||||
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
|
||||
filterRoutesFun(routesList.value).map((v, k) => {
|
||||
if (v.path === `/${currentPathSplit[1]}`) {
|
||||
v['k'] = k;
|
||||
currentData['item'] = [{ ...v }];
|
||||
@@ -82,29 +81,21 @@ export default {
|
||||
return currentData;
|
||||
};
|
||||
// 监听路由的变化,动态赋值给菜单中
|
||||
watch(store.state, (val) => {
|
||||
watch(pinia.state, (val) => {
|
||||
if (val.routesList.routesList.length === state.menuList.length) return false;
|
||||
setFilterRoutes();
|
||||
});
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
setFilterRoutes();
|
||||
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||
setFilterRoutes();
|
||||
});
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
||||
mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
||||
});
|
||||
return {
|
||||
getThemeConfig,
|
||||
setIsShowLogo,
|
||||
isLayoutTransverse,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
<template>
|
||||
<div class="layout-search-dialog">
|
||||
<el-dialog v-model="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
|
||||
<el-autocomplete
|
||||
v-model="menuQuery"
|
||||
:fetch-suggestions="menuSearch"
|
||||
placeholder="菜单搜索"
|
||||
prefix-icon="el-icon-search"
|
||||
ref="layoutMenuAutocompleteRef"
|
||||
@select="onHandleSelect"
|
||||
@blur="onSearchBlur"
|
||||
>
|
||||
<el-dialog v-model="state.isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
|
||||
<el-autocomplete v-model="state.menuQuery" :fetch-suggestions="menuSearch" placeholder="菜单搜索"
|
||||
prefix-icon="el-icon-search" ref="layoutMenuAutocompleteRef" @select="onHandleSelect" @blur="onSearchBlur">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon">
|
||||
<search />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default="{ item }">
|
||||
<div><SvgIcon :name="item.meta.icon" class="mr5" />{{ item.meta.title }}</div>
|
||||
<div>
|
||||
<SvgIcon :name="item.meta.icon" class="mr5" />{{ item.meta.title }}
|
||||
</div>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
|
||||
<script lang="ts" setup name="layoutBreadcrumbSearch">
|
||||
import { reactive, ref, nextTick } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbSearch',
|
||||
setup() {
|
||||
const layoutMenuAutocompleteRef: any = ref(null);
|
||||
const store = useStore();
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
|
||||
const layoutMenuAutocompleteRef: any = ref(null);;
|
||||
const router = useRouter();
|
||||
const state: any = reactive({
|
||||
isShowSearch: false,
|
||||
@@ -70,7 +62,7 @@ export default defineComponent({
|
||||
// 初始化菜单数据
|
||||
const initTageView = () => {
|
||||
if (state.tagsViewList.length > 0) return false;
|
||||
getRoutes(store.state.routesList.routesList).map((v: any) => {
|
||||
getRoutes(useRoutesList().routesList).map((v: any) => {
|
||||
if (!v.meta.isHide) {
|
||||
state.tagsViewList.push({ ...v });
|
||||
}
|
||||
@@ -95,7 +87,7 @@ export default defineComponent({
|
||||
// 当前菜单选中时
|
||||
const onHandleSelect = (item: any) => {
|
||||
let { path, redirect } = item;
|
||||
if (item.meta.link && !item.meta.isIframe) window.open(item.meta.link);
|
||||
if (item.meta.link && item.meta.linkType == 2) window.open(item.meta.link);
|
||||
else if (redirect) router.push(redirect);
|
||||
else router.push(path);
|
||||
closeSearch();
|
||||
@@ -104,17 +96,8 @@ export default defineComponent({
|
||||
const onSearchBlur = () => {
|
||||
closeSearch();
|
||||
};
|
||||
return {
|
||||
layoutMenuAutocompleteRef,
|
||||
openSearch,
|
||||
closeSearch,
|
||||
menuSearch,
|
||||
onHandleSelect,
|
||||
onSearchBlur,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
defineExpose({openSearch})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -124,6 +107,7 @@ export default defineComponent({
|
||||
border-radius: 0 !important;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
::v-deep(.el-autocomplete) {
|
||||
width: 560px;
|
||||
position: absolute;
|
||||
|
||||
@@ -1,85 +1,101 @@
|
||||
<template>
|
||||
<div class="layout-breadcrumb-seting">
|
||||
<el-drawer title="布局设置" v-model="getThemeConfig.isDrawer" direction="rtl" destroy-on-close size="240px" @close="onDrawerClose">
|
||||
<el-drawer title="布局设置" v-model="themeConfig.isDrawer" direction="rtl" destroy-on-close size="240px"
|
||||
@close="onDrawerClose">
|
||||
<el-scrollbar class="layout-breadcrumb-seting-bar">
|
||||
<!-- ssh终端主题 -->
|
||||
<el-divider content-position="left">终端主题</el-divider>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')">
|
||||
<el-color-picker v-model="themeConfig.terminalForeground" size="small"
|
||||
@change="onColorPickerChange('terminalForeground')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')">
|
||||
<el-color-picker v-model="themeConfig.terminalBackground" size="small"
|
||||
@change="onColorPickerChange('terminalBackground')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')">
|
||||
<el-color-picker v-model="themeConfig.terminalCursor" size="small"
|
||||
@change="onColorPickerChange('terminalCursor')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-input-number
|
||||
v-model="getThemeConfig.terminalFontSize"
|
||||
controls-position="right"
|
||||
:min="12"
|
||||
:max="24"
|
||||
@change="setLocalThemeConfig"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
>
|
||||
<el-input-number v-model="themeConfig.terminalFontSize" controls-position="right" :min="12"
|
||||
:max="24" @change="setLocalThemeConfig" size="small" style="width: 90px">
|
||||
</el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-select @change="setLocalThemeConfig" v-model="getThemeConfig.terminalFontWeight" size="small" style="width: 90px">
|
||||
<el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalFontWeight" size="small"
|
||||
style="width: 90px">
|
||||
<el-option label="normal" value="normal"> </el-option>
|
||||
<el-option label="bold" value="bold"> </el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider content-position="left">editor 设置</el-divider>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-select @change="setLocalThemeConfig" v-model="themeConfig.editorTheme" size="small"
|
||||
style="width: 130px">
|
||||
<el-option label="vs" value="vs"> </el-option>
|
||||
<el-option label="vs-dark" value="vs-dark"> </el-option>
|
||||
<el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 全局主题 -->
|
||||
<el-divider content-position="left">全局主题</el-divider>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.primary" size="small" @change="onColorPickerChange('primary')"> </el-color-picker>
|
||||
<el-color-picker v-model="themeConfig.primary" size="small"
|
||||
@change="onColorPickerChange('primary')"> </el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">success</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.success" size="small" @change="onColorPickerChange('success')"> </el-color-picker>
|
||||
<el-color-picker v-model="themeConfig.success" size="small"
|
||||
@change="onColorPickerChange('success')"> </el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">info</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.info" size="small" @change="onColorPickerChange('info')"> </el-color-picker>
|
||||
<el-color-picker v-model="themeConfig.info" size="small" @change="onColorPickerChange('info')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">warning</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.warning" size="small" @change="onColorPickerChange('warning')"> </el-color-picker>
|
||||
<el-color-picker v-model="themeConfig.warning" size="small"
|
||||
@change="onColorPickerChange('warning')"> </el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">danger</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.danger" size="small" @change="onColorPickerChange('danger')"> </el-color-picker>
|
||||
<el-color-picker v-model="themeConfig.danger" size="small" @change="onColorPickerChange('danger')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,69 +104,73 @@
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.topBar" size="small" @change="onBgColorPickerChange('topBar')"> </el-color-picker>
|
||||
<el-color-picker v-model="themeConfig.topBar" size="small"
|
||||
@change="onBgColorPickerChange('topBar')"> </el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单背景</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.menuBar" size="small" @change="onBgColorPickerChange('menuBar')"> </el-color-picker>
|
||||
<el-color-picker v-model="themeConfig.menuBar" size="small"
|
||||
@change="onBgColorPickerChange('menuBar')"> </el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.columnsMenuBar" size="small" @change="onBgColorPickerChange('columnsMenuBar')">
|
||||
<el-color-picker v-model="themeConfig.columnsMenuBar" size="small"
|
||||
@change="onBgColorPickerChange('columnsMenuBar')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏默认字体颜色</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.topBarColor" size="small" @change="onBgColorPickerChange('topBarColor')">
|
||||
<el-color-picker v-model="themeConfig.topBarColor" size="small"
|
||||
@change="onBgColorPickerChange('topBarColor')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单默认字体颜色</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker v-model="getThemeConfig.menuBarColor" size="small" @change="onBgColorPickerChange('menuBarColor')">
|
||||
<el-color-picker v-model="themeConfig.menuBarColor" size="small"
|
||||
@change="onBgColorPickerChange('menuBarColor')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单默认字体颜色</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-color-picker
|
||||
v-model="getThemeConfig.columnsMenuBarColor"
|
||||
size="small"
|
||||
@change="onBgColorPickerChange('columnsMenuBarColor')"
|
||||
>
|
||||
<el-color-picker v-model="themeConfig.columnsMenuBarColor" size="small"
|
||||
@change="onBgColorPickerChange('columnsMenuBarColor')">
|
||||
</el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt10">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景渐变</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isTopBarColorGradual" @change="onTopBarGradualChange"></el-switch>
|
||||
<el-switch v-model="themeConfig.isTopBarColorGradual" @change="onTopBarGradualChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单背景渐变</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isMenuBarColorGradual" @change="onMenuBarGradualChange"></el-switch>
|
||||
<el-switch v-model="themeConfig.isMenuBarColorGradual" @change="onMenuBarGradualChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景渐变</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isColumnsMenuBarColorGradual" @change="onColumnsMenuBarGradualChange"></el-switch>
|
||||
<el-switch v-model="themeConfig.isColumnsMenuBarColorGradual"
|
||||
@change="onColumnsMenuBarGradualChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单字体背景高亮</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isMenuBarColorHighlight" @change="onMenuBarHighlightChange"></el-switch>
|
||||
<el-switch v-model="themeConfig.isMenuBarColorHighlight"
|
||||
@change="onMenuBarHighlightChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -159,50 +179,41 @@
|
||||
<div class="layout-breadcrumb-seting-bar-flex">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单水平折叠</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isCollapse" @change="onThemeConfigChange"></el-switch>
|
||||
<el-switch v-model="themeConfig.isCollapse" @change="onThemeConfigChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单手风琴</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isUniqueOpened" @change="setLocalThemeConfig"></el-switch>
|
||||
<el-switch v-model="themeConfig.isUniqueOpened" @change="setLocalThemeConfig"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">固定 Header</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isFixedHeader" @change="onIsFixedHeaderChange"></el-switch>
|
||||
<el-switch v-model="themeConfig.isFixedHeader" @change="onIsFixedHeaderChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout !== 'classic' ? 0.5 : 1 }">
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15"
|
||||
:style="{ opacity: themeConfig.layout !== 'classic' ? 0.5 : 1 }">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">经典布局分割菜单</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch
|
||||
v-model="getThemeConfig.isClassicSplitMenu"
|
||||
:disabled="getThemeConfig.layout !== 'classic'"
|
||||
@change="onClassicSplitMenuChange"
|
||||
>
|
||||
<el-switch v-model="themeConfig.isClassicSplitMenu" :disabled="themeConfig.layout !== 'classic'"
|
||||
@change="onClassicSplitMenuChange">
|
||||
</el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启锁屏</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isLockScreen" @change="setLocalThemeConfig"></el-switch>
|
||||
<el-switch v-model="themeConfig.isLockScreen" @change="setLocalThemeConfig"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt11">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">自动锁屏(s/秒)</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-input-number
|
||||
v-model="getThemeConfig.lockScreenTime"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
:max="9999"
|
||||
@change="setLocalThemeConfig"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
>
|
||||
<el-input-number v-model="themeConfig.lockScreenTime" controls-position="right" :min="0" :max="9999"
|
||||
@change="setLocalThemeConfig" size="small" style="width: 90px">
|
||||
</el-input-number>
|
||||
</div>
|
||||
</div>
|
||||
@@ -212,65 +223,63 @@
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">侧边栏 Logo</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isShowLogo" @change="onIsShowLogoChange"></el-switch>
|
||||
<el-switch v-model="themeConfig.isShowLogo" @change="onIsShowLogoChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: getThemeConfig.layout === 'transverse' ? 0.5 : 1 }">
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15"
|
||||
:style="{ opacity: themeConfig.layout === 'transverse' ? 0.5 : 1 }">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启Breadcrumb</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch
|
||||
v-model="getThemeConfig.isBreadcrumb"
|
||||
:disabled="getThemeConfig.layout === 'transverse'"
|
||||
@change="onIsBreadcrumbChange"
|
||||
></el-switch>
|
||||
<el-switch v-model="themeConfig.isBreadcrumb" :disabled="themeConfig.layout === 'transverse'"
|
||||
@change="onIsBreadcrumbChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启Breadcrumb图标</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isBreadcrumbIcon" @change="setLocalThemeConfig"></el-switch>
|
||||
<el-switch v-model="themeConfig.isBreadcrumbIcon" @change="setLocalThemeConfig"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启 Tagsview</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isTagsview" @change="setLocalThemeConfig"></el-switch>
|
||||
<el-switch v-model="themeConfig.isTagsview" @change="setLocalThemeConfig"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启 Tagsview图标</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isTagsviewIcon" @change="setLocalThemeConfig"></el-switch>
|
||||
<el-switch v-model="themeConfig.isTagsviewIcon" @change="setLocalThemeConfig"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启 TagsView缓存</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isCacheTagsView" @change="setLocalThemeConfig"></el-switch>
|
||||
<el-switch v-model="themeConfig.isCacheTagsView" @change="setLocalThemeConfig"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启 TagsView拖拽</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isSortableTagsView" @change="onSortableTagsViewChange"></el-switch>
|
||||
<el-switch v-model="themeConfig.isSortableTagsView" @change="onSortableTagsViewChange"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启 Footer</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isFooter" @change="setLocalThemeConfig"></el-switch>
|
||||
<el-switch v-model="themeConfig.isFooter" @change="setLocalThemeConfig"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">灰色模式</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isGrayscale" @change="onAddFilterChange('grayscale')"></el-switch>
|
||||
<el-switch v-model="themeConfig.isGrayscale" @change="onAddFilterChange('grayscale')"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">色弱模式</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-switch v-model="getThemeConfig.isInvert" @change="onAddFilterChange('invert')"></el-switch>
|
||||
<el-switch v-model="themeConfig.isInvert" @change="onAddFilterChange('invert')"></el-switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -279,30 +288,19 @@
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">Tagsview 风格</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-select
|
||||
v-model="getThemeConfig.tagsStyle"
|
||||
placeholder="请选择"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
@change="setLocalThemeConfig"
|
||||
>
|
||||
<el-select v-model="themeConfig.tagsStyle" placeholder="请选择" size="small" style="width: 90px"
|
||||
@change="setLocalThemeConfig">
|
||||
<el-option label="风格1" value="tags-style-one"></el-option>
|
||||
<el-option label="风格2" value="tags-style-two"></el-option>
|
||||
<el-option label="风格3" value="tags-style-three"></el-option>
|
||||
<el-option label="风格4" value="tags-style-four"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">主页面切换动画</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-select
|
||||
v-model="getThemeConfig.animation"
|
||||
placeholder="请选择"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
@change="setLocalThemeConfig"
|
||||
>
|
||||
<el-select v-model="themeConfig.animation" placeholder="请选择" size="small" style="width: 90px"
|
||||
@change="setLocalThemeConfig">
|
||||
<el-option label="slide-right" value="slide-right"></el-option>
|
||||
<el-option label="slide-left" value="slide-left"></el-option>
|
||||
<el-option label="opacitys" value="opacitys"></el-option>
|
||||
@@ -312,13 +310,8 @@
|
||||
<div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
|
||||
<div class="layout-breadcrumb-seting-bar-flex-label">分栏高亮风格</div>
|
||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||
<el-select
|
||||
v-model="getThemeConfig.columnsAsideStyle"
|
||||
placeholder="请选择"
|
||||
size="small"
|
||||
style="width: 90px"
|
||||
@change="setLocalThemeConfig"
|
||||
>
|
||||
<el-select v-model="themeConfig.columnsAsideStyle" placeholder="请选择" size="small"
|
||||
style="width: 90px" @change="setLocalThemeConfig">
|
||||
<el-option label="圆角" value="columns-round"></el-option>
|
||||
<el-option label="卡片" value="columns-card"></el-option>
|
||||
</el-select>
|
||||
@@ -330,14 +323,16 @@
|
||||
<div class="layout-drawer-content-flex">
|
||||
<!-- defaults 布局 -->
|
||||
<div class="layout-drawer-content-item" @click="onSetLayout('defaults')">
|
||||
<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'defaults' }">
|
||||
<section class="el-container el-circular"
|
||||
:class="{ 'drawer-layout-active': themeConfig.layout === 'defaults' }">
|
||||
<aside class="el-aside" style="width: 20px"></aside>
|
||||
<section class="el-container is-vertical">
|
||||
<header class="el-header" style="height: 10px"></header>
|
||||
<main class="el-main"></main>
|
||||
</section>
|
||||
</section>
|
||||
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'defaults' }">
|
||||
<div class="layout-tips-warp"
|
||||
:class="{ 'layout-tips-warp-active': themeConfig.layout === 'defaults' }">
|
||||
<div class="layout-tips-box">
|
||||
<p class="layout-tips-txt">默认</p>
|
||||
</div>
|
||||
@@ -345,10 +340,8 @@
|
||||
</div>
|
||||
<!-- classic 布局 -->
|
||||
<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
|
||||
<section
|
||||
class="el-container is-vertical el-circular"
|
||||
:class="{ 'drawer-layout-active': getThemeConfig.layout === 'classic' }"
|
||||
>
|
||||
<section class="el-container is-vertical el-circular"
|
||||
:class="{ 'drawer-layout-active': themeConfig.layout === 'classic' }">
|
||||
<header class="el-header" style="height: 10px"></header>
|
||||
<section class="el-container">
|
||||
<aside class="el-aside" style="width: 20px"></aside>
|
||||
@@ -357,7 +350,8 @@
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'classic' }">
|
||||
<div class="layout-tips-warp"
|
||||
:class="{ 'layout-tips-warp-active': themeConfig.layout === 'classic' }">
|
||||
<div class="layout-tips-box">
|
||||
<p class="layout-tips-txt">经典</p>
|
||||
</div>
|
||||
@@ -365,10 +359,8 @@
|
||||
</div>
|
||||
<!-- transverse 布局 -->
|
||||
<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
|
||||
<section
|
||||
class="el-container is-vertical el-circular"
|
||||
:class="{ 'drawer-layout-active': getThemeConfig.layout === 'transverse' }"
|
||||
>
|
||||
<section class="el-container is-vertical el-circular"
|
||||
:class="{ 'drawer-layout-active': themeConfig.layout === 'transverse' }">
|
||||
<header class="el-header" style="height: 10px"></header>
|
||||
<section class="el-container">
|
||||
<section class="el-container is-vertical">
|
||||
@@ -376,7 +368,8 @@
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'transverse' }">
|
||||
<div class="layout-tips-warp"
|
||||
:class="{ 'layout-tips-warp-active': themeConfig.layout === 'transverse' }">
|
||||
<div class="layout-tips-box">
|
||||
<p class="layout-tips-txt">横向</p>
|
||||
</div>
|
||||
@@ -384,7 +377,8 @@
|
||||
</div>
|
||||
<!-- columns 布局 -->
|
||||
<div class="layout-drawer-content-item" @click="onSetLayout('columns')">
|
||||
<section class="el-container el-circular" :class="{ 'drawer-layout-active': getThemeConfig.layout === 'columns' }">
|
||||
<section class="el-container el-circular"
|
||||
:class="{ 'drawer-layout-active': themeConfig.layout === 'columns' }">
|
||||
<aside class="el-aside-dark" style="width: 10px"></aside>
|
||||
<aside class="el-aside" style="width: 20px"></aside>
|
||||
<section class="el-container is-vertical">
|
||||
@@ -392,7 +386,8 @@
|
||||
<main class="el-main"></main>
|
||||
</section>
|
||||
</section>
|
||||
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': getThemeConfig.layout === 'columns' }">
|
||||
<div class="layout-tips-warp"
|
||||
:class="{ 'layout-tips-warp-active': themeConfig.layout === 'columns' }">
|
||||
<div class="layout-tips-box">
|
||||
<p class="layout-tips-txt">分栏</p>
|
||||
</div>
|
||||
@@ -402,14 +397,8 @@
|
||||
<div class="copy-config">
|
||||
<el-alert title="点击下方按钮,复制布局配置去 /src/store/modules/themeConfig.ts中修改" type="warning" :closable="false">
|
||||
</el-alert>
|
||||
<el-button
|
||||
size="small"
|
||||
class="copy-config-btn"
|
||||
icon="el-icon-document-copy"
|
||||
type="primary"
|
||||
ref="copyConfigBtnRef"
|
||||
@click="onCopyConfigClick($event.target)"
|
||||
>一键复制配置
|
||||
<el-button size="small" class="copy-config-btn" icon="el-icon-document-copy" type="primary"
|
||||
ref="copyConfigBtnRef" @click="onCopyConfigClick($event.target)">一键复制配置
|
||||
</el-button>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
@@ -417,26 +406,22 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, ref } from 'vue';
|
||||
<script lang="ts" setup name="layoutBreadcrumbSeting">
|
||||
import { nextTick, onUnmounted, onMounted, ref } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import ClipboardJS from 'clipboard';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { getLightColor } from '@/common/utils/theme.ts';
|
||||
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage.ts';
|
||||
export default defineComponent({
|
||||
name: 'layoutBreadcrumbSeting',
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const copyConfigBtnRef = ref();
|
||||
const store = useStore();
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
|
||||
// 1、全局主题
|
||||
const onColorPickerChange = (color: string) => {
|
||||
setPropertyFun(`--color-${color}`, getThemeConfig.value[color]);
|
||||
setPropertyFun(`--color-${color}`, themeConfig.value[color]);
|
||||
setDispatchThemeConfig();
|
||||
};
|
||||
// 1、全局主题设置函数
|
||||
@@ -448,7 +433,7 @@ export default defineComponent({
|
||||
};
|
||||
// 2、菜单 / 顶栏
|
||||
const onBgColorPickerChange = (bg: string) => {
|
||||
document.documentElement.style.setProperty(`--bg-${bg}`, getThemeConfig.value[bg]);
|
||||
document.documentElement.style.setProperty(`--bg-${bg}`, themeConfig.value[bg]);
|
||||
onTopBarGradualChange();
|
||||
onMenuBarGradualChange();
|
||||
onColumnsMenuBarGradualChange();
|
||||
@@ -456,18 +441,18 @@ export default defineComponent({
|
||||
};
|
||||
// 2、菜单 / 顶栏 --> 顶栏背景渐变
|
||||
const onTopBarGradualChange = () => {
|
||||
setGraduaFun('.layout-navbars-breadcrumb-index', getThemeConfig.value.isTopBarColorGradual, getThemeConfig.value.topBar);
|
||||
setGraduaFun('.layout-navbars-breadcrumb-index', themeConfig.value.isTopBarColorGradual, themeConfig.value.topBar);
|
||||
};
|
||||
// 2、菜单 / 顶栏 --> 菜单背景渐变
|
||||
const onMenuBarGradualChange = () => {
|
||||
setGraduaFun('.layout-container .el-aside', getThemeConfig.value.isMenuBarColorGradual, getThemeConfig.value.menuBar);
|
||||
setGraduaFun('.layout-container .el-aside', themeConfig.value.isMenuBarColorGradual, themeConfig.value.menuBar);
|
||||
};
|
||||
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
|
||||
const onColumnsMenuBarGradualChange = () => {
|
||||
setGraduaFun(
|
||||
'.layout-container .layout-columns-aside',
|
||||
getThemeConfig.value.isColumnsMenuBarColorGradual,
|
||||
getThemeConfig.value.columnsMenuBar
|
||||
themeConfig.value.isColumnsMenuBarColorGradual,
|
||||
themeConfig.value.columnsMenuBar
|
||||
);
|
||||
};
|
||||
// 2、菜单 / 顶栏 --> 背景渐变函数
|
||||
@@ -493,7 +478,7 @@ export default defineComponent({
|
||||
let elsItems = document.querySelectorAll('.el-menu-item');
|
||||
let elActive = document.querySelector('.el-menu-item.is-active');
|
||||
if (!elActive) return false;
|
||||
if (getThemeConfig.value.isMenuBarColorHighlight) {
|
||||
if (themeConfig.value.isMenuBarColorHighlight) {
|
||||
elsItems.forEach((el: any) => el.setAttribute('id', ``));
|
||||
elActive.setAttribute('id', `add-is-active`);
|
||||
setLocal('menuBarHighlightId', elActive.getAttribute('id'));
|
||||
@@ -511,43 +496,43 @@ export default defineComponent({
|
||||
};
|
||||
// 3、界面设置 --> 固定 Header
|
||||
const onIsFixedHeaderChange = () => {
|
||||
getThemeConfig.value.isFixedHeaderChange = getThemeConfig.value.isFixedHeader ? false : true;
|
||||
themeConfig.value.isFixedHeaderChange = themeConfig.value.isFixedHeader ? false : true;
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 3、界面设置 --> 经典布局分割菜单
|
||||
const onClassicSplitMenuChange = () => {
|
||||
getThemeConfig.value.isBreadcrumb = false;
|
||||
themeConfig.value.isBreadcrumb = false;
|
||||
setLocalThemeConfig();
|
||||
proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
|
||||
mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
|
||||
};
|
||||
// 4、界面显示 --> 侧边栏 Logo
|
||||
const onIsShowLogoChange = () => {
|
||||
getThemeConfig.value.isShowLogoChange = getThemeConfig.value.isShowLogo ? false : true;
|
||||
themeConfig.value.isShowLogoChange = themeConfig.value.isShowLogo ? false : true;
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 4、界面显示 --> 面包屑 Breadcrumb
|
||||
const onIsBreadcrumbChange = () => {
|
||||
if (getThemeConfig.value.layout === 'classic') {
|
||||
getThemeConfig.value.isClassicSplitMenu = false;
|
||||
if (themeConfig.value.layout === 'classic') {
|
||||
themeConfig.value.isClassicSplitMenu = false;
|
||||
}
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 4、界面显示 --> 开启 TagsView 拖拽
|
||||
const onSortableTagsViewChange = () => {
|
||||
proxy.mittBus.emit('openOrCloseSortable');
|
||||
mittBus.emit('openOrCloseSortable');
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 4、界面显示 --> 灰色模式/色弱模式
|
||||
const onAddFilterChange = (attr: string) => {
|
||||
if (attr === 'grayscale') {
|
||||
if (getThemeConfig.value.isGrayscale) getThemeConfig.value.isInvert = false;
|
||||
if (themeConfig.value.isGrayscale) themeConfig.value.isInvert = false;
|
||||
} else {
|
||||
if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
|
||||
if (themeConfig.value.isInvert) themeConfig.value.isGrayscale = false;
|
||||
}
|
||||
const cssAttr =
|
||||
attr === 'grayscale'
|
||||
? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})`
|
||||
: `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
|
||||
? `grayscale(${themeConfig.value.isGrayscale ? 1 : 0})`
|
||||
: `invert(${themeConfig.value.isInvert ? '80%' : '0%'})`;
|
||||
const appEle: any = document.querySelector('#app');
|
||||
appEle.setAttribute('style', `filter: ${cssAttr}`);
|
||||
setLocalThemeConfig();
|
||||
@@ -556,55 +541,55 @@ export default defineComponent({
|
||||
// 5、布局切换
|
||||
const onSetLayout = (layout: string) => {
|
||||
setLocal('oldLayout', layout);
|
||||
if (getThemeConfig.value.layout === layout) return false;
|
||||
getThemeConfig.value.layout = layout;
|
||||
getThemeConfig.value.isDrawer = false;
|
||||
if (themeConfig.value.layout === layout) return false;
|
||||
themeConfig.value.layout = layout;
|
||||
themeConfig.value.isDrawer = false;
|
||||
initSetLayoutChange();
|
||||
onMenuBarHighlightChange();
|
||||
};
|
||||
// 设置布局切换,重置主题样式
|
||||
const initSetLayoutChange = () => {
|
||||
if (getThemeConfig.value.layout === 'classic') {
|
||||
getThemeConfig.value.isShowLogo = true;
|
||||
getThemeConfig.value.isBreadcrumb = true;
|
||||
getThemeConfig.value.isCollapse = false;
|
||||
getThemeConfig.value.isClassicSplitMenu = false;
|
||||
getThemeConfig.value.menuBar = '#FFFFFF';
|
||||
getThemeConfig.value.menuBarColor = '#606266';
|
||||
getThemeConfig.value.topBar = '#ffffff';
|
||||
getThemeConfig.value.topBarColor = '#606266';
|
||||
if (themeConfig.value.layout === 'classic') {
|
||||
themeConfig.value.isShowLogo = true;
|
||||
themeConfig.value.isBreadcrumb = true;
|
||||
themeConfig.value.isCollapse = false;
|
||||
themeConfig.value.isClassicSplitMenu = false;
|
||||
themeConfig.value.menuBar = '#FFFFFF';
|
||||
themeConfig.value.menuBarColor = '#606266';
|
||||
themeConfig.value.topBar = '#ffffff';
|
||||
themeConfig.value.topBarColor = '#606266';
|
||||
initLayoutChangeFun();
|
||||
} else if (getThemeConfig.value.layout === 'transverse') {
|
||||
getThemeConfig.value.isShowLogo = true;
|
||||
getThemeConfig.value.isBreadcrumb = false;
|
||||
getThemeConfig.value.isCollapse = false;
|
||||
getThemeConfig.value.isTagsview = false;
|
||||
getThemeConfig.value.isClassicSplitMenu = false;
|
||||
getThemeConfig.value.menuBarColor = '#FFFFFF';
|
||||
getThemeConfig.value.topBar = '#545c64';
|
||||
getThemeConfig.value.topBarColor = '#FFFFFF';
|
||||
} else if (themeConfig.value.layout === 'transverse') {
|
||||
themeConfig.value.isShowLogo = true;
|
||||
themeConfig.value.isBreadcrumb = false;
|
||||
themeConfig.value.isCollapse = false;
|
||||
themeConfig.value.isTagsview = false;
|
||||
themeConfig.value.isClassicSplitMenu = false;
|
||||
themeConfig.value.menuBarColor = '#FFFFFF';
|
||||
themeConfig.value.topBar = '#545c64';
|
||||
themeConfig.value.topBarColor = '#FFFFFF';
|
||||
initLayoutChangeFun();
|
||||
} else if (getThemeConfig.value.layout === 'columns') {
|
||||
getThemeConfig.value.isShowLogo = true;
|
||||
getThemeConfig.value.isBreadcrumb = true;
|
||||
getThemeConfig.value.isCollapse = false;
|
||||
getThemeConfig.value.isTagsview = true;
|
||||
getThemeConfig.value.isClassicSplitMenu = false;
|
||||
getThemeConfig.value.menuBar = '#FFFFFF';
|
||||
getThemeConfig.value.menuBarColor = '#606266';
|
||||
getThemeConfig.value.topBar = '#ffffff';
|
||||
getThemeConfig.value.topBarColor = '#606266';
|
||||
} else if (themeConfig.value.layout === 'columns') {
|
||||
themeConfig.value.isShowLogo = true;
|
||||
themeConfig.value.isBreadcrumb = true;
|
||||
themeConfig.value.isCollapse = false;
|
||||
themeConfig.value.isTagsview = true;
|
||||
themeConfig.value.isClassicSplitMenu = false;
|
||||
themeConfig.value.menuBar = '#FFFFFF';
|
||||
themeConfig.value.menuBarColor = '#606266';
|
||||
themeConfig.value.topBar = '#ffffff';
|
||||
themeConfig.value.topBarColor = '#606266';
|
||||
initLayoutChangeFun();
|
||||
} else {
|
||||
getThemeConfig.value.isShowLogo = false;
|
||||
getThemeConfig.value.isBreadcrumb = true;
|
||||
getThemeConfig.value.isCollapse = false;
|
||||
getThemeConfig.value.isTagsview = true;
|
||||
getThemeConfig.value.isClassicSplitMenu = false;
|
||||
getThemeConfig.value.menuBar = '#545c64';
|
||||
getThemeConfig.value.menuBarColor = '#eaeaea';
|
||||
getThemeConfig.value.topBar = '#FFFFFF';
|
||||
getThemeConfig.value.topBarColor = '#606266';
|
||||
themeConfig.value.isShowLogo = false;
|
||||
themeConfig.value.isBreadcrumb = true;
|
||||
themeConfig.value.isCollapse = false;
|
||||
themeConfig.value.isTagsview = true;
|
||||
themeConfig.value.isClassicSplitMenu = false;
|
||||
themeConfig.value.menuBar = '#545c64';
|
||||
themeConfig.value.menuBarColor = '#eaeaea';
|
||||
themeConfig.value.topBar = '#FFFFFF';
|
||||
themeConfig.value.topBarColor = '#606266';
|
||||
initLayoutChangeFun();
|
||||
}
|
||||
};
|
||||
@@ -617,14 +602,14 @@ export default defineComponent({
|
||||
};
|
||||
// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
|
||||
const onDrawerClose = () => {
|
||||
getThemeConfig.value.isFixedHeaderChange = false;
|
||||
getThemeConfig.value.isShowLogoChange = false;
|
||||
getThemeConfig.value.isDrawer = false;
|
||||
themeConfig.value.isFixedHeaderChange = false;
|
||||
themeConfig.value.isShowLogoChange = false;
|
||||
themeConfig.value.isDrawer = false;
|
||||
setLocalThemeConfig();
|
||||
};
|
||||
// 布局配置弹窗打开
|
||||
const openDrawer = () => {
|
||||
getThemeConfig.value.isDrawer = true;
|
||||
themeConfig.value.isDrawer = true;
|
||||
nextTick(() => {
|
||||
// 初始化复制功能,防止点击两次才可以复制
|
||||
onCopyConfigClick(copyConfigBtnRef.value.$el);
|
||||
@@ -638,7 +623,7 @@ export default defineComponent({
|
||||
// 存储布局配置
|
||||
const setLocalThemeConfig = () => {
|
||||
removeLocal('themeConfig');
|
||||
setLocal('themeConfig', getThemeConfig.value);
|
||||
setLocal('themeConfig', themeConfig.value);
|
||||
};
|
||||
// 存储布局配置全局主题样式(html根标签)
|
||||
const setLocalThemeConfigStyle = () => {
|
||||
@@ -652,7 +637,7 @@ export default defineComponent({
|
||||
text: () => JSON.stringify(copyThemeConfig),
|
||||
});
|
||||
clipboard.on('success', () => {
|
||||
getThemeConfig.value.isDrawer = false;
|
||||
themeConfig.value.isDrawer = false;
|
||||
ElMessage.success('复制成功');
|
||||
clipboard.destroy();
|
||||
});
|
||||
@@ -664,37 +649,37 @@ export default defineComponent({
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
// 监听菜单点击,菜单字体背景高亮
|
||||
proxy.mittBus.on('onMenuClick', () => {
|
||||
mittBus.on('onMenuClick', () => {
|
||||
onMenuBarHighlightChange();
|
||||
});
|
||||
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
|
||||
proxy.mittBus.on('layoutMobileResize', (res: any) => {
|
||||
getThemeConfig.value.layout = res.layout;
|
||||
getThemeConfig.value.isDrawer = false;
|
||||
mittBus.on('layoutMobileResize', (res: any) => {
|
||||
themeConfig.value.layout = res.layout;
|
||||
themeConfig.value.isDrawer = false;
|
||||
initSetLayoutChange();
|
||||
onMenuBarHighlightChange();
|
||||
getThemeConfig.value.isCollapse = false;
|
||||
themeConfig.value.isCollapse = false;
|
||||
});
|
||||
window.addEventListener('load', () => {
|
||||
// 刷新页面时,设置了值,直接取缓存中的值进行初始化
|
||||
setTimeout(() => {
|
||||
// 顶栏背景渐变
|
||||
if (getLocal('navbarsBgStyle') && getThemeConfig.value.isTopBarColorGradual) {
|
||||
if (getLocal('navbarsBgStyle') && themeConfig.value.isTopBarColorGradual) {
|
||||
const breadcrumbIndexEl: any = document.querySelector('.layout-navbars-breadcrumb-index');
|
||||
breadcrumbIndexEl.style.cssText = getLocal('navbarsBgStyle');
|
||||
}
|
||||
// 菜单背景渐变
|
||||
if (getLocal('asideBgStyle') && getThemeConfig.value.isMenuBarColorGradual) {
|
||||
if (getLocal('asideBgStyle') && themeConfig.value.isMenuBarColorGradual) {
|
||||
const asideEl: any = document.querySelector('.layout-container .el-aside');
|
||||
asideEl.style.cssText = getLocal('asideBgStyle');
|
||||
}
|
||||
// 分栏菜单背景渐变
|
||||
if (getLocal('columnsBgStyle') && getThemeConfig.value.isColumnsMenuBarColorGradual) {
|
||||
if (getLocal('columnsBgStyle') && themeConfig.value.isColumnsMenuBarColorGradual) {
|
||||
const asideEl: any = document.querySelector('.layout-container .layout-columns-aside');
|
||||
asideEl.style.cssText = getLocal('columnsBgStyle');
|
||||
}
|
||||
// 菜单字体背景高亮
|
||||
if (getLocal('menuBarHighlightId') && getThemeConfig.value.isMenuBarColorHighlight) {
|
||||
if (getLocal('menuBarHighlightId') && themeConfig.value.isMenuBarColorHighlight) {
|
||||
let els = document.querySelector('.el-menu-item.is-active');
|
||||
if (!els) return false;
|
||||
els.setAttribute('id', getLocal('menuBarHighlightId'));
|
||||
@@ -712,56 +697,39 @@ export default defineComponent({
|
||||
});
|
||||
onUnmounted(() => {
|
||||
// 取消监听菜单点击,菜单字体背景高亮
|
||||
proxy.mittBus.off('onMenuClick');
|
||||
proxy.mittBus.off('layoutMobileResize');
|
||||
});
|
||||
return {
|
||||
openDrawer,
|
||||
onColorPickerChange,
|
||||
onBgColorPickerChange,
|
||||
onTopBarGradualChange,
|
||||
onMenuBarGradualChange,
|
||||
onColumnsMenuBarGradualChange,
|
||||
onMenuBarHighlightChange,
|
||||
onThemeConfigChange,
|
||||
onIsFixedHeaderChange,
|
||||
onIsShowLogoChange,
|
||||
getThemeConfig,
|
||||
onDrawerClose,
|
||||
onAddFilterChange,
|
||||
onSetLayout,
|
||||
setLocalThemeConfig,
|
||||
onClassicSplitMenuChange,
|
||||
onIsBreadcrumbChange,
|
||||
onSortableTagsViewChange,
|
||||
copyConfigBtnRef,
|
||||
onCopyConfigClick,
|
||||
};
|
||||
},
|
||||
mittBus.off('onMenuClick');
|
||||
mittBus.off('layoutMobileResize');
|
||||
});
|
||||
|
||||
defineExpose({openDrawer})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-breadcrumb-seting-bar {
|
||||
height: calc(100vh - 50px);
|
||||
padding: 0 15px;
|
||||
|
||||
::v-deep(.el-scrollbar__view) {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.layout-breadcrumb-seting-bar-flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&-label {
|
||||
flex: 1;
|
||||
color: #666666;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-drawer-content-flex {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
margin: 0 -5px;
|
||||
|
||||
.layout-drawer-content-item {
|
||||
width: 50%;
|
||||
height: 70px;
|
||||
@@ -769,31 +737,39 @@ export default defineComponent({
|
||||
border: 1px solid transparent;
|
||||
position: relative;
|
||||
padding: 5px;
|
||||
|
||||
.el-container {
|
||||
height: 100%;
|
||||
|
||||
.el-aside-dark {
|
||||
background-color: #b3c0d1;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
|
||||
.el-header {
|
||||
background-color: #b3c0d1;
|
||||
}
|
||||
|
||||
.el-main {
|
||||
background-color: #e9eef3;
|
||||
}
|
||||
}
|
||||
|
||||
.el-circular {
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.drawer-layout-active {
|
||||
border: 1px solid;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.layout-tips-warp,
|
||||
.layout-tips-warp-active {
|
||||
transition: all 0.3s ease-in-out;
|
||||
@@ -805,6 +781,7 @@ export default defineComponent({
|
||||
border-color: var(--color-primary-light-4);
|
||||
border-radius: 100%;
|
||||
padding: 4px;
|
||||
|
||||
.layout-tips-box {
|
||||
transition: inherit;
|
||||
width: 30px;
|
||||
@@ -813,6 +790,7 @@ export default defineComponent({
|
||||
border: 1px solid;
|
||||
border-color: var(--color-primary-light-4);
|
||||
border-radius: 100%;
|
||||
|
||||
.layout-tips-txt {
|
||||
transition: inherit;
|
||||
position: relative;
|
||||
@@ -832,30 +810,37 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-tips-warp-active {
|
||||
border: 1px solid;
|
||||
border-color: var(--color-primary);
|
||||
|
||||
.layout-tips-box {
|
||||
border: 1px solid;
|
||||
border-color: var(--color-primary);
|
||||
|
||||
.layout-tips-txt {
|
||||
color: var(--color-primary) !important;
|
||||
background-color: #e9eef3 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.el-circular {
|
||||
transition: all 0.3s ease-in-out;
|
||||
border: 1px solid;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.layout-tips-warp {
|
||||
transition: all 0.3s ease-in-out;
|
||||
border-color: var(--color-primary);
|
||||
|
||||
.layout-tips-box {
|
||||
transition: inherit;
|
||||
border-color: var(--color-primary);
|
||||
|
||||
.layout-tips-txt {
|
||||
transition: inherit;
|
||||
color: var(--color-primary) !important;
|
||||
@@ -866,12 +851,15 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copy-config {
|
||||
margin: 10px 0;
|
||||
|
||||
.copy-config-btn {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.copy-config-last-btn {
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
@@ -8,44 +8,39 @@
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="" :disabled="disabledSize === ''">默认</el-dropdown-item>
|
||||
<el-dropdown-item command="large" :disabled="disabledSize === 'large'">大型</el-dropdown-item>
|
||||
<el-dropdown-item command="small" :disabled="disabledSize === 'small'">小型</el-dropdown-item>
|
||||
<el-dropdown-item command="" :disabled="state.disabledSize === ''">默认</el-dropdown-item>
|
||||
<el-dropdown-item command="large" :disabled="state.disabledSize === 'large'">大型</el-dropdown-item>
|
||||
<el-dropdown-item command="small" :disabled="state.disabledSize === 'small'">小型</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- <div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
|
||||
<el-icon title="菜单搜索">
|
||||
<search />
|
||||
</el-icon>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
|
||||
<el-icon title="布局设置">
|
||||
<setting />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon">
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
:visible="isShowUserNewsPopover"
|
||||
:width="300"
|
||||
popper-class="el-popover-pupop-user-news"
|
||||
>
|
||||
<el-popover placement="bottom" trigger="click" :visible="state.isShowUserNewsPopover" :width="300"
|
||||
popper-class="el-popover-pupop-user-news">
|
||||
<template #reference>
|
||||
<el-badge :is-dot="true" @click="isShowUserNewsPopover = !isShowUserNewsPopover">
|
||||
<el-badge :is-dot="false" @click="state.isShowUserNewsPopover = !state.isShowUserNewsPopover">
|
||||
<el-icon title="消息">
|
||||
<bell />
|
||||
</el-icon>
|
||||
</el-badge>
|
||||
</template>
|
||||
<transition name="el-zoom-in-top">
|
||||
<UserNews v-show="isShowUserNewsPopover" />
|
||||
<UserNews v-show="state.isShowUserNewsPopover" />
|
||||
</transition>
|
||||
</el-popover>
|
||||
</div>
|
||||
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
|
||||
<el-icon v-if="!isScreenfull" title="关全屏">
|
||||
<el-icon v-if="!state.isScreenfull" title="关全屏">
|
||||
<full-screen />
|
||||
</el-icon>
|
||||
<el-icon v-else title="开全屏">
|
||||
@@ -54,8 +49,8 @@
|
||||
</div>
|
||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||
<span class="layout-navbars-breadcrumb-user-link" style="cursor: pointer">
|
||||
<img :src="getUserInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
{{ getUserInfos.username === '' ? 'test' : getUserInfos.username }}
|
||||
<img :src="userInfo.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||
{{ userInfo.name || userInfo.username }}
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
@@ -70,23 +65,21 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, getCurrentInstance, computed, reactive, toRefs, onMounted } from 'vue';
|
||||
<script setup lang="ts" name="layoutBreadcrumbUser">
|
||||
import { ref, computed, reactive, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||
import screenfull from 'screenfull';
|
||||
import { resetRoute } from '@/router/index.ts';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { clearSession, setLocal, getLocal, removeLocal } from '@/common/utils/storage.ts';
|
||||
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
|
||||
import Search from '@/views/layout/navBars/breadcrumb/search.vue';
|
||||
export default {
|
||||
name: 'layoutBreadcrumbUser',
|
||||
components: { UserNews, SearchMenu: Search },
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
const searchRef = ref();
|
||||
const state = reactive({
|
||||
isScreenfull: false,
|
||||
@@ -94,17 +87,12 @@ export default {
|
||||
disabledI18n: 'zh-cn',
|
||||
disabledSize: '',
|
||||
});
|
||||
// 获取用户信息 vuex
|
||||
const getUserInfos = computed(() => {
|
||||
return store.state.userInfos.userInfos;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
const { userInfo } = storeToRefs(useUserInfo());
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
|
||||
// 设置分割样式
|
||||
const layoutUserFlexNum = computed(() => {
|
||||
let { layout, isClassicSplitMenu } = getThemeConfig.value;
|
||||
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||
let num = '';
|
||||
if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = '1';
|
||||
else num = '';
|
||||
@@ -121,7 +109,7 @@ export default {
|
||||
};
|
||||
// 布局配置 icon 点击时
|
||||
const onLayoutSetingClick = () => {
|
||||
proxy.mittBus.emit('openSetingsDrawer');
|
||||
mittBus.emit('openSetingsDrawer');
|
||||
};
|
||||
// 下拉菜单点击时
|
||||
const onHandleCommandClick = (path: string) => {
|
||||
@@ -162,19 +150,22 @@ export default {
|
||||
router.push(path);
|
||||
}
|
||||
};
|
||||
// 菜单搜索点击
|
||||
|
||||
// // 菜单搜索点击
|
||||
const onSearchClick = () => {
|
||||
searchRef.value.openSearch();
|
||||
};
|
||||
|
||||
// 组件大小改变
|
||||
const onComponentSizeChange = (size: string) => {
|
||||
removeLocal('themeConfig');
|
||||
getThemeConfig.value.globalComponentSize = size;
|
||||
setLocal('themeConfig', getThemeConfig.value);
|
||||
themeConfig.value.globalComponentSize = size;
|
||||
setLocal('themeConfig', themeConfig.value);
|
||||
// proxy.$ELEMENT.size = size;
|
||||
initComponentSize();
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
// 初始化全局组件大小
|
||||
const initComponentSize = () => {
|
||||
switch (getLocal('themeConfig').globalComponentSize) {
|
||||
@@ -192,25 +183,13 @@ export default {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
if (getLocal('themeConfig')) {
|
||||
initComponentSize();
|
||||
}
|
||||
});
|
||||
return {
|
||||
getUserInfos,
|
||||
onLayoutSetingClick,
|
||||
onHandleCommandClick,
|
||||
onScreenfullClick,
|
||||
onSearchClick,
|
||||
onComponentSizeChange,
|
||||
searchRef,
|
||||
layoutUserFlexNum,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -218,17 +197,20 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
&-link {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
|
||||
&-photo {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-icon {
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
@@ -237,25 +219,29 @@ export default {
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
|
||||
i {
|
||||
display: inline-block;
|
||||
animation: logoAnimation 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.el-dropdown) {
|
||||
color: var(--bg-topBarColor);
|
||||
}
|
||||
|
||||
::v-deep(.el-badge) {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
::v-deep(.el-badge__content.is-fixed) {
|
||||
top: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
}</style>
|
||||
|
||||
@@ -40,7 +40,7 @@ export default {
|
||||
};
|
||||
// 前往通知中心点击
|
||||
const toMsgCenter = () => {
|
||||
// window.open('https://gitee.com/lyt-top/vue-next-admin');
|
||||
|
||||
};
|
||||
return {
|
||||
onAllReadClick,
|
||||
|
||||
@@ -7,17 +7,16 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import BreadcrumbIndex from '@/views/layout/navBars/breadcrumb/index.vue';
|
||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
||||
export default {
|
||||
name: 'layoutNavBars',
|
||||
components: { BreadcrumbIndex, TagsView },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
// 是否显示 tagsView
|
||||
const setShowTagsView = computed(() => {
|
||||
let { layout, isTagsview } = store.state.themeConfig.themeConfig;
|
||||
let { layout, isTagsview } = useThemeConfig().themeConfig;
|
||||
return layout !== 'classic' && isTagsview;
|
||||
});
|
||||
return {
|
||||
|
||||
@@ -7,66 +7,86 @@
|
||||
data-popper-placement="bottom"
|
||||
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
||||
:key="Math.random()"
|
||||
v-show="isShow"
|
||||
v-show="state.isShow"
|
||||
>
|
||||
<ul class="el-dropdown-menu">
|
||||
<template v-for="(v, k) in dropdownList">
|
||||
<template v-for="(v, k) in state.dropdownList">
|
||||
<li
|
||||
class="el-dropdown-menu__item"
|
||||
aria-disabled="false"
|
||||
tabindex="-1"
|
||||
:key="k"
|
||||
v-if="!v.affix"
|
||||
@click="onCurrentContextmenuClick(v.id)"
|
||||
@click="onCurrentContextmenuClick(v.contextMenuClickId)"
|
||||
>
|
||||
<i :class="v.icon"></i>
|
||||
<SvgIcon :name="v.icon" />
|
||||
<span>{{ v.txt }}</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<div class="el-popper__arrow" style="left: 10px"></div>
|
||||
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted } from 'vue';
|
||||
export default defineComponent({
|
||||
name: 'layoutTagsViewContextmenu',
|
||||
props: {
|
||||
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
dropdown: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
});
|
||||
|
||||
// 定义子组件向父组件传值/事件
|
||||
const emit = defineEmits(['currentContextmenuClick']);
|
||||
|
||||
// 定义变量内容
|
||||
const state = reactive({
|
||||
isShow: false,
|
||||
dropdownList: [
|
||||
{ id: 0, txt: '刷新', affix: false, icon: 'el-icon-refresh-right' },
|
||||
{ id: 1, txt: '关闭', affix: false, icon: 'el-icon-close' },
|
||||
{ id: 2, txt: '关闭其他', affix: false, icon: 'el-icon-circle-close' },
|
||||
{ id: 3, txt: '关闭所有', affix: false, icon: 'el-icon-folder-delete' },
|
||||
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
|
||||
{ contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'Close' },
|
||||
{ contextMenuClickId: 2, txt: '关闭其他', affix: false, icon: 'CircleClose' },
|
||||
{ contextMenuClickId: 3, txt: '关闭所有', affix: false, icon: 'FolderDelete' },
|
||||
{
|
||||
id: 4,
|
||||
contextMenuClickId: 4,
|
||||
txt: '当前页全屏',
|
||||
affix: false,
|
||||
icon: 'el-icon-full-screen',
|
||||
icon: 'full-screen',
|
||||
},
|
||||
],
|
||||
path: {},
|
||||
item: {} as any,
|
||||
arrowLeft: 10,
|
||||
});
|
||||
|
||||
// 父级传过来的坐标 x,y 值
|
||||
const dropdowns = computed(() => {
|
||||
// 117 为 `Dropdown 下拉菜单` 的宽度
|
||||
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
|
||||
return {
|
||||
x: document.documentElement.clientWidth - 117 - 5,
|
||||
y: props.dropdown.y,
|
||||
};
|
||||
} else {
|
||||
return props.dropdown;
|
||||
}
|
||||
});
|
||||
// 当前项菜单点击
|
||||
const onCurrentContextmenuClick = (id: number) => {
|
||||
emit('currentContextmenuClick', { id, path: state.path });
|
||||
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
|
||||
emit('currentContextmenuClick', { id: contextMenuClickId, path: state.item.fullPath });
|
||||
};
|
||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||
const openContextmenu = (item: any) => {
|
||||
state.path = item.fullPath;
|
||||
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
|
||||
state.item = item;
|
||||
item.meta?.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
|
||||
closeContextmenu();
|
||||
setTimeout(() => {
|
||||
state.isShow = true;
|
||||
@@ -84,14 +104,21 @@ export default defineComponent({
|
||||
onUnmounted(() => {
|
||||
document.body.removeEventListener('click', closeContextmenu);
|
||||
});
|
||||
return {
|
||||
dropdowns,
|
||||
openContextmenu,
|
||||
closeContextmenu,
|
||||
onCurrentContextmenuClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
// 监听下拉菜单位置
|
||||
watch(
|
||||
() => props.dropdown,
|
||||
({ x }) => {
|
||||
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
|
||||
else state.arrowLeft = 10;
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openContextmenu,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -102,6 +129,7 @@ export default defineComponent({
|
||||
position: fixed;
|
||||
.el-dropdown-menu__item {
|
||||
font-size: 12px !important;
|
||||
white-space: nowrap;
|
||||
i {
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
@@ -1,108 +1,90 @@
|
||||
<template>
|
||||
<div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': getThemeConfig.layout === 'classic' }">
|
||||
<div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': themeConfig.layout === 'classic' }">
|
||||
<el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
|
||||
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
|
||||
<li
|
||||
v-for="(v, k) in tagsViewList"
|
||||
:key="k"
|
||||
class="layout-navbars-tagsview-ul-li"
|
||||
:data-name="v.name"
|
||||
:class="{ 'is-active': isActive(v) }"
|
||||
@contextmenu.prevent="onContextmenu(v, $event)"
|
||||
@click="onTagsClick(v, k)"
|
||||
:ref="
|
||||
<li v-for="(v, k) in state.tagsViewList" :key="k" class="layout-navbars-tagsview-ul-li" :data-name="v.name"
|
||||
:class="{ 'is-active': isActive(v) }" @contextmenu.prevent="onContextmenu(v, $event)"
|
||||
@click="onTagsClick(v, k)" :ref="
|
||||
(el) => {
|
||||
if (el) tagsRefs[k] = el;
|
||||
}
|
||||
"
|
||||
>
|
||||
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)"></i>
|
||||
<SvgIcon
|
||||
:name="v.meta.icon"
|
||||
class="layout-navbars-tagsview-ul-li-iconfont"
|
||||
v-if="!isActive(v) && getThemeConfig.isTagsviewIcon"
|
||||
/>
|
||||
">
|
||||
<SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14"
|
||||
v-if="isActive(v)" />
|
||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont"
|
||||
v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
|
||||
<span>{{ v.meta.title }}</span>
|
||||
<template v-if="isActive(v)">
|
||||
<SvgIcon
|
||||
name="RefreshRight"
|
||||
class="ml5 layout-navbars-tagsview-ul-li-refresh"
|
||||
@click.stop="refreshCurrentTagsView($route.fullPath)"
|
||||
/>
|
||||
<SvgIcon
|
||||
name="Close"
|
||||
class="layout-navbars-tagsview-ul-li-icon layout-icon-active"
|
||||
<SvgIcon name="RefreshRight" class="font14 ml5 layout-navbars-tagsview-ul-li-refresh"
|
||||
@click.stop="refreshCurrentTagsView($route.fullPath)" />
|
||||
<SvgIcon name="Close" class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-active"
|
||||
v-if="!v.meta.isAffix"
|
||||
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.path)"
|
||||
/>
|
||||
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)" />
|
||||
</template>
|
||||
<SvgIcon
|
||||
name="Close"
|
||||
class="layout-navbars-tagsview-ul-li-icon layout-icon-three"
|
||||
|
||||
<SvgIcon name="Close" class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-three"
|
||||
v-if="!v.meta.isAffix"
|
||||
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.path)"
|
||||
/>
|
||||
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)" />
|
||||
</li>
|
||||
</ul>
|
||||
</el-scrollbar>
|
||||
<Contextmenu :dropdown="dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
|
||||
<Contextmenu :dropdown="state.dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
|
||||
<script lang="ts" setup name="layoutTagsView">
|
||||
import { reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
|
||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||
import screenfull from 'screenfull';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { setSession, getSession, removeSession } from '@/common/utils/storage.ts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { getSession, setSession, removeSession } from '@/common/utils/storage';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
import Sortable from 'sortablejs';
|
||||
import Contextmenu from '@/views/layout/navBars/tagsView/contextmenu.vue';
|
||||
export default {
|
||||
name: 'layoutTagsView',
|
||||
components: { Contextmenu },
|
||||
setup() {
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const tagsRefs = ref([]);
|
||||
const tagsRefs = ref([]) as any;
|
||||
const scrollbarRef = ref();
|
||||
const contextmenuRef = ref();
|
||||
const tagsUlRef = ref();
|
||||
const store = useStore();
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const state: any = reactive({
|
||||
|
||||
const state = reactive({
|
||||
routePath: route.fullPath,
|
||||
dropdown: { x: '', y: '' },
|
||||
tagsRefsIndex: 0,
|
||||
tagsViewList: [],
|
||||
sortable: '',
|
||||
tagsViewList: [] as any,
|
||||
sortable: '' as any,
|
||||
});
|
||||
|
||||
// 动态设置 tagsView 风格样式
|
||||
const setTagsStyle = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.tagsStyle;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
return themeConfig.value.tagsStyle;
|
||||
});
|
||||
|
||||
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
||||
const addBrowserSetSession = (tagsViewList: Array<object>) => {
|
||||
setSession('tagsViewList', tagsViewList);
|
||||
};
|
||||
|
||||
// 获取 vuex 中的 tagsViewRoutes 列表
|
||||
const getTagsViewRoutes = () => {
|
||||
state.routePath = route.fullPath;
|
||||
state.tagsViewList = [];
|
||||
if (!store.state.themeConfig.themeConfig.isCacheTagsView) removeSession('tagsViewList');
|
||||
if (!themeConfig.value.isCacheTagsView) removeSession('tagsViewList');
|
||||
initTagsView();
|
||||
};
|
||||
// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
||||
const initTagsView = () => {
|
||||
if (getSession('tagsViewList') && store.state.themeConfig.themeConfig.isCacheTagsView) {
|
||||
if (getSession('tagsViewList') && themeConfig.value.isCacheTagsView) {
|
||||
state.tagsViewList = getSession('tagsViewList');
|
||||
} else {
|
||||
// state.tagsViews.map((v: any) => {
|
||||
// if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
||||
// });
|
||||
state.tagsViewList?.map((v: any) => {
|
||||
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
||||
});
|
||||
addTagsView(route.fullPath);
|
||||
}
|
||||
// 初始化当前元素(li)的下标
|
||||
@@ -117,40 +99,57 @@ export default {
|
||||
if (!to) {
|
||||
to = route;
|
||||
}
|
||||
|
||||
path = decodeURI(path);
|
||||
for (let tv of state.tagsViewList) {
|
||||
if (tv.fullPath === path) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
state.tagsViewList.push({ ...to });
|
||||
// addBrowserSetSession(state.tagsViewList);
|
||||
|
||||
const tagView = { ...to }
|
||||
// 防止Converting circular structure to JSON错误
|
||||
tagView.matched = null;
|
||||
tagView.redirectedFrom = null;
|
||||
state.tagsViewList.push(tagView);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
};
|
||||
|
||||
// 2、刷新当前 tagsView:
|
||||
// path为fullPath
|
||||
const refreshCurrentTagsView = (path: string) => {
|
||||
proxy.mittBus.emit('onTagsViewRefreshRouterView', path);
|
||||
mittBus.emit('onTagsViewRefreshRouterView', path);
|
||||
};
|
||||
|
||||
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
|
||||
// path为fullPath
|
||||
const closeCurrentTagsView = (path: string) => {
|
||||
console.log(path)
|
||||
state.tagsViewList.map((v: any, k: number, arr: any) => {
|
||||
if (!v.meta.isAffix) {
|
||||
if (v.fullPath === path) {
|
||||
state.tagsViewList.splice(k, 1);
|
||||
setTimeout(() => {
|
||||
// 最后一个
|
||||
if (state.tagsViewList.length === k) router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
|
||||
// 否则,跳转到下一个
|
||||
else router.push({ path: arr[k].path, query: arr[k].query });
|
||||
if (state.routePath !== path) {
|
||||
return;
|
||||
}
|
||||
let next;
|
||||
// 最后一个且高亮时
|
||||
if (state.tagsViewList.length === k) {
|
||||
next = k !== arr.length ? arr[k] : arr[arr.length - 1]
|
||||
} else {
|
||||
next = arr[k];
|
||||
}
|
||||
|
||||
if (next.meta.isDynamic) {
|
||||
router.push({ name: next.name, params: next.params });
|
||||
} else {
|
||||
router.push({ path: next.path, query: next.query });
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
// addBrowserSetSession(state.tagsViewList);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
};
|
||||
|
||||
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||
@@ -174,7 +173,7 @@ export default {
|
||||
else router.push({ path: v.path, query: route.query });
|
||||
}
|
||||
});
|
||||
// addBrowserSetSession(state.tagsViewList);
|
||||
addBrowserSetSession(state.tagsViewList);
|
||||
};
|
||||
// 6、开启当前页面全屏
|
||||
const openCurrenFullscreen = (path: string) => {
|
||||
@@ -234,7 +233,7 @@ export default {
|
||||
};
|
||||
// 鼠标滚轮滚动
|
||||
const onHandleScroll = (e: any) => {
|
||||
proxy.$refs.scrollbarRef.$refs.wrap.scrollLeft += e.wheelDelta / 4;
|
||||
proxy.$refs.scrollbarRef.$refs.wrapRef.scrollLeft += e.wheelDelta / 4;
|
||||
};
|
||||
// tagsView 横向滚动
|
||||
const tagsViewmoveToCurrentTag = () => {
|
||||
@@ -251,7 +250,7 @@ export default {
|
||||
// 最后 li
|
||||
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
|
||||
// 当前滚动条的值
|
||||
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
|
||||
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrapRef;
|
||||
// 当前滚动条滚动宽度
|
||||
let scrollS = scrollRefs.scrollWidth;
|
||||
// 当前滚动条偏移宽度
|
||||
@@ -298,8 +297,8 @@ export default {
|
||||
const initSortable = () => {
|
||||
const el: any = document.querySelector('.layout-navbars-tagsview-ul');
|
||||
if (!el) return false;
|
||||
if (!getThemeConfig.value.isSortableTagsView) state.sortable && state.sortable.destroy();
|
||||
if (getThemeConfig.value.isSortableTagsView) {
|
||||
if (!themeConfig.value.isSortableTagsView) state.sortable && state.sortable.destroy();
|
||||
if (themeConfig.value.isSortableTagsView) {
|
||||
state.sortable = Sortable.create(el, {
|
||||
animation: 300,
|
||||
dataIdAttr: 'data-name',
|
||||
@@ -310,33 +309,41 @@ export default {
|
||||
if (v.name === val) sortEndList.push({ ...v });
|
||||
});
|
||||
});
|
||||
// addBrowserSetSession(sortEndList);
|
||||
addBrowserSetSession(sortEndList);
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 监听路由的变化,动态赋值给 tagsView
|
||||
// watch(store.state, (val) => {
|
||||
// if (val.tagsViews.tagsViews.length === state.tagsViewList.length) return false;
|
||||
// watch(
|
||||
// pinia.state,
|
||||
// (val) => {
|
||||
// if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
|
||||
// getTagsViewRoutes();
|
||||
// });
|
||||
// },
|
||||
// {
|
||||
// deep: true,
|
||||
// }
|
||||
// );
|
||||
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
|
||||
proxy.mittBus.on('onCurrentContextmenuClick', (data: object) => {
|
||||
mittBus.on('onCurrentContextmenuClick', (data: object) => {
|
||||
onCurrentContextmenuClick(data);
|
||||
});
|
||||
// 监听布局配置界面开启/关闭拖拽
|
||||
proxy.mittBus.on('openOrCloseSortable', () => {
|
||||
mittBus.on('openOrCloseSortable', () => {
|
||||
initSortable();
|
||||
});
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
// 取消非本页面调用监听
|
||||
proxy.mittBus.off('onCurrentContextmenuClick');
|
||||
mittBus.off('onCurrentContextmenuClick');
|
||||
// 取消监听布局配置界面开启/关闭拖拽
|
||||
proxy.mittBus.off('openOrCloseSortable');
|
||||
mittBus.off('openOrCloseSortable');
|
||||
});
|
||||
// 页面更新时
|
||||
onBeforeUpdate(() => {
|
||||
@@ -355,35 +362,19 @@ export default {
|
||||
getTagsRefsIndex(to.fullPath);
|
||||
tagsViewmoveToCurrentTag();
|
||||
});
|
||||
return {
|
||||
isActive,
|
||||
onContextmenu,
|
||||
getTagsViewRoutes,
|
||||
onTagsClick,
|
||||
tagsRefs,
|
||||
contextmenuRef,
|
||||
scrollbarRef,
|
||||
tagsUlRef,
|
||||
onHandleScroll,
|
||||
getThemeConfig,
|
||||
setTagsStyle,
|
||||
refreshCurrentTagsView,
|
||||
closeCurrentTagsView,
|
||||
onCurrentContextmenuClick,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.layout-navbars-tagsview {
|
||||
flex: 1;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #f1f2f3;
|
||||
::v-deep(.el-scrollbar__wrap) {
|
||||
background-color: var(--el-color-white);
|
||||
border-bottom: 1px solid var(--next-border-color-light);
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
&-ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
@@ -391,16 +382,17 @@ export default {
|
||||
height: 34px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #606266;
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
padding: 0 15px;
|
||||
|
||||
&-li {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid #e6e6e6;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
padding: 0 15px;
|
||||
margin-right: 5px;
|
||||
border-radius: 2px;
|
||||
@@ -408,16 +400,19 @@ export default {
|
||||
z-index: 0;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-primary-light-9);
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary-light-6);
|
||||
background-color: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
}
|
||||
|
||||
&-iconfont {
|
||||
position: relative;
|
||||
left: -5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-icon {
|
||||
border-radius: 100%;
|
||||
position: relative;
|
||||
@@ -426,99 +421,101 @@ export default {
|
||||
text-align: center;
|
||||
line-height: 14px;
|
||||
right: -5px;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: var(--color-primary-light-3);
|
||||
color: var(--el-color-white);
|
||||
background-color: var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
|
||||
.layout-icon-active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.layout-icon-three {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
color: #ffffff;
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--el-color-white);
|
||||
background: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
transition: border-color 3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
// 风格2
|
||||
.tags-style-two {
|
||||
.layout-navbars-tagsview-ul-li {
|
||||
height: 34px !important;
|
||||
line-height: 34px !important;
|
||||
border: none !important;
|
||||
.layout-navbars-tagsview-ul-li-iconfont {
|
||||
display: none;
|
||||
}
|
||||
.layout-icon-active {
|
||||
display: none;
|
||||
}
|
||||
.layout-icon-three {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
background: none !important;
|
||||
color: var(--color-primary) !important;
|
||||
border-bottom: 2px solid !important;
|
||||
border-color: var(--color-primary) !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
}
|
||||
// 风格3
|
||||
.tags-style-three {
|
||||
.layout-navbars-tagsview-ul-li {
|
||||
height: 34px !important;
|
||||
line-height: 34px !important;
|
||||
border-right: 1px solid #f6f6f6 !important;
|
||||
border-top: none !important;
|
||||
border-bottom: none !important;
|
||||
border-left: none !important;
|
||||
border-radius: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
&:first-of-type {
|
||||
border-left: 1px solid #f6f6f6 !important;
|
||||
}
|
||||
.layout-icon-active {
|
||||
display: none;
|
||||
}
|
||||
.layout-icon-three {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
background: white !important;
|
||||
color: var(--color-primary) !important;
|
||||
border-top: 1px solid !important;
|
||||
border-top-color: var(--color-primary) !important;
|
||||
}
|
||||
}
|
||||
// 风格4
|
||||
.tags-style-four {
|
||||
.layout-navbars-tagsview-ul-li {
|
||||
margin-right: 0 !important;
|
||||
border: none !important;
|
||||
position: relative;
|
||||
border-radius: 3px !important;
|
||||
|
||||
.layout-icon-active {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-icon-three {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
background: none !important;
|
||||
color: var(--color-primary) !important;
|
||||
color: var(--el-color-primary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 风格3
|
||||
.tags-style-three {
|
||||
align-items: flex-end;
|
||||
|
||||
.tgs-style-three-svg {
|
||||
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIHRyYW5zZm9ybT0icm90YXRlKC0wLjEzMzUwNiA1MC4xMTkyIDUwKSIgaWQ9InN2Z18xIiBkPSJtMTAwLjExOTE5LDEwMGMtNTUuMjI4LDAgLTEwMCwtNDQuNzcyIC0xMDAsLTEwMGwwLDEwMGwxMDAsMHoiIG9wYWNpdHk9InVuZGVmaW5lZCIgc3Ryb2tlPSJudWxsIiBmaWxsPSIjRjhFQUU3Ii8+CiAgPHBhdGggZD0ibS0wLjYzNzY2LDcuMzEyMjhjMC4xMTkxOSwwIDAuMjE3MzcsMC4wNTc5NiAwLjQ3Njc2LDAuMTE5MTljMC4yMzIsMC4wNTQ3NyAwLjI3MzI5LDAuMDM0OTEgMC4zNTc1NywwLjExOTE5YzAuMDg0MjgsMC4wODQyOCAwLjM1NzU3LDAgMC40NzY3NiwwbDAuMTE5MTksMGwwLjIzODM4LDAiIGlkPSJzdmdfMiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHBhdGggZD0ibTI4LjkyMTM0LDY5LjA1MjQ0YzAsMC4xMTkxOSAwLDAuMjM4MzggMCwwLjM1NzU3bDAsMC4xMTkxOWwwLDAuMTE5MTkiIGlkPSJzdmdfMyIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z180IiBoZWlnaHQ9IjAiIHdpZHRoPSIxLjMxMTA4IiB5PSI2LjgzNTUyIiB4PSItMC4wNDE3MSIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z181IiBoZWlnaHQ9IjEuNzg3ODQiIHdpZHRoPSIwLjExOTE5IiB5PSI2OC40NTY1IiB4PSIyOC45MjEzNCIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z182IiBoZWlnaHQ9IjQuODg2NzciIHdpZHRoPSIxOS4wNzAzMiIgeT0iNTEuMjkzMjEiIHg9IjM2LjY2ODY2IiBzdHJva2U9Im51bGwiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+'),
|
||||
url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CiA8Zz4KICA8dGl0bGU+TGF5ZXIgMTwvdGl0bGU+CiAgPHBhdGggdHJhbnNmb3JtPSJyb3RhdGUoLTg5Ljc2MjQgNy4zMzAxNCA1NS4xMjUyKSIgc3Ryb2tlPSJudWxsIiBpZD0ic3ZnXzEiIGZpbGw9IiNGOEVBRTciIGQ9Im02Mi41NzQ0OSwxMTcuNTIwODZjLTU1LjIyOCwwIC0xMDAsLTQ0Ljc3MiAtMTAwLC0xMDBsMCwxMDBsMTAwLDB6IiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPgogIDxwYXRoIGQ9Im0tMC42Mzc2Niw3LjMxMjI4YzAuMTE5MTksMCAwLjIxNzM3LDAuMDU3OTYgMC40NzY3NiwwLjExOTE5YzAuMjMyLDAuMDU0NzcgMC4yNzMyOSwwLjAzNDkxIDAuMzU3NTcsMC4xMTkxOWMwLjA4NDI4LDAuMDg0MjggMC4zNTc1NywwIDAuNDc2NzYsMGwwLjExOTE5LDBsMC4yMzgzOCwwIiBpZD0ic3ZnXzIiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxwYXRoIGQ9Im0yOC45MjEzNCw2OS4wNTI0NGMwLDAuMTE5MTkgMCwwLjIzODM4IDAsMC4zNTc1N2wwLDAuMTE5MTlsMCwwLjExOTE5IiBpZD0ic3ZnXzMiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNCIgaGVpZ2h0PSIwIiB3aWR0aD0iMS4zMTEwOCIgeT0iNi44MzU1MiIgeD0iLTAuMDQxNzEiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNSIgaGVpZ2h0PSIxLjc4Nzg0IiB3aWR0aD0iMC4xMTkxOSIgeT0iNjguNDU2NSIgeD0iMjguOTIxMzQiIHN0cm9rZT0ibnVsbCIgZmlsbD0ibm9uZSIvPgogIDxyZWN0IGlkPSJzdmdfNiIgaGVpZ2h0PSI0Ljg4Njc3IiB3aWR0aD0iMTkuMDcwMzIiIHk9IjUxLjI5MzIxIiB4PSIzNi42Njg2NiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiA8L2c+Cjwvc3ZnPg=='),
|
||||
url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><rect rx='8' width='100%' height='100%' fill='%23F8EAE7'/></svg>");
|
||||
-webkit-mask-size: 18px 30px, 20px 30px, calc(100% - 30px) calc(100% + 17px);
|
||||
-webkit-mask-position: right bottom, left bottom, center top;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.layout-navbars-tagsview-ul-li {
|
||||
padding: 0 5px;
|
||||
border-width: 15px 27px 15px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
margin: 0 -15px;
|
||||
|
||||
.layout-icon-active {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.layout-icon-three {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@extend .tgs-style-three-svg;
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.is-active {
|
||||
@extend .tgs-style-three-svg;
|
||||
background: var(--el-color-primary-light-9) !important;
|
||||
color: var(--el-color-primary) !important;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-navbars-tagsview-shadow {
|
||||
box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
<template>
|
||||
<div class="el-menu-horizontal-warp">
|
||||
<el-scrollbar @wheel.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
|
||||
<el-menu router :default-active="defaultActive" background-color="transparent" mode="horizontal" @select="onHorizontalSelect">
|
||||
<el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal"
|
||||
@select="onHorizontalSelect">
|
||||
<template v-for="val in menuLists">
|
||||
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<span>{{ val.meta.title }}</span>
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-submenu>
|
||||
<el-menu-item :index="val.path" :key="val.path" v-else>
|
||||
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
|
||||
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
|
||||
</el-sub-menu>
|
||||
<el-menu-item :index="val.path" :key="val?.path" v-else>
|
||||
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
{{ val.meta.title }}
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a :href="val.meta.link" target="_blank">
|
||||
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
{{ val.meta.title }}
|
||||
</a>
|
||||
</template>
|
||||
@@ -28,24 +29,25 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick } from 'vue';
|
||||
<script lang="ts" setup name="navMenuHorizontal">
|
||||
import { reactive, computed, getCurrentInstance, onMounted, nextTick } from 'vue';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
||||
export default defineComponent({
|
||||
name: 'navMenuHorizontal',
|
||||
components: { SubItem },
|
||||
props: {
|
||||
import { useRoutesList } from '@/store/routesList';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
// 菜单列表
|
||||
menuList: {
|
||||
type: Array,
|
||||
type: Array<any>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const state: any = reactive({
|
||||
defaultActive: null,
|
||||
});
|
||||
@@ -56,21 +58,21 @@ export default defineComponent({
|
||||
// 设置横向滚动条可以鼠标滚轮滚动
|
||||
const onElMenuHorizontalScroll = (e: any) => {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40;
|
||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft =
|
||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft + eventDelta / 4;
|
||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft =
|
||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft + eventDelta / 4;
|
||||
};
|
||||
// 初始化数据,页面刷新时,滚动条滚动到对应位置
|
||||
const initElMenuOffsetLeft = () => {
|
||||
nextTick(() => {
|
||||
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
|
||||
if (!els) return false;
|
||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
|
||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft = els.offsetLeft;
|
||||
});
|
||||
};
|
||||
// 设置页面当前路由高亮
|
||||
const setCurrentRouterHighlight = (path: string) => {
|
||||
const currentPathSplit = path.split('/');
|
||||
if (store.state.themeConfig.themeConfig.layout === 'classic') {
|
||||
if (useThemeConfig().themeConfig.layout === 'classic') {
|
||||
state.defaultActive = `/${currentPathSplit[1]}`;
|
||||
} else {
|
||||
state.defaultActive = path;
|
||||
@@ -90,7 +92,7 @@ export default defineComponent({
|
||||
const setSendClassicChildren = (path: string) => {
|
||||
const currentPathSplit = path.split('/');
|
||||
let currentData: any = {};
|
||||
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
|
||||
filterRoutesFun(useRoutesList().routesList).map((v, k) => {
|
||||
if (v.path === `/${currentPathSplit[1]}`) {
|
||||
v['k'] = k;
|
||||
currentData['item'] = [{ ...v }];
|
||||
@@ -102,7 +104,7 @@ export default defineComponent({
|
||||
};
|
||||
// 菜单激活回调
|
||||
const onHorizontalSelect = (path: string) => {
|
||||
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(path));
|
||||
mittBus.emit('setSendClassicChildren', setSendClassicChildren(path));
|
||||
};
|
||||
// 页面加载时
|
||||
onMounted(() => {
|
||||
@@ -112,15 +114,7 @@ export default defineComponent({
|
||||
// 路由更新时
|
||||
onBeforeRouteUpdate((to) => {
|
||||
setCurrentRouterHighlight(to.path);
|
||||
proxy.mittBus.emit('onMenuClick');
|
||||
});
|
||||
return {
|
||||
menuLists,
|
||||
onElMenuHorizontalScroll,
|
||||
onHorizontalSelect,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
mittBus.emit('onMenuClick');
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -129,12 +123,15 @@ export default defineComponent({
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
margin-right: 30px;
|
||||
|
||||
::v-deep(.el-scrollbar__bar.is-vertical) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::v-deep(a) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.el-menu.el-menu--horizontal {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
</template>
|
||||
<sub-item :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
<el-menu-item :index="val.path" :key="val.path" v-else>
|
||||
<template v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
|
||||
<el-menu-item :index="val.path" :key="val?.path" v-else>
|
||||
<template v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<span>{{ val.meta.title }}</span>
|
||||
</template>
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue';
|
||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||
export default defineComponent({
|
||||
name: 'navMenuSubItem',
|
||||
props: {
|
||||
@@ -35,7 +36,7 @@ export default defineComponent({
|
||||
setup(props) {
|
||||
// 获取父级菜单数据
|
||||
const chils = computed(() => {
|
||||
return props.chil;
|
||||
return props.chil as any;
|
||||
});
|
||||
return {
|
||||
chils,
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<template>
|
||||
<el-menu
|
||||
router
|
||||
:default-active="defaultActive"
|
||||
background-color="transparent"
|
||||
:collapse="setIsCollapse"
|
||||
:unique-opened="getThemeConfig.isUniqueOpened"
|
||||
:collapse-transition="false"
|
||||
>
|
||||
<el-menu router :default-active="state.defaultActive" background-color="transparent" :collapse="setIsCollapse"
|
||||
:unique-opened="themeConfig.isUniqueOpened" :collapse-transition="false">
|
||||
<template v-for="val in menuLists">
|
||||
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||
<template #title>
|
||||
@@ -15,36 +9,36 @@
|
||||
</template>
|
||||
<SubItem :chil="val.children" />
|
||||
</el-sub-menu>
|
||||
<el-menu-item :index="val.path" :key="val.path" v-else>
|
||||
<el-menu-item :index="val.path" :key="val?.path" v-else>
|
||||
<SvgIcon :name="val.meta.icon"/>
|
||||
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
|
||||
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
|
||||
<span>{{ val.meta.title }}</span>
|
||||
</template>
|
||||
<template #title v-else>
|
||||
<a :href="val.meta.link" target="_blank">{{ val.meta.title }}</a></template
|
||||
>
|
||||
<a :href="val.meta.link" target="_blank">{{ val.meta.title }}</a></template>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed, defineComponent, getCurrentInstance } from 'vue';
|
||||
<script lang="ts" setup name="navMenuVertical">
|
||||
import { reactive, computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
||||
export default defineComponent({
|
||||
name: 'navMenuVertical',
|
||||
components: { SubItem },
|
||||
props: {
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
// 定义父组件传过来的值
|
||||
const props = defineProps({
|
||||
// 菜单列表
|
||||
menuList: {
|
||||
type: Array,
|
||||
type: Array<any>,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const store = useStore();
|
||||
});
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const route = useRoute();
|
||||
const state = reactive({
|
||||
defaultActive: route.path,
|
||||
@@ -53,27 +47,15 @@ export default defineComponent({
|
||||
const menuLists = computed(() => {
|
||||
return props.menuList;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
// 设置菜单的收起/展开
|
||||
const setIsCollapse = computed(() => {
|
||||
return document.body.clientWidth < 1000 ? false : getThemeConfig.value.isCollapse;
|
||||
return document.body.clientWidth < 1000 ? false : themeConfig.value.isCollapse;
|
||||
});
|
||||
// 路由更新时
|
||||
onBeforeRouteUpdate((to) => {
|
||||
state.defaultActive = to.path;
|
||||
proxy.mittBus.emit('onMenuClick');
|
||||
mittBus.emit('onMenuClick');
|
||||
const clientWidth = document.body.clientWidth;
|
||||
if (clientWidth < 1000) getThemeConfig.value.isCollapse = false;
|
||||
});
|
||||
return {
|
||||
menuLists,
|
||||
getThemeConfig,
|
||||
setIsCollapse,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, onMounted, onBeforeMount, onUnmounted, nextTick, getCurrentInstance } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
export default defineComponent({
|
||||
name: 'layoutIfameView',
|
||||
props: {
|
||||
@@ -18,7 +19,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const route = useRoute();
|
||||
const state = reactive({
|
||||
iframeLoading: true,
|
||||
@@ -38,7 +38,7 @@ export default defineComponent({
|
||||
// 页面加载前
|
||||
onBeforeMount(() => {
|
||||
state.iframeUrl = props.meta.link;
|
||||
proxy.mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
||||
mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
||||
if (route.path !== path) return false;
|
||||
emit('getCurrentRouteMeta');
|
||||
});
|
||||
@@ -49,7 +49,7 @@ export default defineComponent({
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
proxy.mittBus.off('onTagsViewRefreshRouterView', () => {});
|
||||
mittBus.off('onTagsViewRefreshRouterView', () => {});
|
||||
});
|
||||
return {
|
||||
...toRefs(state),
|
||||
|
||||
@@ -2,24 +2,25 @@
|
||||
<div class="h100">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :name="setTransitionName" mode="out-in">
|
||||
<keep-alive :include="keepAliveNameList">
|
||||
<component :is="Component" :key="refreshRouterViewKey" class="w100" />
|
||||
<keep-alive :include="state.keepAliveNameList">
|
||||
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, ref } from 'vue';
|
||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
export default defineComponent({
|
||||
name: 'layoutParentView',
|
||||
setup() {
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
<script lang="ts" setup name="layoutParentView">
|
||||
import { computed, reactive, onBeforeMount, onUnmounted, nextTick } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||
import mittBus from '@/common/utils/mitt';
|
||||
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const { keepAliveNames } = storeToRefs(useKeepALiveNames());
|
||||
const state: any = reactive({
|
||||
refreshRouterViewKey: null,
|
||||
keepAliveNameList: [],
|
||||
@@ -29,41 +30,25 @@ export default defineComponent({
|
||||
// onBeforeRouteUpdate((to: any) => {
|
||||
// state.refreshRouterViewKey = decodeURI(to.fullPath);
|
||||
// });
|
||||
// 设置主界面切换动画
|
||||
const setTransitionName = computed(() => {
|
||||
return store.state.themeConfig.themeConfig.animation;
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
// 获取组件缓存列表(name值)
|
||||
const getKeepAliveNames = computed(() => {
|
||||
return store.state.keepAliveNames.keepAliveNames;
|
||||
});
|
||||
// 页面加载前,处理缓存,页面刷新时路由缓存处理
|
||||
onBeforeMount(() => {
|
||||
state.keepAliveNameList = getKeepAliveNames.value;
|
||||
proxy.mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
||||
state.keepAliveNameList = keepAliveNames.value;
|
||||
mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
||||
if (decodeURI(route.fullPath) !== path) return false;
|
||||
state.keepAliveNameList = getKeepAliveNames.value.filter((name: string) => route.name !== name);
|
||||
state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
|
||||
state.refreshRouterViewKey = route.path;
|
||||
nextTick(() => {
|
||||
state.refreshRouterViewKey = null;
|
||||
state.keepAliveNameList = getKeepAliveNames.value;
|
||||
state.keepAliveNameList = keepAliveNames.value;
|
||||
});
|
||||
});
|
||||
});
|
||||
// 设置主界面切换动画
|
||||
const setTransitionName = computed(() => {
|
||||
return themeConfig.value.animation;
|
||||
});
|
||||
// 页面卸载时
|
||||
onUnmounted(() => {
|
||||
proxy.mittBus.off('onTagsViewRefreshRouterView');
|
||||
});
|
||||
return {
|
||||
getThemeConfig,
|
||||
getKeepAliveNames,
|
||||
setTransitionName,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
mittBus.off('onTagsViewRefreshRouterView');
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -2,37 +2,25 @@
|
||||
<div>
|
||||
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
|
||||
<el-form-item prop="username">
|
||||
<el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable autocomplete="off">
|
||||
<el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable
|
||||
autocomplete="off">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password" autocomplete="off" show-password>
|
||||
<el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password"
|
||||
autocomplete="off" @keyup.enter="login" show-password>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="useLoginCaptcha" prop="captcha">
|
||||
<el-form-item v-if="isUseLoginCaptcha" prop="captcha">
|
||||
<el-row :gutter="15">
|
||||
<el-col :span="16">
|
||||
<el-input
|
||||
type="text"
|
||||
maxlength="6"
|
||||
placeholder="请输入验证码"
|
||||
prefix-icon="position"
|
||||
v-model="loginForm.captcha"
|
||||
clearable
|
||||
autocomplete="off"
|
||||
@keyup.enter="login"
|
||||
></el-input>
|
||||
<el-input type="text" maxlength="6" placeholder="请输入验证码" prefix-icon="position"
|
||||
v-model="loginForm.captcha" clearable autocomplete="off" @keyup.enter="login"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<div class="login-content-code">
|
||||
<img
|
||||
class="login-content-code-img"
|
||||
@click="getCaptcha"
|
||||
width="130px"
|
||||
height="40px"
|
||||
:src="captchaImage"
|
||||
style="cursor: pointer"
|
||||
/>
|
||||
<img class="login-content-code-img" @click="getCaptcha" width="130px" height="40px"
|
||||
:src="captchaImage" style="cursor: pointer" />
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -44,21 +32,20 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
|
||||
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef" label-width="65px">
|
||||
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px"
|
||||
:destroy-on-close="true">
|
||||
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef"
|
||||
label-width="65px">
|
||||
<el-form-item prop="username" label="用户名" required>
|
||||
<el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="oldPassword" label="旧密码" required>
|
||||
<el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password" type="password"></el-input>
|
||||
<el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password"
|
||||
type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="newPassword" label="新密码" required>
|
||||
<el-input
|
||||
v-model.trim="changePwdDialog.form.newPassword"
|
||||
placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
></el-input>
|
||||
<el-input v-model.trim="changePwdDialog.form.newPassword" placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
|
||||
type="password" autocomplete="new-password"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -72,30 +59,32 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { nextTick, onMounted, ref, toRefs, reactive, defineComponent, computed } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { initBackEndControlRoutesFun } from '@/router/index.ts';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
import { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage.ts';
|
||||
import { formatAxis } from '@/common/utils/formatTime.ts';
|
||||
import { initRouter } from '@/router/index';
|
||||
import { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage';
|
||||
import { formatAxis } from '@/common/utils/format';
|
||||
import openApi from '@/common/openApi';
|
||||
import { RsaEncrypt } from '@/common/rsa';
|
||||
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
|
||||
import { letterAvatar } from '@/common/utils/string';
|
||||
import { useUserInfo } from '@/store/userInfo';
|
||||
|
||||
const rules = {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AccountLogin',
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const loginFormRef: any = ref(null);
|
||||
const changePwdFormRef: any = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
useLoginCaptcha: false,
|
||||
isUseLoginCaptcha: false,
|
||||
captchaImage: '',
|
||||
loginForm: {
|
||||
username: '',
|
||||
@@ -121,20 +110,23 @@ export default defineComponent({
|
||||
],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
||||
},
|
||||
loading: {
|
||||
signIn: false,
|
||||
changePwd: false,
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
isUseLoginCaptcha,
|
||||
captchaImage,
|
||||
loginForm,
|
||||
changePwdDialog,
|
||||
loading,
|
||||
} = toRefs(state)
|
||||
|
||||
onMounted(async () => {
|
||||
nextTick(async () => {
|
||||
state.useLoginCaptcha = await useLoginCaptcha();
|
||||
state.isUseLoginCaptcha = await useLoginCaptcha();
|
||||
getCaptcha();
|
||||
});
|
||||
// 移除公钥, 方便后续重新获取
|
||||
@@ -142,10 +134,10 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
const getCaptcha = async () => {
|
||||
if (!state.useLoginCaptcha) {
|
||||
if (!state.isUseLoginCaptcha) {
|
||||
return;
|
||||
}
|
||||
let res: any = await openApi.captcha();
|
||||
let res: any = await openApi.captcha.request();
|
||||
state.captchaImage = res.base64Captcha;
|
||||
state.loginForm.cid = res.cid;
|
||||
};
|
||||
@@ -174,10 +166,9 @@ export default defineComponent({
|
||||
try {
|
||||
const loginReq = { ...state.loginForm };
|
||||
loginReq.password = await RsaEncrypt(originPwd);
|
||||
loginRes = await openApi.login(loginReq);
|
||||
loginRes = await openApi.login.request(loginReq);
|
||||
// 存储 token 到浏览器缓存
|
||||
setSession('token', loginRes.token);
|
||||
setSession('menus', loginRes.menus);
|
||||
} catch (e: any) {
|
||||
state.loading.signIn = false;
|
||||
state.loginForm.captcha = '';
|
||||
@@ -194,12 +185,11 @@ export default defineComponent({
|
||||
}
|
||||
// 用户信息
|
||||
const userInfos = {
|
||||
name: loginRes.name,
|
||||
username: state.loginForm.username,
|
||||
// 头像
|
||||
photo: letterAvatar(state.loginForm.username),
|
||||
time: new Date().getTime(),
|
||||
// // 菜单资源code数组
|
||||
// menus: loginRes.menus,
|
||||
permissions: loginRes.permissions,
|
||||
lastLoginTime: loginRes.lastLoginTime,
|
||||
lastLoginIp: loginRes.lastLoginIp,
|
||||
@@ -208,19 +198,9 @@ export default defineComponent({
|
||||
// 存储用户信息到浏览器缓存
|
||||
setUserInfo2Session(userInfos);
|
||||
// 1、请注意执行顺序(存储用户信息到vuex)
|
||||
store.dispatch('userInfos/setUserInfos', userInfos);
|
||||
if (!store.state.themeConfig.themeConfig.isRequestRoutes) {
|
||||
// 前端控制路由,2、请注意执行顺序
|
||||
// await initAllFun();
|
||||
await initBackEndControlRoutesFun();
|
||||
useUserInfo().setUserInfo(userInfos);
|
||||
await initRouter();
|
||||
signInSuccess();
|
||||
} else {
|
||||
// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
|
||||
await initBackEndControlRoutesFun();
|
||||
// 执行完 initBackEndControlRoutesFun,再执行 signInSuccess
|
||||
signInSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
// 登录成功后的跳转
|
||||
@@ -253,7 +233,7 @@ export default defineComponent({
|
||||
const changePwdReq: any = { ...form };
|
||||
changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
|
||||
changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
|
||||
await openApi.changePwd(changePwdReq);
|
||||
await openApi.changePwd.request(changePwdReq);
|
||||
ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
|
||||
state.loginForm.password = state.changePwdDialog.form.newPassword;
|
||||
state.changePwdDialog.visible = false;
|
||||
@@ -271,28 +251,17 @@ export default defineComponent({
|
||||
state.changePwdDialog.form.username = '';
|
||||
getCaptcha();
|
||||
};
|
||||
|
||||
return {
|
||||
getCaptcha,
|
||||
currentTime,
|
||||
loginFormRef,
|
||||
changePwdFormRef,
|
||||
login,
|
||||
changePwd,
|
||||
cancelChangePwd,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-content-form {
|
||||
margin-top: 20px;
|
||||
|
||||
.login-content-code {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
|
||||
.login-content-code-img {
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
@@ -309,12 +278,14 @@ export default defineComponent({
|
||||
transition: all ease 0.2s;
|
||||
border-radius: 4px;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
border-color: #c0c4cc;
|
||||
transition: all ease 0.2s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-content-submit {
|
||||
width: 100%;
|
||||
letter-spacing: 2px;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="login-container">
|
||||
<div class="login-logo">
|
||||
<span>{{ getThemeConfig.globalViceTitle }}</span>
|
||||
<span>{{ themeConfig.globalViceTitle }}</span>
|
||||
</div>
|
||||
<div class="login-content" :class="{ 'login-content-mobile': tabsActiveName === 'mobile' }">
|
||||
<div class="login-content-main">
|
||||
@@ -24,41 +24,35 @@
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="login-copyright">
|
||||
<!-- <div class="login-copyright">
|
||||
<div class="mb5 login-copyright-company">mayfly</div>
|
||||
<div class="login-copyright-msg">mayfly</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, computed } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive } from 'vue';
|
||||
import Account from '@/views/login/component/AccountLogin.vue';
|
||||
import { useStore } from '@/store/index.ts';
|
||||
export default {
|
||||
name: 'LoginPage',
|
||||
components: { Account },
|
||||
setup() {
|
||||
const store = useStore();
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeConfig } from '@/store/themeConfig';
|
||||
|
||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||
const state = reactive({
|
||||
tabsActiveName: 'account',
|
||||
isTabPaneShow: true,
|
||||
});
|
||||
// 获取布局配置信息
|
||||
const getThemeConfig = computed(() => {
|
||||
return store.state.themeConfig.themeConfig;
|
||||
});
|
||||
|
||||
const {
|
||||
isTabPaneShow,
|
||||
tabsActiveName,
|
||||
} = toRefs(state)
|
||||
|
||||
|
||||
// 切换密码、手机登录
|
||||
const onTabsClick = () => {
|
||||
state.isTabPaneShow = !state.isTabPaneShow;
|
||||
};
|
||||
return {
|
||||
onTabsClick,
|
||||
getThemeConfig,
|
||||
...toRefs(state),
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -67,6 +61,7 @@ export default {
|
||||
height: 100%;
|
||||
background: url('@/assets/image/bg-login.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.login-logo {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
@@ -80,6 +75,7 @@ export default {
|
||||
width: 90%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.login-content {
|
||||
width: 500px;
|
||||
padding: 20px;
|
||||
@@ -94,9 +90,11 @@ export default {
|
||||
height: 480px;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
|
||||
.login-content-main {
|
||||
margin: 0 auto;
|
||||
width: 80%;
|
||||
|
||||
.login-content-title {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
@@ -108,9 +106,11 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.login-content-mobile {
|
||||
height: 418px;
|
||||
}
|
||||
|
||||
.login-copyright {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
@@ -120,9 +120,11 @@ export default {
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
|
||||
.login-copyright-company {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.login-copyright-msg {
|
||||
@extend .login-copyright-company;
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-form class="search-form" label-position="right" :inline="true">
|
||||
<el-form-item prop="project" label="项目" label-width="40px">
|
||||
<el-select v-model="projectId" placeholder="请选择项目" @change="changeProject" filterable>
|
||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="env" label="env" label-width="33px">
|
||||
<el-select style="width: 85px" v-model="envId" placeholder="环境" @change="changeEnv" filterable>
|
||||
<el-option v-for="item in envs" :key="item.id" :label="item.name" :value="item.id">
|
||||
<span style="float: left">{{ item.name }}</span>
|
||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.remark }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<slot></slot>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
|
||||
import { projectApi } from '../project/api';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProjectEnvSelect',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
data: {
|
||||
type: Object,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
machineId: {
|
||||
type: Number,
|
||||
},
|
||||
isCommon: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const state = reactive({
|
||||
projects: [] as any,
|
||||
envs: [] as any,
|
||||
projectId: null,
|
||||
envId: null,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
state.projects = await projectApi.accountProjects.request(null);
|
||||
});
|
||||
|
||||
const changeProject = async (projectId: any) => {
|
||||
emit('update:projectId', projectId);
|
||||
emit('changeProjectEnv', state.projectId, null);
|
||||
state.envId = null;
|
||||
state.envs = await projectApi.projectEnvs.request({ projectId });
|
||||
};
|
||||
|
||||
const changeEnv = (envId: any) => {
|
||||
emit('update:envId', envId);
|
||||
emit('changeProjectEnv', state.projectId, envId);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
changeProject,
|
||||
changeEnv,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
61
mayfly_go_web/src/views/ops/component/SshTunnelSelect.vue
Normal file
61
mayfly_go_web/src/views/ops/component/SshTunnelSelect.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div style="width: 100%">
|
||||
<el-select @focus="getSshTunnelMachines" @change="change" style="width: 100%" v-model="sshTunnelMachineId"
|
||||
@clear="clear" placeholder="请选择SSH隧道机器" clearable>
|
||||
<el-option v-for="item in sshTunnelMachineList" :key="item.id" :label="`${item.ip}:${item.port} [${item.name}]`"
|
||||
:value="item.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, onMounted } from 'vue';
|
||||
import { machineApi } from '../machine/api';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Number,
|
||||
},
|
||||
})
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const state = reactive({
|
||||
// 单选则为id,多选为id数组
|
||||
sshTunnelMachineId: null as any,
|
||||
sshTunnelMachineList: [] as any,
|
||||
});
|
||||
|
||||
const {
|
||||
sshTunnelMachineId,
|
||||
sshTunnelMachineList,
|
||||
} = toRefs(state)
|
||||
|
||||
onMounted(async () => {
|
||||
if (!props.modelValue || props.modelValue <= 0) {
|
||||
state.sshTunnelMachineId = null;
|
||||
} else {
|
||||
state.sshTunnelMachineId = props.modelValue;
|
||||
}
|
||||
await getSshTunnelMachines();
|
||||
});
|
||||
|
||||
const getSshTunnelMachines = async () => {
|
||||
if (state.sshTunnelMachineList.length == 0) {
|
||||
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
|
||||
state.sshTunnelMachineList = res.list;
|
||||
}
|
||||
};
|
||||
|
||||
const clear = () => {
|
||||
state.sshTunnelMachineId = null;
|
||||
change();
|
||||
}
|
||||
|
||||
const change = () => {
|
||||
emit('update:modelValue', state.sshTunnelMachineId);
|
||||
};
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
68
mayfly_go_web/src/views/ops/component/TagInfo.vue
Normal file
68
mayfly_go_web/src/views/ops/component/TagInfo.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div
|
||||
style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer;vertical-align: middle;">
|
||||
<el-popover @show="showTagInfo" placement="top-start" title="标签信息" :width="300" trigger="hover">
|
||||
<template #reference>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</template>
|
||||
<span v-for="(v, i) in tags" :key="i">
|
||||
<el-tooltip effect="customized" :content="v.remark" placement="top">
|
||||
<span class="color-success">{{ v.name }}</span>
|
||||
</el-tooltip>
|
||||
<span v-if="i != state.tags.length - 1" class="color-primary"> / </span>
|
||||
</span>
|
||||
</el-popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, toRefs, onMounted } from 'vue';
|
||||
import { tagApi } from '../tag/api';
|
||||
const props = defineProps({
|
||||
tagPath: {
|
||||
type: [String],
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
tagPath: '',
|
||||
tags: [] as any,
|
||||
})
|
||||
|
||||
const {
|
||||
tags,
|
||||
} = toRefs(state)
|
||||
|
||||
onMounted(async () => {
|
||||
state.tagPath = props.tagPath;
|
||||
})
|
||||
|
||||
const showTagInfo = async () => {
|
||||
if (state.tags && state.tags.length > 0) {
|
||||
return;
|
||||
}
|
||||
const tagStrs = state.tagPath.split('/');
|
||||
const tagPaths = [];
|
||||
let nowTag = '';
|
||||
for (let tagStr of tagStrs) {
|
||||
if (!tagStr) {
|
||||
continue;
|
||||
}
|
||||
if (nowTag) {
|
||||
nowTag = `${nowTag}${tagStr}/`;
|
||||
} else {
|
||||
nowTag = tagStr + '/';
|
||||
}
|
||||
tagPaths.push(nowTag);
|
||||
}
|
||||
state.tags = await tagApi.listByQuery.request({ tagPaths: tagPaths.join(',') });
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -1,21 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tree-select
|
||||
@check="changeTag"
|
||||
style="width: 100%"
|
||||
v-model="selectTags"
|
||||
:data="tags"
|
||||
:render-after-expand="true"
|
||||
:default-expanded-keys="[selectTags]"
|
||||
show-checkbox
|
||||
check-strictly
|
||||
node-key="id"
|
||||
<el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags" placeholder="请选择关联标签"
|
||||
:render-after-expand="true" :default-expanded-keys="[selectTags]" show-checkbox check-strictly node-key="id"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'codePath',
|
||||
children: 'children',
|
||||
}"
|
||||
>
|
||||
}">
|
||||
<template #default="{ data }">
|
||||
<span class="custom-tree-node">
|
||||
<span style="font-size: 13px">
|
||||
@@ -31,27 +22,33 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, onMounted } from 'vue';
|
||||
import { tagApi } from '../tag/api';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TagSelect',
|
||||
props: {
|
||||
const props = defineProps({
|
||||
tagId: {
|
||||
type: Number,
|
||||
},
|
||||
tagPath: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
})
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['changeTag', 'update:tagId', 'update:tagPath'])
|
||||
|
||||
const state = reactive({
|
||||
tags: [],
|
||||
// 单选则为id,多选为id数组
|
||||
selectTags: null as any,
|
||||
});
|
||||
|
||||
const {
|
||||
tags,
|
||||
selectTags,
|
||||
} = toRefs(state)
|
||||
|
||||
onMounted(async () => {
|
||||
if (props.tagId) {
|
||||
state.selectTags = props.tagId;
|
||||
@@ -69,13 +66,7 @@ export default defineComponent({
|
||||
emit('update:tagPath', null);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
changeTag,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
166
mayfly_go_web/src/views/ops/component/TagTree.vue
Normal file
166
mayfly_go_web/src/views/ops/component/TagTree.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="instances-box">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-col :span="24" class="el-scrollbar flex-auto" style="overflow: auto">
|
||||
<el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5" />
|
||||
|
||||
<el-tree ref="treeRef" :style="{ maxHeight: state.height, height: state.height, overflow: 'auto' }"
|
||||
:highlight-current="true" :indent="7" :load="loadNode" :props="treeProps" lazy node-key="key"
|
||||
:expand-on-click-node="true" :filter-node-method="filterNode" @node-click="treeNodeClick"
|
||||
@node-expand="treeNodeClick" @node-contextmenu="nodeContextmenu">
|
||||
<template #default="{ node, data }">
|
||||
<span>
|
||||
<span v-if="data.type == TagTreeNode.TagPath">
|
||||
<tag-info :tag-path="data.label" />
|
||||
</span>
|
||||
|
||||
<slot v-else :node="node" :data="data" name="prefix"></slot>
|
||||
|
||||
<span class="ml3">
|
||||
<slot name="label" :data="data"> {{ data.label }}</slot>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
</el-tree>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<contextmenu :dropdown="state.dropdown" :items="state.contextmenuItems" ref="contextmenuRef"
|
||||
@currentContextmenuClick="onCurrentContextmenuClick" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
|
||||
import { TagTreeNode } from './tag';
|
||||
import TagInfo from './TagInfo.vue';
|
||||
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||
|
||||
const props = defineProps({
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
load: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
loadContextmenuItems: {
|
||||
type: Function,
|
||||
required: false,
|
||||
}
|
||||
})
|
||||
|
||||
const treeProps = {
|
||||
label: 'name',
|
||||
children: 'zones',
|
||||
isLeaf: 'isLeaf',
|
||||
}
|
||||
|
||||
const emit = defineEmits(['nodeClick', 'currentContextmenuClick'])
|
||||
const treeRef: any = ref(null)
|
||||
const contextmenuRef = ref();
|
||||
|
||||
const state = reactive({
|
||||
height: 600 as any,
|
||||
filterText: '',
|
||||
dropdown: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
},
|
||||
contextmenuItems: [],
|
||||
opend: {},
|
||||
})
|
||||
const { filterText } = toRefs(state)
|
||||
|
||||
onMounted(async () => {
|
||||
if (!props.height) {
|
||||
state.height = window.innerHeight - 147 + 'px';
|
||||
} else {
|
||||
state.height = props.height;
|
||||
}
|
||||
})
|
||||
|
||||
watch(filterText, (val) => {
|
||||
treeRef.value?.filter(val)
|
||||
})
|
||||
|
||||
const filterNode = (value: string, data: any) => {
|
||||
if (!value) return true
|
||||
return data.label.includes(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载树节点
|
||||
* @param { Object } node
|
||||
* @param { Object } resolve
|
||||
*/
|
||||
const loadNode = async (node: any, resolve: any) => {
|
||||
if (typeof resolve !== 'function') {
|
||||
return;
|
||||
}
|
||||
let nodes = []
|
||||
try {
|
||||
nodes = await props.load(node)
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
}
|
||||
return resolve(nodes)
|
||||
};
|
||||
|
||||
const treeNodeClick = (data: any) => {
|
||||
emit('nodeClick', data);
|
||||
// 关闭可能存在的右击菜单
|
||||
contextmenuRef.value.closeContextmenu();
|
||||
}
|
||||
|
||||
// 树节点右击事件
|
||||
const nodeContextmenu = (event: any, data: any) => {
|
||||
if (!props.loadContextmenuItems) {
|
||||
return;
|
||||
}
|
||||
// 加载当前节点是否需要显示右击菜单
|
||||
const items = props.loadContextmenuItems(data)
|
||||
if (!items || items.length == 0) {
|
||||
return;
|
||||
}
|
||||
state.contextmenuItems = items;
|
||||
const { clientX, clientY } = event;
|
||||
state.dropdown.x = clientX;
|
||||
state.dropdown.y = clientY;
|
||||
contextmenuRef.value.openContextmenu(data);
|
||||
}
|
||||
|
||||
const onCurrentContextmenuClick = (clickData: any) => {
|
||||
emit('currentContextmenuClick', clickData);
|
||||
}
|
||||
|
||||
const reloadNode = (nodeKey: any) => {
|
||||
let node = getNode(nodeKey);
|
||||
node.loaded = false;
|
||||
node.expand();
|
||||
}
|
||||
|
||||
const getNode = (nodeKey: any) => {
|
||||
let node = treeRef.value.getNode(nodeKey);
|
||||
if (!node) {
|
||||
throw new Error('未找到节点: ' + nodeKey);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
reloadNode,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.instances-box {
|
||||
overflow: 'auto';
|
||||
position: relative;
|
||||
|
||||
.el-tree {
|
||||
display: inline-block;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
38
mayfly_go_web/src/views/ops/component/tag.ts
Normal file
38
mayfly_go_web/src/views/ops/component/tag.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export class TagTreeNode {
|
||||
/**
|
||||
* 节点id
|
||||
*/
|
||||
key: any
|
||||
|
||||
/**
|
||||
* 节点名称
|
||||
*/
|
||||
label: string
|
||||
|
||||
/**
|
||||
* 树节点类型
|
||||
*/
|
||||
type: any
|
||||
|
||||
isLeaf: boolean = false;
|
||||
|
||||
params: any;
|
||||
|
||||
static TagPath = -1;
|
||||
|
||||
constructor(key: any, label: string, type?: any) {
|
||||
this.key = key;
|
||||
this.label = label;
|
||||
this.type = type || TagTreeNode.TagPath;
|
||||
}
|
||||
|
||||
withIsLeaf(isLeaf: boolean) {
|
||||
this.isLeaf = isLeaf;
|
||||
return this;
|
||||
}
|
||||
|
||||
withParams(params: any) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog title="创建表" v-model="dialogVisible" :before-close="cancel" width="90%">
|
||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" width="90%">
|
||||
<el-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
@@ -13,10 +13,21 @@
|
||||
<el-input style="width: 80%" v-model="tableData.tableComment" size="small"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col style="margin-top: 20px" :span="12">
|
||||
<el-form-item prop="characterSet" label="字符集">
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="characterSet" label="charset">
|
||||
<el-select filterable style="width: 80%" v-model="tableData.characterSet" size="small">
|
||||
<el-option v-for="item in characterSetNameList" :key="item" :label="item" :value="item"> </el-option>
|
||||
<el-option v-for="item in characterSetNameList" :key="item" :label="item" :value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item prop="characterSet" label="collation">
|
||||
<el-select filterable style="width: 80%" v-model="tableData.collation" size="small">
|
||||
<el-option v-for="item in collationNameList" :key="item"
|
||||
:label="tableData.characterSet + '_' + item"
|
||||
:value="tableData.characterSet + '_' + item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -24,35 +35,88 @@
|
||||
|
||||
<el-tabs v-model="activeName">
|
||||
<el-tab-pane label="字段" name="1">
|
||||
<el-table :data="tableData.fields.res">
|
||||
<el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.fields.colNames" :key="item.prop">
|
||||
<el-table :data="tableData.fields.res" :max-height="tableData.height">
|
||||
<el-table-column :prop="item.prop" :label="item.label"
|
||||
v-for="item in tableData.fields.colNames" :key="item.prop">
|
||||
<template #default="scope">
|
||||
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name"></el-input>
|
||||
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name">
|
||||
</el-input>
|
||||
|
||||
<el-select v-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
|
||||
<el-option v-for="typeValue in typeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option>
|
||||
<el-select v-if="item.prop === 'type'" filterable size="small"
|
||||
v-model="scope.row.type">
|
||||
<el-option v-for="typeValue in columnTypeList" :key="typeValue"
|
||||
:value="typeValue">{{ typeValue }}</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-input v-if="item.prop === 'value'" size="small" v-model="scope.row.value"> </el-input>
|
||||
<el-input v-if="item.prop === 'value'" size="small" v-model="scope.row.value">
|
||||
</el-input>
|
||||
|
||||
<el-input v-if="item.prop === 'length'" size="small" v-model="scope.row.length"> </el-input>
|
||||
<el-input v-if="item.prop === 'length'" size="small" v-model="scope.row.length">
|
||||
</el-input>
|
||||
|
||||
<el-checkbox v-if="item.prop === 'notNull'" size="small" v-model="scope.row.notNull"> </el-checkbox>
|
||||
<el-checkbox v-if="item.prop === 'notNull'" size="small"
|
||||
v-model="scope.row.notNull"> </el-checkbox>
|
||||
|
||||
<el-checkbox v-if="item.prop === 'pri'" size="small" v-model="scope.row.pri"> </el-checkbox>
|
||||
<el-checkbox v-if="item.prop === 'pri'" size="small" v-model="scope.row.pri">
|
||||
</el-checkbox>
|
||||
|
||||
<el-checkbox v-if="item.prop === 'auto_increment'" size="small" v-model="scope.row.auto_increment"> </el-checkbox>
|
||||
<el-checkbox v-if="item.prop === 'auto_increment'" size="small"
|
||||
v-model="scope.row.auto_increment"> </el-checkbox>
|
||||
|
||||
<el-input v-if="item.prop === 'remark'" size="small" v-model="scope.row.remark"> </el-input>
|
||||
<el-input v-if="item.prop === 'remark'" size="small" v-model="scope.row.remark">
|
||||
</el-input>
|
||||
|
||||
<el-button v-if="item.prop === 'action'" type="text" size="small" @click.prevent="deleteRow(scope.$index)">删除</el-button>
|
||||
<el-link v-if="item.prop === 'action'" type="danger" plain size="small"
|
||||
:underline="false" @click.prevent="deleteRow(scope.$index)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-row style="margin-top: 20px">
|
||||
<el-button @click="addRow()" type="text" icon="plus"></el-button>
|
||||
<el-button @click="addDefaultRows()" link type="warning" icon="plus">添加默认列</el-button>
|
||||
<el-button @click="addRow()" link type="primary" icon="plus">添加列</el-button>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="索引" name="2">
|
||||
<el-table :data="tableData.indexs.res" :max-height="tableData.height">
|
||||
<el-table-column :prop="item.prop" :label="item.label"
|
||||
v-for="item in tableData.indexs.colNames" :key="item.prop">
|
||||
<template #default="scope">
|
||||
|
||||
<el-input v-if="item.prop === 'indexName'" size="small"
|
||||
v-model="scope.row.indexName"></el-input>
|
||||
|
||||
<el-select v-if="item.prop === 'columnNames'" v-model="scope.row.columnNames"
|
||||
multiple collapse-tags collapse-tags-tooltip filterable placeholder="请选择字段"
|
||||
style="width: 100%">
|
||||
<el-option v-for="cl in tableData.indexs.columns" :key="cl.name"
|
||||
:label="cl.name" :value="cl.name">
|
||||
{{ cl.name + ' - ' + (cl.remark || '') }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique">
|
||||
</el-checkbox>
|
||||
|
||||
<el-select v-if="item.prop === 'indexType'" filterable size="small"
|
||||
v-model="scope.row.indexType">
|
||||
<el-option v-for="typeValue in indexTypeList" :key="typeValue"
|
||||
:value="typeValue">{{ typeValue }}</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-input v-if="item.prop === 'indexComment'" size="small"
|
||||
v-model="scope.row.indexComment"> </el-input>
|
||||
|
||||
<el-link v-if="item.prop === 'action'" type="danger" plain size="small"
|
||||
:underline="false" @click.prevent="deleteIndex(scope.$index)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-row style="margin-top: 20px">
|
||||
<el-button @click="addIndex()" link type="primary" icon="plus">添加索引</el-button>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
@@ -63,14 +127,13 @@
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { watch, toRefs, reactive, defineComponent, ref, getCurrentInstance } from 'vue';
|
||||
import { TYPE_LIST, CHARACTER_SET_NAME_LIST } from './service.ts';
|
||||
<script lang="ts" setup>
|
||||
import { watch, toRefs, reactive, ref } from 'vue';
|
||||
import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import SqlExecBox from './component/SqlExecBox.ts';
|
||||
export default defineComponent({
|
||||
name: 'createTable',
|
||||
props: {
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
@@ -86,16 +149,20 @@ export default defineComponent({
|
||||
db: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
})
|
||||
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql'])
|
||||
|
||||
const formRef: any = ref();
|
||||
const { proxy } = getCurrentInstance() as any;
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
btnloading: false,
|
||||
activeName: '1',
|
||||
typeList: TYPE_LIST,
|
||||
columnTypeList: TYPE_LIST,
|
||||
indexTypeList: ['BTREE'], // mysql索引类型详解 http://c.biancheng.net/view/7897.html
|
||||
characterSetNameList: CHARACTER_SET_NAME_LIST,
|
||||
collationNameList: COLLATION_SUFFIX_LIST,
|
||||
tableData: {
|
||||
fields: {
|
||||
colNames: [
|
||||
@@ -137,7 +204,6 @@ export default defineComponent({
|
||||
label: '操作',
|
||||
},
|
||||
],
|
||||
|
||||
res: [
|
||||
{
|
||||
name: '',
|
||||
@@ -151,19 +217,72 @@ export default defineComponent({
|
||||
},
|
||||
],
|
||||
},
|
||||
indexs: {
|
||||
colNames: [
|
||||
{
|
||||
prop: 'indexName',
|
||||
label: '索引名',
|
||||
},
|
||||
{
|
||||
prop: 'columnNames',
|
||||
label: '列名',
|
||||
},
|
||||
{
|
||||
prop: 'unique',
|
||||
label: '唯一',
|
||||
},
|
||||
{
|
||||
prop: 'indexType',
|
||||
label: '类型',
|
||||
},
|
||||
{
|
||||
prop: 'indexComment',
|
||||
label: '备注',
|
||||
},
|
||||
{
|
||||
prop: 'action',
|
||||
label: '操作',
|
||||
},
|
||||
],
|
||||
columns: [{ name: '', remark: '' }],
|
||||
res: [
|
||||
{
|
||||
indexName: '',
|
||||
columnNames: [],
|
||||
unique: false,
|
||||
indexType: 'BTREE',
|
||||
indexComment: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
characterSet: 'utf8mb4',
|
||||
collation: 'utf8mb4_general_ci',
|
||||
tableName: '',
|
||||
tableComment: '',
|
||||
height: 550
|
||||
},
|
||||
});
|
||||
|
||||
const {
|
||||
dialogVisible,
|
||||
btnloading,
|
||||
activeName,
|
||||
columnTypeList,
|
||||
indexTypeList,
|
||||
characterSetNameList,
|
||||
collationNameList,
|
||||
tableData,
|
||||
} = toRefs(state)
|
||||
|
||||
watch(props, async (newValue) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
});
|
||||
|
||||
const cancel = () => {
|
||||
emit('update:visible', false);
|
||||
reset();
|
||||
};
|
||||
|
||||
const addRow = () => {
|
||||
state.tableData.fields.res.push({
|
||||
name: '',
|
||||
@@ -176,45 +295,236 @@ export default defineComponent({
|
||||
remark: '',
|
||||
});
|
||||
};
|
||||
|
||||
const addIndex = () => {
|
||||
state.tableData.indexs.res.push({
|
||||
indexName: '',
|
||||
columnNames: [],
|
||||
unique: false,
|
||||
indexType: 'BTREE',
|
||||
indexComment: '',
|
||||
});
|
||||
};
|
||||
|
||||
const addDefaultRows = () => {
|
||||
state.tableData.fields.res.push(
|
||||
{ name: 'id', type: 'bigint', length: '20', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
|
||||
{ name: 'creator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
|
||||
{ name: 'creator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人姓名' },
|
||||
{ name: 'create_time', type: 'datetime', length: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '创建时间' },
|
||||
{ name: 'updator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
|
||||
{ name: 'updator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名' },
|
||||
{ name: 'update_time', type: 'datetime', length: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '修改时间' },
|
||||
);
|
||||
};
|
||||
|
||||
const deleteRow = (index: any) => {
|
||||
state.tableData.fields.res.splice(index, 1);
|
||||
};
|
||||
|
||||
const deleteIndex = (index: any) => {
|
||||
state.tableData.indexs.res.splice(index, 1);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
let data = state.tableData;
|
||||
let primary_key = '';
|
||||
let fields: string[] = [];
|
||||
data.fields.res.forEach((item) => {
|
||||
fields.push(
|
||||
`${item.name} ${item.type}${+item.length > 0 ? `(${item.length})` : ''} ${item.notNull ? 'NOT NULL' : ''} ${
|
||||
item.auto_increment ? 'AUTO_INCREMENT' : ''
|
||||
} ${item.value ? 'DEFAULT ' + item.value : item.notNull ? '' : 'DEFAULT NULL'} ${
|
||||
item.remark ? `COMMENT '${item.remark}'` : ''
|
||||
} \n`
|
||||
);
|
||||
if (item.pri) {
|
||||
primary_key += `${item.name},`;
|
||||
let sql = genSql();
|
||||
if (!sql) {
|
||||
ElMessage.warning('没有更改');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
let sql = `
|
||||
CREATE TABLE ${data.tableName} (
|
||||
${fields.join(',')}
|
||||
${primary_key ? `, PRIMARY KEY (${primary_key.slice(0, -1)})` : ''}
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=${data.characterSet} COLLATE=utf8mb4_bin COMMENT='${data.tableComment}';`;
|
||||
|
||||
SqlExecBox({
|
||||
sql: sql,
|
||||
dbId: props.dbId as any,
|
||||
db: props.db,
|
||||
runSuccessCallback: () => {
|
||||
ElMessage.success('创建成功');
|
||||
proxy.$parent.tableInfo({ id: props.dbId });
|
||||
cancel();
|
||||
emit('submit-sql', {tableName: state.tableData.tableName });
|
||||
// cancel();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 对比两个数组,取出被修改过的对象数组
|
||||
* @param oldArr 原对象数组
|
||||
* @param nowArr 修改后的对象数组
|
||||
* @param key 标志对象唯一属性
|
||||
*/
|
||||
const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { del: any[], add: any[], upd: any[] } => {
|
||||
let data = {
|
||||
del: [] as object[], // 删除的数据
|
||||
add: [] as object[], // 新增的数据
|
||||
upd: [] as object[] // 修改的数据
|
||||
}
|
||||
|
||||
// 旧数据为空
|
||||
if (oldArr && Array.isArray(oldArr) && oldArr.length === 0
|
||||
&& nowArr && Array.isArray(nowArr) && nowArr.length > 0) {
|
||||
data.add = nowArr;
|
||||
return data;
|
||||
}
|
||||
|
||||
// 新数据为空
|
||||
if (nowArr && Array.isArray(nowArr) && nowArr.length === 0
|
||||
&& oldArr && Array.isArray(oldArr) && oldArr.length > 0) {
|
||||
data.del = oldArr;
|
||||
return data;
|
||||
}
|
||||
|
||||
let oldMap = {}, newMap = {};
|
||||
oldArr.forEach(a => oldMap[a[key]] = a)
|
||||
|
||||
nowArr.forEach(a => {
|
||||
let k = a[key]
|
||||
newMap[k] = a;
|
||||
if (!oldMap.hasOwnProperty(k)) {// 新增
|
||||
data.add.push(a)
|
||||
}
|
||||
})
|
||||
|
||||
oldArr.forEach(a => {
|
||||
let k = a[key];
|
||||
let newData = newMap[k];
|
||||
if (!newData) { // 删除
|
||||
data.del.push(a)
|
||||
} else { // 判断每个字段是否相等,否则为修改
|
||||
for (let f in a) {
|
||||
let oldV = a[f]
|
||||
let newV = newData[f]
|
||||
if (oldV.toString() !== newV.toString()) {
|
||||
data.upd.push(newData)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return data;
|
||||
}
|
||||
|
||||
const genSql = () => {
|
||||
|
||||
const genColumnBasicSql = (cl: any) => {
|
||||
let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : '\'' + cl.value + '\'') : '';
|
||||
let defVal = `${val ? ('DEFAULT ' + val) : ''}`;
|
||||
let length = cl.length ? `(${cl.length})` : '';
|
||||
let onUpdate = 'update_time' === cl.name ? ' ON UPDATE CURRENT_TIMESTAMP ' : ''
|
||||
return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${cl.auto_increment ? 'AUTO_INCREMENT' : ''} ${defVal} ${onUpdate} comment '${cl.remark || ''}' `
|
||||
}
|
||||
|
||||
let data = state.tableData;
|
||||
// 创建表
|
||||
if (!props.data?.edit) {
|
||||
if (state.activeName === '1') {// 创建表结构
|
||||
let primary_key = '';
|
||||
let fields: string[] = [];
|
||||
data.fields.res.forEach((item) => {
|
||||
item.name && fields.push(genColumnBasicSql(item));
|
||||
if (item.pri) {
|
||||
primary_key += `${item.name},`;
|
||||
}
|
||||
});
|
||||
|
||||
return `CREATE TABLE ${data.tableName}
|
||||
( ${fields.join(',')}
|
||||
${primary_key ? `, PRIMARY KEY (${primary_key.slice(0, -1)})` : ''}
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=${data.characterSet} COLLATE =${data.collation} COMMENT='${data.tableComment}';`;
|
||||
|
||||
} else if (state.activeName === '2' && data.indexs.res.length > 0) { // 创建索引
|
||||
let sql = `ALTER TABLE ${data.tableName}`;
|
||||
state.tableData.indexs.res.forEach(a => {
|
||||
sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
|
||||
})
|
||||
return sql.substring(0, sql.length - 1) + ';'
|
||||
}
|
||||
} else { // 修改
|
||||
let addSql = '', updSql = '', delSql = '';
|
||||
if (state.activeName === '1') {// 修改列
|
||||
let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name')
|
||||
if (changeData.add.length > 0) {
|
||||
addSql = `ALTER TABLE ${data.tableName}`
|
||||
changeData.add.forEach(a => {
|
||||
addSql += ` ADD ${genColumnBasicSql(a)},`
|
||||
})
|
||||
addSql = addSql.substring(0, addSql.length - 1)
|
||||
addSql += ';'
|
||||
}
|
||||
|
||||
if (changeData.upd.length > 0) {
|
||||
updSql = `ALTER TABLE ${data.tableName}`;
|
||||
changeData.upd.forEach(a => {
|
||||
updSql += ` MODIFY ${genColumnBasicSql(a)},`
|
||||
})
|
||||
updSql = updSql.substring(0, updSql.length - 1)
|
||||
updSql += ';'
|
||||
}
|
||||
|
||||
if (changeData.del.length > 0) {
|
||||
changeData.del.forEach(a => {
|
||||
delSql += ` ALTER TABLE ${data.tableName} DROP COLUMN ${a.name}; `
|
||||
})
|
||||
}
|
||||
return addSql + updSql + delSql;
|
||||
|
||||
} else if (state.activeName === '2') { // 修改索引
|
||||
let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName')
|
||||
// 搜集修改和删除的索引,添加到drop index xx
|
||||
// 收集新增和修改的索引,添加到ADD xx
|
||||
// ALTER TABLE `test1`
|
||||
// DROP INDEX `test1_name_uindex`,
|
||||
// DROP INDEX `test1_column_name4_index`,
|
||||
// ADD UNIQUE INDEX `test1_name_uindex`(`id`) USING BTREE COMMENT 'ASDASD',
|
||||
// ADD INDEX `111`(`column_name4`) USING BTREE COMMENT 'zasf';
|
||||
|
||||
let dropIndexNames: string[] = [];
|
||||
let addIndexs: any[] = [];
|
||||
|
||||
if (changeData.upd.length > 0) {
|
||||
changeData.upd.forEach(a => {
|
||||
dropIndexNames.push(a.indexName)
|
||||
addIndexs.push(a)
|
||||
})
|
||||
}
|
||||
|
||||
if (changeData.del.length > 0) {
|
||||
changeData.del.forEach(a => {
|
||||
dropIndexNames.push(a.indexName)
|
||||
})
|
||||
}
|
||||
|
||||
if (changeData.add.length > 0) {
|
||||
changeData.add.forEach(a => {
|
||||
addIndexs.push(a)
|
||||
})
|
||||
}
|
||||
|
||||
if (dropIndexNames.length > 0 || addIndexs.length > 0) {
|
||||
let sql = `ALTER TABLE ${data.tableName} `;
|
||||
if (dropIndexNames.length > 0) {
|
||||
dropIndexNames.forEach(a => {
|
||||
sql += `DROP INDEX ${a},`
|
||||
})
|
||||
sql = sql.substring(0, sql.length - 1)
|
||||
}
|
||||
|
||||
if (addIndexs.length > 0) {
|
||||
if (dropIndexNames.length > 0){
|
||||
sql += ','
|
||||
}
|
||||
addIndexs.forEach(a => {
|
||||
sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
|
||||
})
|
||||
sql = sql.substring(0, sql.length - 1)
|
||||
}
|
||||
return sql;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
formRef.value.resetFields();
|
||||
state.activeName = '1'
|
||||
formRef.value.resetFields()
|
||||
state.tableData.tableName = ''
|
||||
state.tableData.tableComment = ''
|
||||
state.tableData.fields.res = [
|
||||
{
|
||||
name: '',
|
||||
@@ -227,17 +537,64 @@ export default defineComponent({
|
||||
remark: '',
|
||||
},
|
||||
];
|
||||
state.tableData.indexs.res = [{
|
||||
indexName: '',
|
||||
columnNames: [],
|
||||
unique: false,
|
||||
indexType: 'BTREE',
|
||||
indexComment: '',
|
||||
},]
|
||||
};
|
||||
return {
|
||||
...toRefs(state),
|
||||
formRef,
|
||||
cancel,
|
||||
reset,
|
||||
addRow,
|
||||
deleteRow,
|
||||
submit,
|
||||
|
||||
const oldData = { indexs: [] as any[], fields: [] as any[] }
|
||||
watch(() => props.data, (newValue: any) => {
|
||||
const { row, indexs, columns } = newValue;
|
||||
// 回显表名表注释
|
||||
state.tableData.tableName = row.tableName
|
||||
state.tableData.tableComment = row.tableComment
|
||||
// 回显列
|
||||
if (columns && Array.isArray(columns) && columns.length > 0) {
|
||||
oldData.fields = [];
|
||||
state.tableData.fields.res = [];
|
||||
// 索引列下拉选
|
||||
state.tableData.indexs.columns = [];
|
||||
columns.forEach(a => {
|
||||
let typeObj = a.columnType.replace(')', '').split('(')
|
||||
let type = typeObj[0];
|
||||
let length = typeObj.length > 1 && typeObj[1] || '';
|
||||
let data = {
|
||||
name: a.columnName,
|
||||
type,
|
||||
value: a.columnDefault || '',
|
||||
length,
|
||||
notNull: a.nullable !== 'YES',
|
||||
pri: a.columnKey === 'PRI',
|
||||
auto_increment: a.extra?.indexOf('auto_increment') > -1,
|
||||
remark: a.columnComment,
|
||||
};
|
||||
},
|
||||
});
|
||||
state.tableData.fields.res.push(data)
|
||||
oldData.fields.push(JSON.parse(JSON.stringify(data)))
|
||||
// 索引字段下拉选项
|
||||
state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment })
|
||||
})
|
||||
}
|
||||
// 回显索引
|
||||
if (indexs && Array.isArray(indexs) && indexs.length > 0) {
|
||||
oldData.indexs = [];
|
||||
state.tableData.indexs.res = [];
|
||||
// 索引过滤掉主键
|
||||
indexs.filter(a => a.indexName !== "PRIMARY").forEach(a => {
|
||||
let data = {
|
||||
indexName: a.indexName,
|
||||
columnNames: a.columnName?.split(','),
|
||||
unique: a.nonUnique === 0 || false,
|
||||
indexType: a.indexType,
|
||||
indexComment: a.indexComment,
|
||||
}
|
||||
state.tableData.indexs.res.push(data)
|
||||
oldData.indexs.push(JSON.parse(JSON.stringify(data)))
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
|
||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
|
||||
:destroy-on-close="true" width="38%">
|
||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
|
||||
<el-tabs v-model="tabActiveName">
|
||||
<el-tab-pane label="基础信息" name="basic">
|
||||
<el-form-item prop="tagId" label="标签:" required>
|
||||
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
|
||||
</el-form-item>
|
||||
@@ -17,7 +20,8 @@
|
||||
</el-form-item>
|
||||
<el-form-item prop="host" label="host:" required>
|
||||
<el-col :span="18">
|
||||
<el-input :disabled="form.id" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
|
||||
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip"
|
||||
auto-complete="off"></el-input>
|
||||
</el-col>
|
||||
<el-col style="text-align: center" :span="1">:</el-col>
|
||||
<el-col :span="5">
|
||||
@@ -28,42 +32,30 @@
|
||||
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password" label="密码:">
|
||||
<el-input
|
||||
type="password"
|
||||
show-password
|
||||
v-model.trim="form.password"
|
||||
placeholder="请输入密码,修改操作可不填"
|
||||
autocomplete="new-password"
|
||||
>
|
||||
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
|
||||
autocomplete="new-password">
|
||||
<template v-if="form.id && form.id != 0" #suffix>
|
||||
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
|
||||
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
|
||||
:content="pwd">
|
||||
<template #reference>
|
||||
<el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
|
||||
<el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码
|
||||
</el-link>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="params" label="连接参数:">
|
||||
<el-input v-model="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="database" label="数据库名:" required>
|
||||
<el-col :span="19">
|
||||
<el-select
|
||||
@change="changeDatabase"
|
||||
v-model="databaseList"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
filterable
|
||||
allow-create
|
||||
placeholder="请确保数据库实例信息填写完整后获取库名"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
|
||||
collapse-tags-tooltip filterable allow-create placeholder="请确保数据库实例信息填写完整后获取库名"
|
||||
style="width: 100%">
|
||||
<el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col style="text-align: center" :span="1"><el-divider direction="vertical" border-style="dashed" /></el-col>
|
||||
<el-col style="text-align: center" :span="1">
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link>
|
||||
</el-col>
|
||||
@@ -72,24 +64,24 @@
|
||||
<el-form-item prop="remark" label="备注:">
|
||||
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
|
||||
<el-col :span="3">
|
||||
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1" :false-label="-1"></el-checkbox>
|
||||
</el-col>
|
||||
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
|
||||
<el-col :span="19" v-if="form.enableSshTunnel == 1">
|
||||
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
|
||||
<el-option
|
||||
v-for="item in sshTunnelMachineList"
|
||||
:key="item.id"
|
||||
:label="`${item.ip}:${item.port} [${item.name}]`"
|
||||
:value="item.id"
|
||||
>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-tab-pane label="其他配置" name="other">
|
||||
<el-form-item prop="params" label="连接参数:">
|
||||
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
|
||||
<template #suffix>
|
||||
<el-link target="_blank" href="https://github.com/go-sql-driver/mysql#parameters"
|
||||
:underline="false" type="primary" class="mr5">参数参考</el-link>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
|
||||
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
@@ -102,79 +94,35 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { toRefs, reactive, watch, ref } from 'vue';
|
||||
import { dbApi } from './api';
|
||||
import { machineApi } from '../machine/api.ts';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { notBlank } from '@/common/assert';
|
||||
import { RsaEncrypt } from '@/common/rsa';
|
||||
import TagSelect from '../component/TagSelect.vue';
|
||||
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DbEdit',
|
||||
components: {
|
||||
TagSelect,
|
||||
},
|
||||
props: {
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
},
|
||||
projects: {
|
||||
type: Array,
|
||||
},
|
||||
db: {
|
||||
type: [Boolean, Object],
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
setup(props: any, { emit }) {
|
||||
const dbForm: any = ref(null);
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
projects: [],
|
||||
envs: [],
|
||||
allDatabases: [] as any,
|
||||
databaseList: [] as any,
|
||||
sshTunnelMachineList: [] as any,
|
||||
form: {
|
||||
id: null,
|
||||
tagId: null as any,
|
||||
tagPath: null as any,
|
||||
type: null,
|
||||
name: null,
|
||||
host: '',
|
||||
port: 3306,
|
||||
username: null,
|
||||
password: null,
|
||||
params: null,
|
||||
database: '',
|
||||
project: null,
|
||||
projectId: null,
|
||||
envId: null,
|
||||
env: null,
|
||||
remark: '',
|
||||
enableSshTunnel: null,
|
||||
sshTunnelMachineId: null,
|
||||
},
|
||||
// 原密码
|
||||
pwd: '',
|
||||
btnLoading: false,
|
||||
rules: {
|
||||
projectId: [
|
||||
//定义事件
|
||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
|
||||
|
||||
const rules = {
|
||||
tagId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择项目',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
envId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择环境',
|
||||
message: '请选择标签',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
@@ -213,25 +161,59 @@ export default defineComponent({
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const dbForm: any = ref(null);
|
||||
|
||||
const state = reactive({
|
||||
dialogVisible: false,
|
||||
tabActiveName: 'basic',
|
||||
allDatabases: [] as any,
|
||||
databaseList: [] as any,
|
||||
form: {
|
||||
id: null,
|
||||
tagId: null as any,
|
||||
tagPath: null as any,
|
||||
type: null,
|
||||
name: null,
|
||||
host: '',
|
||||
port: 3306,
|
||||
username: null,
|
||||
password: null,
|
||||
params: null,
|
||||
database: '',
|
||||
remark: '',
|
||||
sshTunnelMachineId: null as any,
|
||||
},
|
||||
// 原密码
|
||||
pwd: '',
|
||||
btnLoading: false,
|
||||
});
|
||||
|
||||
watch(props, (newValue) => {
|
||||
const {
|
||||
dialogVisible,
|
||||
tabActiveName,
|
||||
allDatabases,
|
||||
databaseList,
|
||||
form,
|
||||
pwd,
|
||||
btnLoading,
|
||||
} = toRefs(state)
|
||||
|
||||
watch(props, (newValue: any) => {
|
||||
state.dialogVisible = newValue.visible;
|
||||
if (!state.dialogVisible) {
|
||||
return;
|
||||
}
|
||||
state.projects = newValue.projects;
|
||||
state.tabActiveName = 'basic';
|
||||
if (newValue.db) {
|
||||
state.form = { ...newValue.db };
|
||||
// 将数据库名使用空格切割,获取所有数据库列表
|
||||
state.databaseList = newValue.db.database.split(' ');
|
||||
} else {
|
||||
state.envs = [];
|
||||
state.form = { port: 3306, enableSshTunnel: -1 } as any;
|
||||
state.form = { port: 3306 } as any;
|
||||
state.databaseList = [];
|
||||
}
|
||||
getSshTunnelMachines();
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -241,21 +223,6 @@ export default defineComponent({
|
||||
state.form.database = state.databaseList.length == 0 ? '' : state.databaseList.join(' ');
|
||||
};
|
||||
|
||||
const getSshTunnelMachines = async () => {
|
||||
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
|
||||
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
|
||||
state.sshTunnelMachineList = res.list;
|
||||
}
|
||||
};
|
||||
|
||||
const changeEnv = (envId: number) => {
|
||||
for (let p of state.envs as any) {
|
||||
if (p.id == envId) {
|
||||
state.form.env = p.name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getAllDatabase = async () => {
|
||||
const reqForm = { ...state.form };
|
||||
reqForm.password = await RsaEncrypt(reqForm.password);
|
||||
@@ -275,6 +242,9 @@ export default defineComponent({
|
||||
if (valid) {
|
||||
const reqForm = { ...state.form };
|
||||
reqForm.password = await RsaEncrypt(reqForm.password);
|
||||
if (!state.form.sshTunnelMachineId) {
|
||||
reqForm.sshTunnelMachineId = -1;
|
||||
}
|
||||
dbApi.saveDb.request(reqForm).then(() => {
|
||||
ElMessage.success('保存成功');
|
||||
emit('val-change', state.form);
|
||||
@@ -304,20 +274,5 @@ export default defineComponent({
|
||||
resetInputDb();
|
||||
}, 500);
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
dbForm,
|
||||
getAllDatabase,
|
||||
getDbPwd,
|
||||
changeDatabase,
|
||||
getSshTunnelMachines,
|
||||
changeEnv,
|
||||
btnOk,
|
||||
cancel,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
</style>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
<div class="db-list">
|
||||
<el-card>
|
||||
<el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button>
|
||||
<el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary" icon="edit">编辑</el-button>
|
||||
<el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger" icon="delete"
|
||||
>删除</el-button
|
||||
>
|
||||
<el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary"
|
||||
icon="edit">编辑</el-button>
|
||||
<el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger"
|
||||
icon="delete">删除</el-button>
|
||||
<div style="float: right">
|
||||
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
|
||||
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
|
||||
</el-select>
|
||||
<el-button v-waves type="primary" icon="search" @click="search()" class="ml5">查询</el-button>
|
||||
<el-button type="success" icon="search" @click="search()" class="ml5"></el-button>
|
||||
</div>
|
||||
<el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip stripe>
|
||||
<el-table-column label="选择" width="60px">
|
||||
@@ -20,7 +20,14 @@
|
||||
</el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<tag-info :tag-path="scope.row.tagPath" />
|
||||
<span class="ml5">
|
||||
{{ scope.row.tagPath }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" label="名称" min-width="160" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column min-width="170" label="host:port" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
@@ -30,56 +37,55 @@
|
||||
<el-table-column prop="type" label="类型" min-width="90"></el-table-column>
|
||||
<el-table-column prop="database" label="数据库" min-width="80">
|
||||
<template #default="scope">
|
||||
<el-popover :width="250" placement="right" trigger="click">
|
||||
<el-popover placement="right" trigger="click" :width="300">
|
||||
<template #reference>
|
||||
<el-link type="primary" :underline="false" plain>查看</el-link>
|
||||
<el-link type="primary" :underline="false" plain @click="selectDb(scope.row.dbs)">查看
|
||||
</el-link>
|
||||
</template>
|
||||
<el-tag
|
||||
@click="showTableInfo(scope.row, db)"
|
||||
effect="plain"
|
||||
type="success"
|
||||
size="small"
|
||||
v-for="db in scope.row.dbs"
|
||||
:key="db"
|
||||
style="cursor: pointer; margin-left: 3px; margin-bottom: 3px"
|
||||
>{{ db }}</el-tag
|
||||
>
|
||||
<el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索"
|
||||
size="small">
|
||||
<template #prefix>
|
||||
<el-icon class="el-input__icon">
|
||||
<search-icon />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
<div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
|
||||
style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative">
|
||||
<el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
|
||||
<el-link type="primary" plain size="small" :underline="false"
|
||||
@click="showTableInfo(scope.row, db)" style="position: absolute; right: 4px">操作
|
||||
</el-link>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
|
||||
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
|
||||
<el-table-column min-width="160" prop="createTime" label="创建时间" show-overflow-tooltip>
|
||||
<el-table-column label="操作" min-width="160" fixed="right">
|
||||
<template #default="scope">
|
||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" min-width="120" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-link type="primary" plain size="small" :underline="false" @click="onShowSqlExec(scope.row)">SQL执行记录</el-link>
|
||||
<el-link plain size="small" :underline="false" @click="showInfo(scope.row)">
|
||||
详情</el-link>
|
||||
<el-divider direction="vertical" border-style="dashed" />
|
||||
<el-link class="ml5" type="primary" plain size="small" :underline="false"
|
||||
@click="onShowSqlExec(scope.row)">
|
||||
SQL执行记录</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-row style="margin-top: 20px" type="flex" justify="end">
|
||||
<el-pagination
|
||||
style="text-align: right"
|
||||
@current-change="handlePageChange"
|
||||
:total="total"
|
||||
layout="prev, pager, next, total, jumper"
|
||||
v-model:current-page="query.pageNum"
|
||||
:page-size="query.pageSize"
|
||||
></el-pagination>
|
||||
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
|
||||
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
|
||||
:page-size="query.pageSize"></el-pagination>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
||||
<el-dialog width="75%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
|
||||
<el-dialog width="80%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
|
||||
<el-row class="mb10">
|
||||
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right">
|
||||
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
|
||||
<template #reference>
|
||||
<el-button class="ml5" type="success" size="small" @click="showDumpInfo = !showDumpInfo">导出</el-button>
|
||||
<el-button class="ml5" type="success" size="small">导出</el-button>
|
||||
</template>
|
||||
<el-form-item label="导出内容: ">
|
||||
<el-radio-group v-model="dumpInfo.type">
|
||||
@@ -90,10 +96,13 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="导出表: ">
|
||||
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tableInfoDialog.infos">
|
||||
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small"
|
||||
:data="tableInfoDialog.infos">
|
||||
<el-table-column type="selection" width="45" />
|
||||
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
|
||||
@@ -103,52 +112,45 @@
|
||||
</div>
|
||||
</el-popover>
|
||||
|
||||
<el-button type="primary" size="small" @click="tableCreateDialog.visible = true">创建表</el-button>
|
||||
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
|
||||
</el-row>
|
||||
<el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small">
|
||||
<el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small"
|
||||
max-height="680">
|
||||
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
|
||||
<el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤"
|
||||
clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
|
||||
<template #header>
|
||||
<el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤" clearable />
|
||||
<el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤"
|
||||
clearable />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="tableRows"
|
||||
label="Rows"
|
||||
min-width="70"
|
||||
sortable
|
||||
:sort-method="(a, b) => parseInt(a.tableRows) - parseInt(b.tableRows)"
|
||||
></el-table-column>
|
||||
<el-table-column
|
||||
property="dataLength"
|
||||
label="数据大小"
|
||||
sortable
|
||||
:sort-method="(a, b) => parseInt(a.dataLength) - parseInt(b.dataLength)"
|
||||
>
|
||||
<el-table-column prop="tableRows" label="Rows" min-width="70" sortable
|
||||
:sort-method="(a: any, b: any) => parseInt(a.tableRows) - parseInt(b.tableRows)"></el-table-column>
|
||||
<el-table-column property="dataLength" label="数据大小" sortable
|
||||
:sort-method="(a: any, b: any) => parseInt(a.dataLength) - parseInt(b.dataLength)">
|
||||
<template #default="scope">
|
||||
{{ formatByteSize(scope.row.dataLength) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
property="indexLength"
|
||||
label="索引大小"
|
||||
sortable
|
||||
:sort-method="(a, b) => parseInt(a.indexLength) - parseInt(b.indexLength)"
|
||||
>
|
||||
<el-table-column property="indexLength" label="索引大小" sortable
|
||||
:sort-method="(a: any, b: any) => parseInt(a.indexLength) - parseInt(b.indexLength)">
|
||||
<template #default="scope">
|
||||
{{ formatByteSize(scope.row.indexLength) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="createTime" label="创建时间" min-width="150"> </el-table-column>
|
||||
<el-table-column label="更多信息" min-width="100">
|
||||
<el-table-column label="更多信息" min-width="140">
|
||||
<template #default="scope">
|
||||
<el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
|
||||
<el-link class="ml5" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
|
||||
<el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">SQL</el-link>
|
||||
<el-link class="ml5"
|
||||
v-if="tableCreateDialog.enableEditTypes.indexOf(tableCreateDialog.type) > -1"
|
||||
@click.prevent="openEditTable(scope.row)" type="warning">编辑表</el-link>
|
||||
<el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" min-width="80">
|
||||
@@ -159,19 +161,18 @@
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
width="90%"
|
||||
:title="`${sqlExecLogDialog.title} - SQL执行记录`"
|
||||
:before-close="onBeforeCloseSqlExecDialog"
|
||||
v-model="sqlExecLogDialog.visible"
|
||||
>
|
||||
<el-dialog width="90%" :title="`${sqlExecLogDialog.title} - SQL执行记录`" :before-close="onBeforeCloseSqlExecDialog"
|
||||
v-model="sqlExecLogDialog.visible">
|
||||
<div class="toolbar">
|
||||
<el-select v-model="sqlExecLogDialog.query.db" placeholder="请选择数据库" filterable clearable>
|
||||
<el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item"> </el-option>
|
||||
<el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5" style="width: 180px" />
|
||||
<el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5"
|
||||
style="width: 180px" />
|
||||
<el-select v-model="sqlExecLogDialog.query.type" placeholder="请选择操作类型" clearable class="ml5">
|
||||
<el-option v-for="item in enums.DbSqlExecTypeEnum" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
||||
<el-option v-for="item in enums.DbSqlExecTypeEnum as any" :key="item.value" :label="item.label"
|
||||
:value="item.value"> </el-option>
|
||||
</el-select>
|
||||
<el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
|
||||
</div>
|
||||
@@ -180,9 +181,14 @@
|
||||
<el-table-column prop="table" label="表" min-width="60" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.UPDATE.value" color="#E4F5EB" size="small">UPDATE</el-tag>
|
||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.DELETE.value" color="#F9E2AE" size="small">DELETE</el-tag>
|
||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.INSERT.value" color="#A8DEE0" size="small">INSERT</el-tag>
|
||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value" color="#E4F5EB"
|
||||
size="small">UPDATE</el-tag>
|
||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value" color="#F9E2AE"
|
||||
size="small">DELETE</el-tag>
|
||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['INSERT'].value" color="#A8DEE0"
|
||||
size="small">INSERT</el-tag>
|
||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['QUERY'].value" color="#A8DEE0"
|
||||
size="small">QUERY</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
|
||||
@@ -197,31 +203,23 @@
|
||||
<el-table-column label="操作" min-width="50" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-link
|
||||
v-if="scope.row.type == enums.DbSqlExecTypeEnum.UPDATE.value || scope.row.type == enums.DbSqlExecTypeEnum.DELETE.value"
|
||||
type="primary"
|
||||
plain
|
||||
size="small"
|
||||
:underline="false"
|
||||
@click="onShowRollbackSql(scope.row)"
|
||||
>还原SQL</el-link
|
||||
>
|
||||
v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value || scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value"
|
||||
type="primary" plain size="small" :underline="false" @click="onShowRollbackSql(scope.row)">
|
||||
还原SQL</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-row style="margin-top: 20px" type="flex" justify="end">
|
||||
<el-pagination
|
||||
style="text-align: right"
|
||||
@current-change="handleSqlExecPageChange"
|
||||
:total="sqlExecLogDialog.total"
|
||||
layout="prev, pager, next, total, jumper"
|
||||
v-model:current-page="sqlExecLogDialog.query.pageNum"
|
||||
:page-size="sqlExecLogDialog.query.pageSize"
|
||||
></el-pagination>
|
||||
<el-pagination style="text-align: right" @current-change="handleSqlExecPageChange"
|
||||
:total="sqlExecLogDialog.total" layout="prev, pager, next, total, jumper"
|
||||
v-model:current-page="sqlExecLogDialog.query.pageNum" :page-size="sqlExecLogDialog.query.pageSize">
|
||||
</el-pagination>
|
||||
</el-row>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
|
||||
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql" size="small"> </el-input>
|
||||
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql"
|
||||
size="small"> </el-input>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
|
||||
@@ -235,59 +233,87 @@
|
||||
|
||||
<el-dialog width="40%" :title="`${chooseTableName} 索引信息`" v-model="indexDialog.visible">
|
||||
<el-table border stripe :data="indexDialog.indexs" size="small">
|
||||
<el-table-column prop="indexName" label="索引名" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="columnName" label="列名" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="indexName" label="索引名" min-width="120" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="columnName" label="列名" min-width="120" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="indexType" label="类型"> </el-table-column>
|
||||
<el-table-column prop="indexComment" label="备注" min-width="230" show-overflow-tooltip> </el-table-column>
|
||||
<el-table-column prop="indexComment" label="备注" min-width="130" show-overflow-tooltip>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog width="55%" :title="`${chooseTableName} Create-DDL`" v-model="ddlDialog.visible">
|
||||
<el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl" size="small"> </el-input>
|
||||
<el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl"
|
||||
size="small"> </el-input>
|
||||
</el-dialog>
|
||||
|
||||
<db-edit
|
||||
@val-change="valChange"
|
||||
:title="dbEditDialog.title"
|
||||
v-model:visible="dbEditDialog.visible"
|
||||
v-model:db="dbEditDialog.data"
|
||||
></db-edit>
|
||||
<create-table :dbId="dbId" :db="db" v-model:visible="tableCreateDialog.visible"></create-table>
|
||||
<el-dialog v-model="infoDialog.visible">
|
||||
<el-descriptions title="详情" :column="3" border>
|
||||
<el-descriptions-item :span="1.5" label="id">{{ infoDialog.data.id }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1.5" label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data.tagPath }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="主机">{{ infoDialog.data.host }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="类型">{{ infoDialog.data.type }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="连接参数">{{ infoDialog.data.params }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
|
||||
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data.database }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
|
||||
<db-edit @val-change="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible"
|
||||
v-model:db="dbEditDialog.data"></db-edit>
|
||||
<create-table :title="tableCreateDialog.title" :active-name="tableCreateDialog.activeName" :dbId="dbId" :db="db"
|
||||
:data="tableCreateDialog.data" v-model:visible="tableCreateDialog.visible" @submit-sql="onSubmitSql">
|
||||
</create-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang='ts'>
|
||||
import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
|
||||
<script lang='ts' setup>
|
||||
import { toRefs, reactive, computed, onMounted, defineAsyncComponent } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { formatByteSize } from '@/common/utils/format';
|
||||
import DbEdit from './DbEdit.vue';
|
||||
import CreateTable from './CreateTable.vue';
|
||||
import { dbApi } from './api';
|
||||
import enums from './enums';
|
||||
import SqlExecBox from './component/SqlExecBox.ts';
|
||||
import SqlExecBox from './component/SqlExecBox';
|
||||
import config from '@/common/config';
|
||||
import { getSession } from '@/common/utils/storage';
|
||||
import { isTrue } from '@/common/assert';
|
||||
import { tagApi } from '../tag/api.ts';
|
||||
import { Search as SearchIcon } from '@element-plus/icons-vue'
|
||||
import { tagApi } from '../tag/api';
|
||||
import { dateFormat } from '@/common/utils/date';
|
||||
import TagInfo from '../component/TagInfo.vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DbList',
|
||||
components: {
|
||||
DbEdit,
|
||||
CreateTable,
|
||||
},
|
||||
setup() {
|
||||
const state = reactive({
|
||||
dbId: 0,
|
||||
db: '',
|
||||
permissions: {
|
||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
||||
const CreateTable = defineAsyncComponent(() => import('./CreateTable.vue'));
|
||||
|
||||
const permissions = {
|
||||
saveDb: 'db:save',
|
||||
delDb: 'db:del',
|
||||
},
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
row: {},
|
||||
dbId: 0,
|
||||
db: '',
|
||||
tags: [],
|
||||
chooseId: null,
|
||||
chooseId: null as any,
|
||||
/**
|
||||
* 选中的数据
|
||||
*/
|
||||
@@ -302,6 +328,10 @@ export default defineComponent({
|
||||
},
|
||||
datas: [],
|
||||
total: 0,
|
||||
infoDialog: {
|
||||
visible: false,
|
||||
data: null as any,
|
||||
},
|
||||
showDumpInfo: false,
|
||||
dumpInfo: {
|
||||
id: 0,
|
||||
@@ -351,14 +381,53 @@ export default defineComponent({
|
||||
},
|
||||
dbEditDialog: {
|
||||
visible: false,
|
||||
data: null,
|
||||
data: null as any,
|
||||
title: '新增数据库',
|
||||
},
|
||||
tableCreateDialog: {
|
||||
title: '创建表',
|
||||
visible: false,
|
||||
activeName: '1',
|
||||
type: '',
|
||||
enableEditTypes: ['mysql'], // 支持"编辑表"的数据库类型
|
||||
data: { // 修改表时,传递修改数据
|
||||
edit: false,
|
||||
row: {},
|
||||
indexs: [],
|
||||
columns: [],
|
||||
},
|
||||
},
|
||||
filterDb: {
|
||||
param: '',
|
||||
cache: [],
|
||||
list: [],
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
dbId,
|
||||
db,
|
||||
tags,
|
||||
chooseId,
|
||||
query,
|
||||
datas,
|
||||
total,
|
||||
infoDialog,
|
||||
showDumpInfo,
|
||||
dumpInfo,
|
||||
sqlExecLogDialog,
|
||||
rollbackSqlDialog,
|
||||
chooseTableName,
|
||||
tableInfoDialog,
|
||||
columnDialog,
|
||||
indexDialog,
|
||||
ddlDialog,
|
||||
dbEditDialog,
|
||||
tableCreateDialog,
|
||||
filterDb,
|
||||
} = toRefs(state)
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
search();
|
||||
});
|
||||
@@ -407,6 +476,11 @@ export default defineComponent({
|
||||
search();
|
||||
};
|
||||
|
||||
const showInfo = (info: any) => {
|
||||
state.infoDialog.data = info;
|
||||
state.infoDialog.visible = true;
|
||||
}
|
||||
|
||||
const getTags = async () => {
|
||||
state.tags = await tagApi.getAccountTags.request(null);
|
||||
};
|
||||
@@ -499,7 +573,7 @@ export default defineComponent({
|
||||
|
||||
const onShowRollbackSql = async (sqlExecLog: any) => {
|
||||
const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
|
||||
const primaryKey = columns[0].columnName;
|
||||
const primaryKey = getPrimaryKey(columns);
|
||||
const oldValue = JSON.parse(sqlExecLog.oldValue);
|
||||
|
||||
const rollbackSqls = [];
|
||||
@@ -529,6 +603,14 @@ export default defineComponent({
|
||||
state.rollbackSqlDialog.visible = true;
|
||||
};
|
||||
|
||||
const getPrimaryKey = (columns: any) => {
|
||||
const col = columns.find((c: any) => c.columnKey == 'PRI');
|
||||
if (col) {
|
||||
return col.columnName;
|
||||
}
|
||||
return columns[0].columnName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装值,如果值类型为number则直接返回,其他则需要使用''包装
|
||||
*/
|
||||
@@ -544,7 +626,9 @@ export default defineComponent({
|
||||
state.tableInfoDialog.visible = true;
|
||||
try {
|
||||
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id, db });
|
||||
state.tableCreateDialog.type = row.type
|
||||
state.dbId = row.id;
|
||||
state.row = row;
|
||||
state.db = db;
|
||||
} catch (e) {
|
||||
state.tableInfoDialog.visible = false;
|
||||
@@ -553,6 +637,11 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmitSql = async (row: { tableName: string }) => {
|
||||
await openEditTable(row)
|
||||
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.dbId, db: state.db });
|
||||
}
|
||||
|
||||
const closeTableInfo = () => {
|
||||
state.showDumpInfo = false;
|
||||
state.tableInfoDialog.visible = false;
|
||||
@@ -588,7 +677,7 @@ export default defineComponent({
|
||||
db: state.db,
|
||||
tableName: row.tableName,
|
||||
});
|
||||
state.ddlDialog.ddl = res[0]['Create Table'];
|
||||
state.ddlDialog.ddl = res;
|
||||
state.ddlDialog.visible = true;
|
||||
};
|
||||
|
||||
@@ -614,35 +703,49 @@ export default defineComponent({
|
||||
} catch (err) { }
|
||||
};
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
dateFormat,
|
||||
getTags,
|
||||
filterTableInfos,
|
||||
enums,
|
||||
search,
|
||||
choose,
|
||||
handlePageChange,
|
||||
editDb,
|
||||
valChange,
|
||||
deleteDb,
|
||||
onShowSqlExec,
|
||||
handleDumpTableSelectionChange,
|
||||
dump,
|
||||
onBeforeCloseSqlExecDialog,
|
||||
handleSqlExecPageChange,
|
||||
searchSqlExecLog,
|
||||
onShowRollbackSql,
|
||||
showTableInfo,
|
||||
closeTableInfo,
|
||||
showColumns,
|
||||
showTableIndex,
|
||||
showCreateDdl,
|
||||
dropTable,
|
||||
formatByteSize,
|
||||
};
|
||||
},
|
||||
// 点击查看时初始化数据
|
||||
const selectDb = (row: any) => {
|
||||
state.filterDb.param = ''
|
||||
state.filterDb.cache = row;
|
||||
state.filterDb.list = row;
|
||||
}
|
||||
|
||||
// 输入字符过滤schema
|
||||
const filterSchema = () => {
|
||||
if (state.filterDb.param) {
|
||||
state.filterDb.list = state.filterDb.cache.filter((a) => { return String(a).toLowerCase().indexOf(state.filterDb.param) > -1 })
|
||||
} else {
|
||||
state.filterDb.list = state.filterDb.cache;
|
||||
}
|
||||
}
|
||||
|
||||
// 打开编辑表
|
||||
const openEditTable = async (row: any) => {
|
||||
|
||||
state.tableCreateDialog.visible = true
|
||||
state.tableCreateDialog.activeName = '1'
|
||||
|
||||
if (row === false) {
|
||||
state.tableCreateDialog.data = { edit: false, row: {}, indexs: [], columns: [] }
|
||||
state.tableCreateDialog.title = '创建表'
|
||||
}
|
||||
|
||||
if (row.tableName) {
|
||||
state.tableCreateDialog.title = '修改表'
|
||||
let indexs = await dbApi.tableIndex.request({
|
||||
id: state.chooseId,
|
||||
db: state.db,
|
||||
tableName: row.tableName,
|
||||
});
|
||||
let columns = await dbApi.columnMetadata.request({
|
||||
id: state.chooseId,
|
||||
db: state.db,
|
||||
tableName: row.tableName,
|
||||
});
|
||||
state.tableCreateDialog.data = { edit: true, row, indexs, columns }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user