mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 16:00:25 +08:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
f2119b2c52 | ||
|
|
f15c45793b |
@@ -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/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"/>
|
<img src="https://img.shields.io/github/forks/may-fly/mayfly-go.svg?style=social" alt="github fork"/>
|
||||||
</a>
|
</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">
|
<a href="https://github.com/golang/go" target="_blank">
|
||||||
<img src="https://img.shields.io/badge/Golang-1.18%2B-yellow.svg" alt="golang"/>
|
<img src="https://img.shields.io/badge/Golang-1.18%2B-yellow.svg" alt="golang"/>
|
||||||
</a>
|
</a>
|
||||||
@@ -34,7 +37,7 @@ web版 **linux(终端[终端回放] 文件 脚本 进程)、数据库(mysql po
|
|||||||
### 系统相关资料
|
### 系统相关资料
|
||||||
- 项目文档: https://objs.gitee.io/mayfly-go-docs
|
- 项目文档: https://objs.gitee.io/mayfly-go-docs
|
||||||
- 系统操作视频: https://space.bilibili.com/484091081/channel/collectiondetail?sid=392854
|
- 系统操作视频: 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
|
toFolder=$1
|
||||||
os=$2
|
os=$2
|
||||||
arch=$3
|
arch=$3
|
||||||
copyStatic=$4
|
copyDocScript=$4
|
||||||
|
|
||||||
echo_yellow "-------------------${os}-${arch}打包构建开始-------------------"
|
echo_yellow "-------------------${os}-${arch}打包构建开始-------------------"
|
||||||
|
|
||||||
@@ -68,17 +68,19 @@ function build() {
|
|||||||
echo_green "移动二进制文件至'${toFolder}'"
|
echo_green "移动二进制文件至'${toFolder}'"
|
||||||
mv ${server_folder}/${execFileName} ${toFolder}
|
mv ${server_folder}/${execFileName} ${toFolder}
|
||||||
|
|
||||||
if [ "${copy2Server}" == "1" ] ; then
|
# if [ "${copy2Server}" == "1" ] ; then
|
||||||
echo_green "拷贝前端静态页面至'${toFolder}/static'"
|
# echo_green "拷贝前端静态页面至'${toFolder}/static'"
|
||||||
mkdir -p ${toFolder}/static && cp -r ${web_folder}/dist/* ${toFolder}/static
|
# mkdir -p ${toFolder}/static && cp -r ${web_folder}/dist/* ${toFolder}/static
|
||||||
fi
|
# fi
|
||||||
|
|
||||||
|
if [ "${copyDocScript}" == "1" ] ; then
|
||||||
echo_green "拷贝脚本等资源文件[config.yml、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
|
echo_green "拷贝脚本等资源文件[config.yml、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
|
||||||
cp ${server_folder}/config.yml ${toFolder}
|
cp ${server_folder}/config.yml ${toFolder}
|
||||||
cp ${server_folder}/mayfly-go.sql ${toFolder}
|
cp ${server_folder}/mayfly-go.sql ${toFolder}
|
||||||
cp ${server_folder}/readme.txt ${toFolder}
|
cp ${server_folder}/readme.txt ${toFolder}
|
||||||
cp ${server_folder}/startup.sh ${toFolder}
|
cp ${server_folder}/startup.sh ${toFolder}
|
||||||
cp ${server_folder}/shutdown.sh ${toFolder}
|
cp ${server_folder}/shutdown.sh ${toFolder}
|
||||||
|
fi
|
||||||
|
|
||||||
echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
|
echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
|
||||||
}
|
}
|
||||||
@@ -99,45 +101,100 @@ function buildMac() {
|
|||||||
build "$1/mayfly-go-mac" "darwin" "amd64" $2
|
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="mayflygo/mayfly-go:${imageVersion}"
|
||||||
|
docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
|
||||||
|
echo_green "docker多版本镜像构建完成->[${imageName}]"
|
||||||
|
echo_yellow "-------------------docker buildx构建镜像结束-------------------"
|
||||||
|
}
|
||||||
|
|
||||||
function runBuild() {
|
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
|
if [ ! -d ${toPath} ] ; then
|
||||||
echo_red "构建产物输出目录不存在!"
|
echo_red "构建产物输出目录不存在!"
|
||||||
exit;
|
exit;
|
||||||
fi
|
fi
|
||||||
|
if [ "${toPath}" == "" ] ; then
|
||||||
|
toPath="."
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "是否拷贝文档&脚本[0->否 1->是][默认是]: " copyDocScript
|
||||||
|
if [ "${copyDocScript}" == "" ] ; then
|
||||||
|
copyDocScript="1"
|
||||||
|
fi
|
||||||
|
|
||||||
# 进入目标路径,并赋值全路径
|
# 进入目标路径,并赋值全路径
|
||||||
cd ${toPath}
|
cd ${toPath}
|
||||||
toPath=`pwd`
|
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
|
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
|
case ${buildType} in
|
||||||
"1")
|
"1")
|
||||||
buildLinuxAmd64 ${toPath} ${runBuildWeb}
|
buildLinuxAmd64 ${toPath} ${copyDocScript}
|
||||||
;;
|
;;
|
||||||
"2")
|
"2")
|
||||||
buildLinuxArm64 ${toPath} ${runBuildWeb}
|
buildLinuxArm64 ${toPath} ${copyDocScript}
|
||||||
;;
|
;;
|
||||||
"3")
|
"3")
|
||||||
buildWindows ${toPath} ${runBuildWeb}
|
buildWindows ${toPath} ${copyDocScript}
|
||||||
;;
|
;;
|
||||||
"4")
|
"4")
|
||||||
buildMac ${toPath} ${runBuildWeb}
|
buildMac ${toPath} ${copyDocScript}
|
||||||
|
;;
|
||||||
|
"5")
|
||||||
|
buildDocker ${imageVersion}
|
||||||
|
;;
|
||||||
|
"6")
|
||||||
|
buildxDocker ${imageVersion}
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
buildLinuxAmd64 ${toPath} ${runBuildWeb}
|
buildLinuxAmd64 ${toPath} ${copyDocScript}
|
||||||
buildLinuxArm64 ${toPath} ${runBuildWeb}
|
buildLinuxArm64 ${toPath} ${copyDocScript}
|
||||||
buildWindows ${toPath} ${runBuildWeb}
|
buildWindows ${toPath} ${copyDocScript}
|
||||||
buildMac ${toPath} ${runBuildWeb}
|
buildMac ${toPath} ${copyDocScript}
|
||||||
;;
|
;;
|
||||||
esac
|
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
|
runBuild
|
||||||
@@ -2,4 +2,4 @@
|
|||||||
ENV = 'production'
|
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',
|
parser: '@typescript-eslint/parser',
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
},
|
},
|
||||||
extends: ['plugin:vue/essential'],
|
extends: ['plugin:vue/vue3-essential', 'plugin:vue/essential', 'eslint:recommended'],
|
||||||
// plugins: ['vue', '@typescript-eslint'],
|
plugins: ['vue', '@typescript-eslint'],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: ['*.ts', '*.tsx', '*.vue'],
|
||||||
|
rules: {
|
||||||
|
'no-undef': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
rules: {
|
rules: {
|
||||||
// http://eslint.cn/docs/rules/
|
// http://eslint.cn/docs/rules/
|
||||||
// https://eslint.vuejs.org/rules/
|
// https://eslint.vuejs.org/rules/
|
||||||
'@type-eslint/ban-ts-ignore': 'off',
|
// https://typescript-eslint.io/rules/no-unused-vars/
|
||||||
'@type-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||||
'@type-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@type-eslint/no-var-requires': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
'@type-eslint/no-empty-function': 'off',
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
'@type-eslint/no-use-before-define': 'off',
|
'@typescript-eslint/no-empty-function': 'off',
|
||||||
'@type-eslint/ban-ts-comment': 'off',
|
'@typescript-eslint/no-use-before-define': 'off',
|
||||||
'@type-eslint/ban-types': 'off',
|
'@typescript-eslint/ban-ts-comment': 'off',
|
||||||
'@type-eslint/no-non-null-assertion': 'off',
|
'@typescript-eslint/ban-types': 'off',
|
||||||
'@type-eslint/explicit-module-boundary-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/custom-event-name-casing': 'off',
|
||||||
'vue/attributes-order': 'off',
|
'vue/attributes-order': 'off',
|
||||||
'vue/one-component-per-file': 'off',
|
'vue/one-component-per-file': 'off',
|
||||||
@@ -43,6 +55,12 @@ module.exports = {
|
|||||||
'vue/no-v-html': 'off',
|
'vue/no-v-html': 'off',
|
||||||
'vue/comment-directive': 'off',
|
'vue/comment-directive': 'off',
|
||||||
'vue/no-parsing-error': '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-use-before-define': 'off',
|
||||||
'no-restricted-globals': 'off',
|
'no-restricted-globals': 'off',
|
||||||
'no-restricted-syntax': 'off',
|
'no-restricted-syntax': 'off',
|
||||||
@@ -51,5 +69,8 @@ module.exports = {
|
|||||||
'no-multiple-template-root': 'off',
|
'no-multiple-template-root': 'off',
|
||||||
'no-unused-vars': 'error',
|
'no-unused-vars': 'error',
|
||||||
'no-v-model-argument': 'off',
|
'no-v-model-argument': 'off',
|
||||||
|
'no-case-declarations': 'off',
|
||||||
|
'no-console': 'error',
|
||||||
|
'no-redeclare': 'off',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -4,32 +4,34 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"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/"
|
"lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.0.10",
|
"@element-plus/icons-vue": "^2.1.0",
|
||||||
"asciinema-player": "^3.0.1",
|
"asciinema-player": "^3.2.0",
|
||||||
"axios": "^1.2.0",
|
"axios": "^1.3.4",
|
||||||
"countup.js": "^2.0.7",
|
"countup.js": "^2.0.7",
|
||||||
"cropperjs": "^1.5.11",
|
"cropperjs": "^1.5.11",
|
||||||
"echarts": "^5.4.0",
|
"echarts": "^5.4.0",
|
||||||
"element-plus": "^2.2.26",
|
"element-plus": "^2.3.2",
|
||||||
"jsencrypt": "^3.2.1",
|
"jsencrypt": "^3.3.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
"monaco-editor": "^0.34.1",
|
"monaco-editor": "^0.37.1",
|
||||||
"monaco-sql-languages": "^0.9.5",
|
"monaco-sql-languages": "^0.11.0",
|
||||||
"monaco-themes": "^0.4.2",
|
"monaco-themes": "^0.4.2",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "^2.0.33",
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
"sortablejs": "^1.13.0",
|
"sortablejs": "^1.13.0",
|
||||||
"sql-formatter": "^9.2.0",
|
"sql-formatter": "^12.1.2",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.47",
|
||||||
"vue-clipboard3": "^1.0.1",
|
"vue-clipboard3": "^1.0.1",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vuex": "^4.0.2",
|
"xterm": "^5.1.0",
|
||||||
"xterm": "^5.0.0",
|
"xterm-addon-fit": "^0.7.0"
|
||||||
"xterm-addon-fit": "^0.6.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.14.178",
|
"@types/lodash": "^4.14.178",
|
||||||
@@ -38,16 +40,16 @@
|
|||||||
"@types/sortablejs": "^1.10.6",
|
"@types/sortablejs": "^1.10.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||||
"@typescript-eslint/parser": "^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",
|
"@vue/compiler-sfc": "^3.0.11",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"eslint": "^8.5.0",
|
"eslint": "^8.35.0",
|
||||||
"eslint-plugin-vue": "^8.2.0",
|
"eslint-plugin-vue": "^8.2.0",
|
||||||
"prettier": "^2.3.0",
|
"prettier": "^2.3.0",
|
||||||
"sass": "^1.45.1",
|
"sass": "^1.58.0",
|
||||||
"sass-loader": "^12.4.0",
|
"sass-loader": "^13.2.0",
|
||||||
"typescript": "^4.7.4",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^2.9.13",
|
"vite": "^4.2.0",
|
||||||
"vue-eslint-parser": "^8.0.1"
|
"vue-eslint-parser": "^8.0.1"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
1
mayfly_go_web/plugins.d.ts
vendored
1
mayfly_go_web/plugins.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
declare module 'vue-grid-layout';
|
|
||||||
@@ -1,34 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view v-show="getThemeConfig.lockScreenTime !== 0" />
|
<router-view v-show="themeConfig.lockScreenTime !== 0" />
|
||||||
<LockScreen v-if="getThemeConfig.isLockScreen" />
|
<LockScreen v-if="themeConfig.isLockScreen" />
|
||||||
<Setings ref="setingsRef" v-show="getThemeConfig.lockScreenTime !== 0" />
|
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts" name="app">
|
||||||
import { computed, ref, getCurrentInstance, onBeforeMount, onMounted, onUnmounted, nextTick, defineComponent, watch } from 'vue';
|
import { ref, onBeforeMount, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useStore } from '@/store/index.ts';
|
// import { useTagsViewRoutes } from '@/store/tagsViewRoutes';
|
||||||
import { getLocal } from '@/common/utils/storage.ts';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
|
import { getLocal } from '@/common/utils/storage';
|
||||||
import LockScreen from '@/views/layout/lockScreen/index.vue';
|
import LockScreen from '@/views/layout/lockScreen/index.vue';
|
||||||
import Setings from '@/views/layout/navBars/breadcrumb/setings.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 setingsRef = ref();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const store = useStore();
|
|
||||||
// 获取布局配置信息
|
const themeConfigStores = useThemeConfig();
|
||||||
const getThemeConfig = computed(() => {
|
const { themeConfig } = storeToRefs(themeConfigStores);
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 布局配置弹窗打开
|
// 布局配置弹窗打开
|
||||||
const openSetingsDrawer = () => {
|
const openSetingsDrawer = () => {
|
||||||
setingsRef.value.openDrawer();
|
setingsRef.value.openDrawer();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置初始化,防止刷新时恢复默认
|
// 设置初始化,防止刷新时恢复默认
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
// 设置批量第三方 icon 图标
|
// 设置批量第三方 icon 图标
|
||||||
@@ -36,24 +34,27 @@ export default defineComponent({
|
|||||||
// // 设置批量第三方 js
|
// // 设置批量第三方 js
|
||||||
// setIntroduction.jsCdn();
|
// setIntroduction.jsCdn();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 监听布局配置弹窗点击打开
|
// 监听布局配置弹窗点击打开
|
||||||
proxy.mittBus.on('openSetingsDrawer', () => {
|
mittBus.on('openSetingsDrawer', () => {
|
||||||
openSetingsDrawer();
|
openSetingsDrawer();
|
||||||
});
|
});
|
||||||
// 获取缓存中的布局配置
|
// 获取缓存中的布局配置
|
||||||
if (getLocal('themeConfig')) {
|
if (getLocal('themeConfig')) {
|
||||||
store.dispatch('themeConfig/setThemeConfig', getLocal('themeConfig'));
|
themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') })
|
||||||
document.documentElement.style.cssText = getLocal('themeConfigStyle');
|
document.documentElement.style.cssText = getLocal('themeConfigStyle');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 页面销毁时,关闭监听布局配置
|
// 页面销毁时,关闭监听布局配置
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
proxy.mittBus.off('openSetingsDrawer', () => {});
|
mittBus.off('openSetingsDrawer', () => { });
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听路由的变化,设置网站标题
|
// 监听路由的变化,设置网站标题
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
@@ -61,14 +62,8 @@ export default defineComponent({
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 路由变化更新水印
|
// 路由变化更新水印
|
||||||
Watermark.use();
|
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>
|
</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;
|
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
|
* 获取权限的完整url
|
||||||
*/
|
*/
|
||||||
@@ -46,7 +28,7 @@ class Api {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作该权限,即请求对应的url
|
* 操作该权限,即请求对应的url
|
||||||
* @param {Object} param 请求该权限的参数
|
* @param {Object} param 请求该api的参数
|
||||||
*/
|
*/
|
||||||
request(param: any = null, options: any = null): Promise<any> {
|
request(param: any = null, options: any = null): Promise<any> {
|
||||||
return request.send(this, param, options);
|
return request.send(this, param, options);
|
||||||
@@ -54,7 +36,8 @@ class Api {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 操作该权限,即请求对应的url
|
* 操作该权限,即请求对应的url
|
||||||
* @param {Object} param 请求该权限的参数
|
* @param {Object} param 请求该api的参数
|
||||||
|
* @param headers headers
|
||||||
*/
|
*/
|
||||||
requestWithHeaders(param: any, headers: any): Promise<any> {
|
requestWithHeaders(param: any, headers: any): Promise<any> {
|
||||||
return request.sendWithHeaders(this, param, headers);
|
return request.sendWithHeaders(this, param, headers);
|
||||||
@@ -68,9 +51,41 @@ class Api {
|
|||||||
* @param url url
|
* @param url url
|
||||||
* @param method 请求方法(get,post,put,delete...)
|
* @param method 请求方法(get,post,put,delete...)
|
||||||
*/
|
*/
|
||||||
static create(url: string, method: string) {
|
static create(url: string, method: string) :Api {
|
||||||
return new Api(url, method);
|
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,9 +1,17 @@
|
|||||||
|
function getBaseApiUrl() {
|
||||||
|
let path = window.location.pathname;
|
||||||
|
if (path == '/') {
|
||||||
|
return window.location.host;
|
||||||
|
}
|
||||||
|
return window.location.host + path;
|
||||||
|
}
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl}/api`,
|
baseApiUrl: `${(window as any).globalConfig.BaseApiUrl || location.protocol + '//' + getBaseApiUrl()}/api`,
|
||||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${location.host}`}/api`,
|
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||||
|
|
||||||
// 系统版本
|
// 系统版本
|
||||||
version: 'v1.3.1'
|
version: 'v1.4.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import request from './request'
|
import Api from './Api'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
login: (param: any) => request.request('POST', '/sys/accounts/login', param),
|
login: Api.newPost("/sys/accounts/login"),
|
||||||
changePwd: (param: any) => request.request('POST', '/sys/accounts/change-pwd', param),
|
changePwd: Api.newPost("/sys/accounts/change-pwd"),
|
||||||
getPublicKey: () => request.request('GET', '/common/public-key'),
|
getPublicKey: Api.newGet("/common/public-key"),
|
||||||
getConfigValue: (param: any) => request.request('GET', '/sys/configs/value', param),
|
getConfigValue: Api.newGet("/sys/configs/value"),
|
||||||
captcha: () => request.request('GET', '/sys/captcha'),
|
captcha: Api.newGet("/sys/captcha"),
|
||||||
logout: (param: any) => request.request('POST', '/sys/accounts/logout/{token}', param),
|
logout: Api.newPost("/sys/accounts/logout/{token}"),
|
||||||
getMenuRoute: (param: any) => request.request('Get', '/sys/resources/account', param)
|
getPermissions: Api.newGet("/sys/accounts/permissions")
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,8 @@ export interface Result {
|
|||||||
data?: any;
|
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
|
query.headers = headers
|
||||||
}
|
}
|
||||||
|
|
||||||
const lowMethod = method.toLowerCase();
|
|
||||||
// post和put使用json格式传参
|
// post和put使用json格式传参
|
||||||
if (lowMethod === 'post' || lowMethod === 'put') {
|
if (method === 'post' || method === 'put') {
|
||||||
query.data = params;
|
query.data = params;
|
||||||
} else {
|
} else {
|
||||||
query.params = params;
|
query.params = params;
|
||||||
@@ -155,6 +155,7 @@ function getApiUrl(url: string) {
|
|||||||
return baseUrl + url + '?token=' + getSession('token');
|
return baseUrl + url + '?token=' + getSession('token');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
request,
|
request,
|
||||||
send,
|
send,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export async function getRsaPublicKey() {
|
|||||||
if (publicKey) {
|
if (publicKey) {
|
||||||
return publicKey
|
return publicKey
|
||||||
}
|
}
|
||||||
publicKey = await openApi.getPublicKey() as string
|
publicKey = await openApi.getPublicKey.request() as string
|
||||||
sessionStorage.setItem('RsaPublicKey', publicKey)
|
sessionStorage.setItem('RsaPublicKey', publicKey)
|
||||||
return publicKey
|
return publicKey
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const UseWartermarkConfigKey = "UseWartermark"
|
|||||||
* @returns 配置值
|
* @returns 配置值
|
||||||
*/
|
*/
|
||||||
export async function getConfigValue(key: string) : Promise<string> {
|
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);
|
notify('', data, isLast, indent, false);
|
||||||
return draw.join('');
|
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 { nextTick } from 'vue';
|
||||||
import loadingCss from '@/theme/loading.scss';
|
import loadingCss from "@/theme/loading.scss?inline"
|
||||||
|
|
||||||
// 定义方法
|
// 定义方法
|
||||||
export const NextLoading = {
|
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
|
// 字体图标 url
|
||||||
const cssCdnUrlList: Array<string> = [
|
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
|
// 第三方 js url
|
||||||
const jsCdnUrlList: Array<string> = [];
|
const jsCdnUrlList: Array<string> = [];
|
||||||
|
|
||||||
// 动态设置字体图标
|
// 动态批量设置字体图标
|
||||||
export function setCssCdn() {
|
export function setCssCdn() {
|
||||||
if (cssCdnUrlList.length <= 0) return false;
|
if (cssCdnUrlList.length <= 0) return false;
|
||||||
cssCdnUrlList.map((v) => {
|
cssCdnUrlList.map((v) => {
|
||||||
@@ -18,7 +17,7 @@ export function setCssCdn() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量设置第三方js
|
// 动态批量设置第三方js
|
||||||
export function setJsCdn() {
|
export function setJsCdn() {
|
||||||
if (jsCdnUrlList.length <= 0) return false;
|
if (jsCdnUrlList.length <= 0) return false;
|
||||||
jsCdnUrlList.map((v) => {
|
jsCdnUrlList.map((v) => {
|
||||||
@@ -28,11 +27,17 @@ export function setJsCdn() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置执行函数
|
/**
|
||||||
|
* 批量设置字体图标、动态js
|
||||||
|
* @method cssCdn 动态批量设置字体图标
|
||||||
|
* @method jsCdn 动态批量设置第三方js
|
||||||
|
*/
|
||||||
const setIntroduction = {
|
const setIntroduction = {
|
||||||
|
// 设置css
|
||||||
cssCdn: () => {
|
cssCdn: () => {
|
||||||
setCssCdn();
|
setCssCdn();
|
||||||
},
|
},
|
||||||
|
// 设置js
|
||||||
jsCdn: () => {
|
jsCdn: () => {
|
||||||
setJsCdn();
|
setJsCdn();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import * as svg from '@element-plus/icons-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 = () => {
|
const getAlicdnIconfont = () => {
|
||||||
@@ -9,7 +24,8 @@ const getAlicdnIconfont = () => {
|
|||||||
let sheetsList = [];
|
let sheetsList = [];
|
||||||
let sheetsIconList = [];
|
let sheetsIconList = [];
|
||||||
for (let i = 0; i < styles.length; i++) {
|
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]);
|
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 自带图标
|
// 初始化获取 css 样式,获取 element plus 自带图标
|
||||||
const elementPlusIconfont = () => {
|
const elementPlusIconfont = () => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -76,9 +102,9 @@ const awesomeIconfont = () => {
|
|||||||
|
|
||||||
// 定义导出方法集合
|
// 定义导出方法集合
|
||||||
const initIconfont = {
|
const initIconfont = {
|
||||||
// ali: () => {
|
ali: () => {
|
||||||
// return getAlicdnIconfont();
|
return getLocalAliIconfont();
|
||||||
// },
|
},
|
||||||
ele: () => {
|
ele: () => {
|
||||||
return elementPlusIconfont();
|
return elementPlusIconfont();
|
||||||
},
|
},
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useStore } from '/@/store/index.ts';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
export default {
|
export default {
|
||||||
name: 'auth',
|
name: 'auth',
|
||||||
props: {
|
props: {
|
||||||
@@ -16,10 +16,9 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore();
|
|
||||||
// 获取 vuex 中的用户权限
|
// 获取 vuex 中的用户权限
|
||||||
const getUserAuthBtnList = computed(() => {
|
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 {
|
return {
|
||||||
getUserAuthBtnList,
|
getUserAuthBtnList,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useStore } from '/@/store/index.ts';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
import { judementSameArr } from '/@/utils/arrayOperation.ts';
|
import { judementSameArr } from '/@/utils/arrayOperation.ts';
|
||||||
export default {
|
export default {
|
||||||
name: 'authAll',
|
name: 'authAll',
|
||||||
@@ -17,10 +17,9 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore();
|
|
||||||
// 获取 vuex 中的用户权限
|
// 获取 vuex 中的用户权限
|
||||||
const getUserAuthBtnList = computed(() => {
|
const getUserAuthBtnList = computed(() => {
|
||||||
return judementSameArr(props.value, store.state.userInfos.userInfos.authBtnList);
|
return judementSameArr(props.value, useUserInfo().userInfo.authBtnList);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
getUserAuthBtnList,
|
getUserAuthBtnList,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useStore } from '/@/store/index.ts';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
export default {
|
export default {
|
||||||
name: 'auths',
|
name: 'auths',
|
||||||
props: {
|
props: {
|
||||||
@@ -16,11 +16,10 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore();
|
|
||||||
// 获取 vuex 中的用户权限
|
// 获取 vuex 中的用户权限
|
||||||
const getUserAuthBtnList = computed(() => {
|
const getUserAuthBtnList = computed(() => {
|
||||||
let flag = false;
|
let flag = false;
|
||||||
store.state.userInfos.userInfos.authBtnList.map((val: any) => {
|
useUserInfo().userInfo.authBtnList.map((val: any) => {
|
||||||
props.value.map((v) => {
|
props.value.map((v) => {
|
||||||
if (val === v) flag = true;
|
if (val === v) flag = true;
|
||||||
});
|
});
|
||||||
|
|||||||
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>
|
<template>
|
||||||
<div class="icon-selector">
|
<div class="icon-selector w100 h100">
|
||||||
<el-popover placement="bottom" :width="450" v-model:visible="fontIconVisible" popper-class="icon-selector-popper">
|
|
||||||
<template #reference>
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="fontIconSearch"
|
v-model="state.fontIconSearch"
|
||||||
:placeholder="fontIconPlaceholder"
|
:placeholder="state.fontIconPlaceholder"
|
||||||
:clearable="clearable"
|
:clearable="clearable"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:size="size"
|
:size="size"
|
||||||
@@ -14,72 +12,49 @@
|
|||||||
@blur="onIconBlur"
|
@blur="onIconBlur"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<SvgIcon :name="prepend" class="font14" />
|
<SvgIcon
|
||||||
|
:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
|
||||||
|
class="font14"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</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>
|
</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>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts" name="iconSelector">
|
||||||
import { ref, toRefs, reactive, onMounted, nextTick, computed, watch } from 'vue';
|
import { defineAsyncComponent, ref, reactive, onMounted, nextTick, computed, watch } from 'vue';
|
||||||
import initIconfont from '@/common/utils/getStyleSheets';
|
import type { TabsPaneContext } from 'element-plus';
|
||||||
export default {
|
import initIconfont from '@/common/utils/svgIcons';
|
||||||
name: 'iconSelector',
|
import '@/theme/iconSelector.scss';
|
||||||
emits: ['update:modelValue', 'get', 'clear'],
|
|
||||||
props: {
|
// 定义父组件传过来的值
|
||||||
|
const props = defineProps({
|
||||||
// 输入框前置内容
|
// 输入框前置内容
|
||||||
prepend: {
|
prepend: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -100,11 +75,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: () => '请选择图标',
|
default: () => '请选择图标',
|
||||||
},
|
},
|
||||||
// icon 图标类型
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
default: () => 'ele',
|
|
||||||
},
|
|
||||||
// 禁用
|
// 禁用
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -120,53 +90,129 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: () => '无相关图标',
|
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/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,
|
modelValue: String,
|
||||||
},
|
});
|
||||||
setup(props, { emit }) {
|
|
||||||
|
// 定义子组件向父组件传值/事件
|
||||||
|
const emit = defineEmits(['update:modelValue', 'get', 'clear']);
|
||||||
|
|
||||||
|
// 引入组件
|
||||||
|
const IconList = defineAsyncComponent(() => import('@/components/iconSelector/list.vue'));
|
||||||
|
|
||||||
|
// 定义变量内容
|
||||||
const inputWidthRef = ref();
|
const inputWidthRef = ref();
|
||||||
const selectorScrollbarRef = ref();
|
const state = reactive({
|
||||||
const state: any = reactive({
|
|
||||||
fontIconPrefix: '',
|
fontIconPrefix: '',
|
||||||
fontIconVisible: false,
|
|
||||||
fontIconWidth: 0,
|
fontIconWidth: 0,
|
||||||
fontIconSearch: '',
|
fontIconSearch: '',
|
||||||
fontIconTabsIndex: 0,
|
|
||||||
fontIconSheetsList: [],
|
|
||||||
fontIconPlaceholder: '',
|
fontIconPlaceholder: '',
|
||||||
fontIconType: 'ali',
|
fontIconTabActive: 'ele',
|
||||||
fontIconShow: true,
|
fontIconList: {
|
||||||
|
ali: [],
|
||||||
|
ele: [],
|
||||||
|
awe: [],
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
|
// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
|
||||||
const onIconFocus = () => {
|
const onIconFocus = () => {
|
||||||
state.fontIconVisible = true;
|
|
||||||
if (!props.modelValue) return false;
|
if (!props.modelValue) return false;
|
||||||
state.fontIconSearch = '';
|
state.fontIconSearch = '';
|
||||||
state.fontIconPlaceholder = props.modelValue;
|
state.fontIconPlaceholder = props.modelValue;
|
||||||
};
|
};
|
||||||
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
|
// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
|
||||||
const onIconBlur = () => {
|
const onIconBlur = () => {
|
||||||
state.fontIconVisible = false;
|
const list = fontIconTabNameList();
|
||||||
setTimeout(() => {
|
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 = '';
|
if (icon.length <= 0) state.fontIconSearch = '';
|
||||||
}, 300);
|
}, 300);
|
||||||
};
|
};
|
||||||
// 处理 icon 双向绑定数值回显
|
|
||||||
const initModeValueEcho = () => {
|
|
||||||
if (props.modelValue === '') return false;
|
|
||||||
state.fontIconPlaceholder = props.modelValue;
|
|
||||||
state.fontIconPrefix = props.modelValue;
|
|
||||||
};
|
|
||||||
// 图标搜索及图标数据显示
|
// 图标搜索及图标数据显示
|
||||||
const fontIconSheetsFilterList = computed(() => {
|
const fontIconSheetsFilterList = computed(() => {
|
||||||
if (!state.fontIconSearch) return state.fontIconSheetsList;
|
const list = fontIconTabNameList();
|
||||||
|
if (!state.fontIconSearch) return list;
|
||||||
let search = state.fontIconSearch.trim().toLowerCase();
|
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;
|
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 的宽度
|
// 获取 input 的宽度
|
||||||
const getInputWidth = () => {
|
const getInputWidth = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -179,62 +225,9 @@ export default {
|
|||||||
getInputWidth();
|
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(() => {
|
onMounted(() => {
|
||||||
// 判断默认进来是什么类型图标,进行 tab 回显
|
initFontIconData(initFontIconName());
|
||||||
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);
|
|
||||||
}
|
|
||||||
initResize();
|
initResize();
|
||||||
getInputWidth();
|
getInputWidth();
|
||||||
});
|
});
|
||||||
@@ -243,19 +236,7 @@ export default {
|
|||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
() => {
|
() => {
|
||||||
initModeValueEcho();
|
initModeValueEcho();
|
||||||
|
initFontIconName();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return {
|
|
||||||
inputWidthRef,
|
|
||||||
selectorScrollbarRef,
|
|
||||||
fontIconSheetsFilterList,
|
|
||||||
onColClick,
|
|
||||||
onIconChange,
|
|
||||||
onClearFontIcon,
|
|
||||||
onIconFocus,
|
|
||||||
onIconBlur,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</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,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="monaco-editor" style="border: 1px solid #ccc;">
|
<div class="monaco-editor" style="border: 1px solid #ccc;">
|
||||||
<div ref="monacoTextarea" :style="{ height: height }"></div>
|
<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-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
|
||||||
<el-option v-for="mode in languages" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
<el-option v-for="mode in languages" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
<script lang="ts">
|
<template>
|
||||||
// 渲染函数:https://v3.cn.vuejs.org/guide/render-function.html
|
<i v-if="isShowIconSvg" class="el-icon icon-middle" :style="setIconSvgStyle">
|
||||||
import { h, resolveComponent, defineComponent } from 'vue';
|
<component :is="getIconName" :style="setIconSvgStyle" />
|
||||||
export default defineComponent({
|
</i>
|
||||||
name: 'svgIcon',
|
|
||||||
props: {
|
<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 图标组件名字
|
// svg 图标组件名字
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -11,14 +26,80 @@ export default defineComponent({
|
|||||||
// svg 大小
|
// svg 大小
|
||||||
size: {
|
size: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
default: () => 14,
|
||||||
},
|
},
|
||||||
// svg 颜色
|
// svg 颜色
|
||||||
color: {
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
},
|
isEle: {
|
||||||
setup(props: any) {
|
type: Boolean,
|
||||||
return () => h('i', { class: 'el-icon', style: `--font-size: ${props.size};--color: ${props.color}` }, [h(resolveComponent(`${props.name}`))]);
|
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>
|
</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 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) {
|
export function authDirective(app: App) {
|
||||||
// 单个权限验证(v-auth="xxx")
|
// 单个权限验证(v-auth="xxx")
|
||||||
app.directive('auth', {
|
app.directive('auth', {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
if (!auth(binding.value)) {
|
if (!useUserInfo().userInfo.permissions.some((v: any) => v === binding.value)) {
|
||||||
parseNoAuth(el, binding);
|
parseNoAuth(el, binding);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -14,7 +15,14 @@ export function authDirective(app: App) {
|
|||||||
// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
|
// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
|
||||||
app.directive('auths', {
|
app.directive('auths', {
|
||||||
mounted(el, binding) {
|
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);
|
parseNoAuth(el, binding);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -22,7 +30,7 @@ export function authDirective(app: App) {
|
|||||||
// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
|
// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
|
||||||
app.directive('auth-all', {
|
app.directive('auth-all', {
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
if (!authAll(binding.value)) {
|
if (!judementSameArr(binding.value, useUserInfo().userInfo.permissions)) {
|
||||||
parseNoAuth(el, binding);
|
parseNoAuth(el, binding);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import { authDirective } from '@/common/utils/authDirective.ts';
|
import { authDirective } from './auth';
|
||||||
import { wavesDirective } from '@/common/utils/customDirective.ts';
|
import { wavesDirective } from './waves';
|
||||||
|
|
||||||
// 导出指令方法
|
// 导出指令方法
|
||||||
export function directive(app: App) {
|
export function directive(app: App) {
|
||||||
@@ -1,56 +1,33 @@
|
|||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import App from './App.vue';
|
import App from '@/App.vue';
|
||||||
|
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import { store, key } from './store';
|
import pinia from '@/store/index';
|
||||||
import { directive } from '@/common/utils/directive.ts';
|
import { directive } from '@/directive/index';
|
||||||
import { globalComponentSize } from '@/common/utils/componentSize.ts';
|
import { globalComponentSize } from '@/common/utils/componentSize';
|
||||||
import { dateStrFormat } from '@/common/utils/date.ts'
|
import { registElSvgIcon } from '@/common/utils/svgIcons';
|
||||||
|
|
||||||
import ElementPlus from 'element-plus';
|
import ElementPlus from 'element-plus';
|
||||||
import 'element-plus/dist/index.css';
|
import 'element-plus/dist/index.css';
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||||
import '@/theme/index.scss';
|
|
||||||
import mitt from 'mitt';
|
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
import * as svg from '@element-plus/icons-vue';
|
import '@/theme/index.scss';
|
||||||
import SvgIcon from '@/components/svgIcon/index.vue';
|
|
||||||
import '@/assets/font/font.css'
|
import '@/assets/font/font.css'
|
||||||
|
import '@/assets/iconfont/iconfont.js'
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
/**
|
registElSvgIcon(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);
|
|
||||||
directive(app);
|
directive(app);
|
||||||
|
|
||||||
app.use(router)
|
app.use(pinia)
|
||||||
.use(store, key)
|
.use(router)
|
||||||
.use(ElementPlus, { size: globalComponentSize, locale: zhCn })
|
.use(ElementPlus, { size: globalComponentSize, locale: zhCn })
|
||||||
.mount('#app');
|
.mount('#app');
|
||||||
|
|
||||||
|
// 屏蔽警告信息
|
||||||
// 自定义全局过滤器
|
app.config.warnHandler = () => null;
|
||||||
app.config.globalProperties.$filters = {
|
|
||||||
dateFormat(value: any) {
|
|
||||||
if (!value) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return dateStrFormat('yyyy-MM-dd HH:mm:ss', value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 全局error处理
|
// 全局error处理
|
||||||
app.config.errorHandler = function (err: any, vm, info) {
|
app.config.errorHandler = function (err: any, vm, info) {
|
||||||
// 如果是断言错误,则进行提示即可
|
// 如果是断言错误,则进行提示即可
|
||||||
@@ -60,5 +37,3 @@ app.config.errorHandler = function (err: any, vm, info) {
|
|||||||
console.error(err, 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/Home.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 { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import NProgress from 'nprogress';
|
import NProgress from 'nprogress';
|
||||||
import 'nprogress/nprogress.css';
|
import 'nprogress/nprogress.css';
|
||||||
import { store } from '@/store/index.ts';
|
import { getSession, clearSession } from '@/common/utils/storage';
|
||||||
import { getSession, clearSession } from '@/common/utils/storage.ts';
|
import { templateResolve } from '@/common/utils/string'
|
||||||
import { templateResolve } from '@/common/utils/string.ts'
|
import { NextLoading } from '@/common/utils/loading';
|
||||||
import { NextLoading } from '@/common/utils/loading.ts';
|
import { dynamicRoutes, staticRoutes, pathMatch } from './route'
|
||||||
import { dynamicRoutes, staticRoutes, pathMatch } from './route.ts'
|
|
||||||
import { imports } from './imports';
|
|
||||||
import openApi from '@/common/openApi';
|
import openApi from '@/common/openApi';
|
||||||
import sockets from '@/common/sockets';
|
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({
|
const router = createRouter({
|
||||||
@@ -24,7 +35,7 @@ export function initAllFun() {
|
|||||||
// 无 token 停止执行下一步
|
// 无 token 停止执行下一步
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
store.dispatch('userInfos/setUserInfos'); // 触发初始化用户信息
|
useUserInfo().setUserInfo({});
|
||||||
router.addRoute(pathMatch); // 添加404界面
|
router.addRoute(pathMatch); // 添加404界面
|
||||||
resetRoute(); // 删除/重置路由
|
resetRoute(); // 删除/重置路由
|
||||||
// 添加动态路由
|
// 添加动态路由
|
||||||
@@ -32,23 +43,20 @@ export function initAllFun() {
|
|||||||
router.addRoute((route as unknown) as RouteRecordRaw);
|
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 动画开始执行
|
NextLoading.start(); // 界面 loading 动画开始执行
|
||||||
const token = getSession('token'); // 获取浏览器缓存 token 值
|
const token = getSession('token'); // 获取浏览器缓存 token 值
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// 无 token 停止执行下一步
|
// 无 token 停止执行下一步
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
store.dispatch('userInfos/setUserInfos'); // 触发初始化用户信息
|
useUserInfo().setUserInfo({});
|
||||||
let menuRoute = getSession('menus')
|
// 获取路由
|
||||||
if (!menuRoute) {
|
let menuRoute = await getBackEndControlRoutes();
|
||||||
menuRoute = getBackEndControlRoutes(); // 获取路由
|
|
||||||
// const oldRoutes = res; // 获取接口原始路由(未处理component)
|
|
||||||
}
|
|
||||||
dynamicRoutes[0].children = backEndRouterConverter(menuRoute); // 处理路由(component)
|
dynamicRoutes[0].children = backEndRouterConverter(menuRoute); // 处理路由(component)
|
||||||
// 添加404界面
|
// 添加404界面
|
||||||
router.addRoute(pathMatch);
|
router.addRoute(pathMatch);
|
||||||
@@ -57,12 +65,20 @@ export function initBackEndControlRoutesFun() {
|
|||||||
formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
|
formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
|
||||||
router.addRoute((route as unknown) as RouteRecordRaw);
|
router.addRoute((route as unknown) as RouteRecordRaw);
|
||||||
});
|
});
|
||||||
store.dispatch('routesList/setRoutesList', dynamicRoutes[0].children);
|
useRoutesList().setRoutesList(dynamicRoutes[0].children)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||||
export function getBackEndControlRoutes() {
|
export async function getBackEndControlRoutes() {
|
||||||
return openApi.getMenuRoute({});
|
try {
|
||||||
|
const menuAndPermission = await openApi.getPermissions.request();
|
||||||
|
// 赋值权限码,用于控制按钮等
|
||||||
|
useUserInfo().userInfo.permissions = menuAndPermission.permissions;
|
||||||
|
return menuAndPermission.menus;
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后端控制路由,后端返回路由 转换为vue route
|
// 后端控制路由,后端返回路由 转换为vue route
|
||||||
@@ -76,7 +92,7 @@ export function backEndRouterConverter(routes: any, parentPath: string = "/") {
|
|||||||
item.meta = JSON.parse(item.meta)
|
item.meta = JSON.parse(item.meta)
|
||||||
// 将meta.comoponet 解析为route.component
|
// 将meta.comoponet 解析为route.component
|
||||||
if (item.meta.component) {
|
if (item.meta.component) {
|
||||||
item.component = imports[item.meta.component as string]
|
item.component = dynamicImport(dynamicViewsModules, item.meta.component)
|
||||||
delete item.meta['component']
|
delete item.meta['component']
|
||||||
}
|
}
|
||||||
// route.path == resource.code
|
// 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) {
|
export function formatFlatteningRoutes(arr: any) {
|
||||||
if (arr.length <= 0) return false;
|
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;
|
return newArr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +204,7 @@ export function setFilterRoute(chil: any) {
|
|||||||
chil.forEach((route: any) => {
|
chil.forEach((route: any) => {
|
||||||
// 如果路由需要拥有指定code才可访问,则校验该用户菜单是否存在该code
|
// 如果路由需要拥有指定code才可访问,则校验该用户菜单是否存在该code
|
||||||
if (route.meta.code) {
|
if (route.meta.code) {
|
||||||
store.state.userInfos.userInfos.menus.forEach((m: any) => {
|
useUserInfo().userInfo.menus.forEach((m: any) => {
|
||||||
if (route.meta.code == m) {
|
if (route.meta.code == m) {
|
||||||
filterRoute.push({ ...route })
|
filterRoute.push({ ...route })
|
||||||
}
|
}
|
||||||
@@ -188,33 +225,35 @@ export function setFilterRouteEnd() {
|
|||||||
|
|
||||||
// 删除/重置路由
|
// 删除/重置路由
|
||||||
export function resetRoute() {
|
export function resetRoute() {
|
||||||
store.state.routesList.routesList.forEach((route: any) => {
|
useRoutesList().routesList.forEach((route: any) => {
|
||||||
const { name } = route;
|
const { name } = route;
|
||||||
router.hasRoute(name) && router.removeRoute(name);
|
router.hasRoute(name) && router.removeRoute(name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function initRouter() {
|
||||||
// 初始化方法执行
|
// 初始化方法执行
|
||||||
const { isRequestRoutes } = store.state.themeConfig.themeConfig;
|
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
|
||||||
if (!isRequestRoutes) {
|
if (!isRequestRoutes) {
|
||||||
// 未开启后端控制路由
|
// 未开启后端控制路由
|
||||||
initAllFun();
|
initAllFun();
|
||||||
} else if (isRequestRoutes) {
|
} else if (isRequestRoutes) {
|
||||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||||
initBackEndControlRoutesFun();
|
await initBackEndControlRoutesFun();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let SysWs: any;
|
let SysWs: any;
|
||||||
|
let loadRouter = false;
|
||||||
|
|
||||||
// 路由加载前
|
// 路由加载前
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
NProgress.configure({ showSpinner: false });
|
NProgress.configure({ showSpinner: false });
|
||||||
if (to.meta.title) NProgress.start();
|
if (to.meta.title) NProgress.start();
|
||||||
|
|
||||||
// 如果有标题参数,则再原标题后加上参数来区别
|
// 如果有标题参数,则再原标题后加上参数来区别
|
||||||
if (to.meta.titleRename) {
|
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');
|
const token = getSession('token');
|
||||||
@@ -245,7 +284,12 @@ router.beforeEach((to, from, next) => {
|
|||||||
if (!SysWs && to.path != '/machine/terminal') {
|
if (!SysWs && to.path != '/machine/terminal') {
|
||||||
SysWs = sockets.sysMsgSocket();
|
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();
|
next();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import Layout from '@/views/layout/index.vue'
|
import Layout from '@/views/layout/index.vue'
|
||||||
// import RouterParent from '@/views/layout/routerView/parent.vue';
|
|
||||||
|
|
||||||
// 定义动态路由
|
// 定义动态路由
|
||||||
export const dynamicRoutes = [
|
export const dynamicRoutes = [
|
||||||
@@ -12,6 +11,7 @@ export const dynamicRoutes = [
|
|||||||
meta: {
|
meta: {
|
||||||
isKeepAlive: true,
|
isKeepAlive: true,
|
||||||
},
|
},
|
||||||
|
children: []
|
||||||
// children: [
|
// children: [
|
||||||
// {
|
// {
|
||||||
// path: '/home',
|
// path: '/home',
|
||||||
@@ -124,7 +124,7 @@ export const staticRoutes: Array<RouteRecordRaw> = [
|
|||||||
name: 'login',
|
name: 'login',
|
||||||
component: () => import('@/views/login/index.vue'),
|
component: () => import('@/views/login/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '登陆',
|
title: '登录',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,28 +1,8 @@
|
|||||||
import { InjectionKey } from 'vue';
|
// https://pinia.vuejs.org/
|
||||||
import { createStore, useStore as baseUseStore, Store } from 'vuex';
|
import { createPinia } from 'pinia';
|
||||||
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';
|
|
||||||
import sqlExecInfo from '@/store/modules/mysqlDbOptInfo.ts';
|
|
||||||
import redisDbOptInfo from '@/store/modules/redisDbOptInfo.ts';
|
|
||||||
import mongoDbOptInfo from '@/store/modules/mongoDbOptInfo.ts';
|
|
||||||
|
|
||||||
export const key: InjectionKey<Store<RootStateTypes>> = Symbol();
|
// 创建
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
export const store = createStore<RootStateTypes>({
|
// 导出
|
||||||
modules: {
|
export default pinia;
|
||||||
themeConfig,
|
|
||||||
routesList,
|
|
||||||
keepAliveNames,
|
|
||||||
userInfos,
|
|
||||||
sqlExecInfo,
|
|
||||||
redisDbOptInfo,
|
|
||||||
mongoDbOptInfo,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useStore() {
|
|
||||||
return baseUseStore(key);
|
|
||||||
}
|
|
||||||
|
|||||||
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,30 +0,0 @@
|
|||||||
import { Module } from 'vuex';
|
|
||||||
// 此处加上 `.ts` 后缀报错,具体原因不详
|
|
||||||
import {DbOptInfoState, RootStateTypes} from '@/store/interface';
|
|
||||||
|
|
||||||
const mongoDbOptInfoModule: Module<DbOptInfoState, RootStateTypes> = {
|
|
||||||
namespaced: true,
|
|
||||||
state: {
|
|
||||||
dbOptInfo: {
|
|
||||||
tagPath: '',
|
|
||||||
dbId: 0,
|
|
||||||
db: '0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
// 设置用户信息
|
|
||||||
getMongoDbOptInfo(state: any, data: object) {
|
|
||||||
state.dbOptInfo = data;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
// 设置用户信息
|
|
||||||
async setMongoDbOptInfo({ commit }, data: object) {
|
|
||||||
if (data) {
|
|
||||||
commit('getMongoDbOptInfo', data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default mongoDbOptInfoModule;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Module } from 'vuex';
|
|
||||||
// 此处加上 `.ts` 后缀报错,具体原因不详
|
|
||||||
import { DbOptInfoState, RootStateTypes } from '@/store/interface';
|
|
||||||
|
|
||||||
const sqlExecInfoModule: Module<DbOptInfoState, RootStateTypes> = {
|
|
||||||
namespaced: true,
|
|
||||||
state: {
|
|
||||||
dbOptInfo: {
|
|
||||||
tagPath: '',
|
|
||||||
dbId: 0,
|
|
||||||
db: '0',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
// 设置用户信息
|
|
||||||
getSqlExecInfo(state: any, data: object) {
|
|
||||||
state.dbOptInfo = data;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
// 设置用户信息
|
|
||||||
async setSqlExecInfo({ commit }, data: object) {
|
|
||||||
if (data) {
|
|
||||||
commit('getSqlExecInfo', data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sqlExecInfoModule;
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Module } from 'vuex';
|
|
||||||
// 此处加上 `.ts` 后缀报错,具体原因不详
|
|
||||||
import {DbOptInfoState, RootStateTypes} from '@/store/interface';
|
|
||||||
|
|
||||||
const redisDbOptInfoModule: Module<DbOptInfoState, RootStateTypes> = {
|
|
||||||
namespaced: true,
|
|
||||||
state: {
|
|
||||||
dbOptInfo: {
|
|
||||||
tagPath: '',
|
|
||||||
dbId: 0,
|
|
||||||
db: '0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
// 设置用户信息
|
|
||||||
getRedisDbOptInfo(state: any, data: object) {
|
|
||||||
state.dbOptInfo = data;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
// 设置用户信息
|
|
||||||
async setRedisDbOptInfo({ commit }, data: object) {
|
|
||||||
if (data) {
|
|
||||||
commit('getRedisDbOptInfo', data);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default redisDbOptInfoModule;
|
|
||||||
@@ -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';
|
import { defineStore } from 'pinia';
|
||||||
// 此处加上 `.ts` 后缀报错,具体原因不详
|
|
||||||
import { ThemeConfigState, RootStateTypes } from '@/store/interface/index';
|
|
||||||
|
|
||||||
const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
export const useThemeConfig = defineStore('themeConfig', {
|
||||||
namespaced: true,
|
state: (): ThemeConfigState => ({
|
||||||
state: {
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
// 是否开启布局配置抽屉
|
// 是否开启布局配置抽屉
|
||||||
isDrawer: false,
|
isDrawer: false,
|
||||||
@@ -73,12 +70,13 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
|||||||
isBreadcrumb: true,
|
isBreadcrumb: true,
|
||||||
// 是否开启 Tagsview
|
// 是否开启 Tagsview
|
||||||
isTagsview: true,
|
isTagsview: true,
|
||||||
|
isShareTagsView: false,
|
||||||
// 是否开启 Breadcrumb 图标
|
// 是否开启 Breadcrumb 图标
|
||||||
isBreadcrumbIcon: true,
|
isBreadcrumbIcon: true,
|
||||||
// 是否开启 Tagsview 图标
|
// 是否开启 Tagsview 图标
|
||||||
isTagsviewIcon: true,
|
isTagsviewIcon: true,
|
||||||
// 是否开启 TagsView 缓存
|
// 是否开启 TagsView 缓存
|
||||||
isCacheTagsView: false,
|
isCacheTagsView: true,
|
||||||
// 是否开启 TagsView 拖拽
|
// 是否开启 TagsView 拖拽
|
||||||
isSortableTagsView: true,
|
isSortableTagsView: true,
|
||||||
// 是否开启 Footer 底部版权信息
|
// 是否开启 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
|
// 默认 Tagsview 风格,可选 1、 tags-style-one 2、 tags-style-two 3、 tags-style-three
|
||||||
tagsStyle: 'tags-style-one',
|
tagsStyle: 'tags-style-three',
|
||||||
// 默认主页面切换动画,可选 1、 slide-right 2、 slide-left 3、 opacitys
|
// 默认主页面切换动画,可选 1、 slide-right 2、 slide-left 3、 opacitys
|
||||||
animation: 'slide-right',
|
animation: 'slide-right',
|
||||||
// 默认分栏高亮风格,可选 1、 圆角 columns-round 2、 卡片 columns-card
|
// 默认分栏高亮风格,可选 1、 圆角 columns-round 2、 卡片 columns-card
|
||||||
@@ -107,13 +105,13 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
|||||||
layout: 'classic',
|
layout: 'classic',
|
||||||
|
|
||||||
// ssh终端字体颜色
|
// ssh终端字体颜色
|
||||||
terminalForeground: '#7e9192',
|
terminalForeground: '#C5C8C6',
|
||||||
// ssh终端背景色
|
// ssh终端背景色
|
||||||
terminalBackground: '#002833',
|
terminalBackground: '#121212',
|
||||||
// ssh终端cursor色
|
// ssh终端cursor色
|
||||||
terminalCursor: '#268F81',
|
terminalCursor: '#F0CC09',
|
||||||
terminalFontSize: 15,
|
terminalFontSize: 14,
|
||||||
terminalFontWeight: 'normal',
|
terminalFontWeight: 'bold',
|
||||||
|
|
||||||
// 编辑器主题
|
// 编辑器主题
|
||||||
editorTheme: 'vs',
|
editorTheme: 'vs',
|
||||||
@@ -135,19 +133,11 @@ const themeConfigModule: Module<ThemeConfigState, RootStateTypes> = {
|
|||||||
// 默认全局组件大小,可选值"<|large|default|small>",默认 ''
|
// 默认全局组件大小,可选值"<|large|default|small>",默认 ''
|
||||||
globalComponentSize: '',
|
globalComponentSize: '',
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
mutations: {
|
|
||||||
// 设置布局配置
|
|
||||||
getThemeConfig(state: any, data: object) {
|
|
||||||
state.themeConfig = data;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
actions: {
|
actions: {
|
||||||
// 设置布局配置
|
// 设置布局配置
|
||||||
setThemeConfig({ commit }, data: object) {
|
setThemeConfig(data: ThemeConfigState) {
|
||||||
commit('getThemeConfig', data);
|
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 {
|
.layout-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.layout-aside {
|
.layout-aside {
|
||||||
background: var(--bg-menuBar);
|
background: var(--bg-menuBar);
|
||||||
box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
|
box-shadow: 2px 0 6px rgb(0 21 41 / 1%);
|
||||||
@@ -38,22 +39,27 @@ body,
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
|
|
||||||
.el-scrollbar__view {
|
.el-scrollbar__view {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-header {
|
.layout-header {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-main {
|
.layout-main {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-scrollbar {
|
.el-scrollbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-view-bg-white {
|
.layout-view-bg-white {
|
||||||
background: white;
|
background: white;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -61,33 +67,41 @@ body,
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid #ebeef5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-el-aside-br-color {
|
.layout-el-aside-br-color {
|
||||||
border-right: 1px solid rgb(238, 238, 238);
|
border-right: 1px solid rgb(238, 238, 238);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-aside-width-default {
|
.layout-aside-width-default {
|
||||||
width: 220px !important;
|
width: 220px !important;
|
||||||
transition: width 0.3s ease;
|
transition: width 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-aside-width64 {
|
.layout-aside-width64 {
|
||||||
width: 64px !important;
|
width: 64px !important;
|
||||||
transition: width 0.3s ease;
|
transition: width 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-aside-width1 {
|
.layout-aside-width1 {
|
||||||
width: 1px !important;
|
width: 1px !important;
|
||||||
transition: width 0.3s ease;
|
transition: width 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-scrollbar {
|
.layout-scrollbar {
|
||||||
@extend .el-scrollbar;
|
@extend .el-scrollbar;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-mian-height-50 {
|
.layout-mian-height-50 {
|
||||||
height: calc(100vh - 50px);
|
height: calc(100vh - 50px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-columns-warp {
|
.layout-columns-warp {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-hide {
|
.layout-hide {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -104,6 +118,7 @@ body,
|
|||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
border-bottom: 1px solid rgb(230, 230, 230);
|
border-bottom: 1px solid rgb(230, 230, 230);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-divider {
|
.el-divider {
|
||||||
background-color: rgb(230, 230, 230);
|
background-color: rgb(230, 230, 230);
|
||||||
}
|
}
|
||||||
@@ -123,25 +138,31 @@ body,
|
|||||||
.flex {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-auto {
|
.flex-auto {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-center {
|
.flex-center {
|
||||||
@extend .flex;
|
@extend .flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-margin {
|
.flex-margin {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-warp {
|
.flex-warp {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
margin: 0 -5px;
|
margin: 0 -5px;
|
||||||
|
|
||||||
.flex-warp-item {
|
.flex-warp-item {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
||||||
.flex-warp-item-box {
|
.flex-warp-item-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -154,15 +175,19 @@ body,
|
|||||||
.w100 {
|
.w100 {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h100 {
|
.h100 {
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vh100 {
|
.vh100 {
|
||||||
height: 100vh !important;
|
height: 100vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.max100vh {
|
.max100vh {
|
||||||
max-height: 100vh !important;
|
max-height: 100vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.min100vh {
|
.min100vh {
|
||||||
min-height: 100vh !important;
|
min-height: 100vh !important;
|
||||||
}
|
}
|
||||||
@@ -172,15 +197,19 @@ body,
|
|||||||
.color-primary {
|
.color-primary {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-success {
|
.color-success {
|
||||||
color: var(--color-success);
|
color: var(--color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-warning {
|
.color-warning {
|
||||||
color: var(--color-warning);
|
color: var(--color-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-danger {
|
.color-danger {
|
||||||
color: var(--color-danger);
|
color: var(--color-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-info {
|
.color-info {
|
||||||
color: var(--color-info);
|
color: var(--color-info);
|
||||||
}
|
}
|
||||||
@@ -199,24 +228,31 @@ body,
|
|||||||
.mt#{$i} {
|
.mt#{$i} {
|
||||||
margin-top: #{$i}px !important;
|
margin-top: #{$i}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mr#{$i} {
|
.mr#{$i} {
|
||||||
margin-right: #{$i}px !important;
|
margin-right: #{$i}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mb#{$i} {
|
.mb#{$i} {
|
||||||
margin-bottom: #{$i}px !important;
|
margin-bottom: #{$i}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ml#{$i} {
|
.ml#{$i} {
|
||||||
margin-left: #{$i}px !important;
|
margin-left: #{$i}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pt#{$i} {
|
.pt#{$i} {
|
||||||
padding-top: #{$i}px !important;
|
padding-top: #{$i}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pr#{$i} {
|
.pr#{$i} {
|
||||||
padding-right: #{$i}px !important;
|
padding-right: #{$i}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pb#{$i} {
|
.pb#{$i} {
|
||||||
padding-bottom: #{$i}px !important;
|
padding-bottom: #{$i}px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pl#{$i} {
|
.pl#{$i} {
|
||||||
padding-left: #{$i}px !important;
|
padding-left: #{$i}px !important;
|
||||||
}
|
}
|
||||||
@@ -249,15 +285,22 @@ body,
|
|||||||
.el-menu .fa:not(.is-children) {
|
.el-menu .fa:not(.is-children) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gray-mode {
|
.gray-mode {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.fade-enter-active, .fade-leave-active {
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
transition: opacity .2s ease-in-out;
|
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;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +328,10 @@ body,
|
|||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fr {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.search-form {
|
.search-form {
|
||||||
.el-form-item {
|
.el-form-item {
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
@@ -294,3 +341,7 @@ body,
|
|||||||
.el-table-z-index-inherit .el-table .el-table__cell {
|
.el-table-z-index-inherit .el-table .el-table__cell {
|
||||||
z-index: inherit !important;
|
z-index: inherit !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.f12 {
|
||||||
|
font-size: 12px
|
||||||
|
}
|
||||||
@@ -3,83 +3,27 @@
|
|||||||
.icon-selector-popper {
|
.icon-selector-popper {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
.icon-selector-warp {
|
.icon-selector-warp {
|
||||||
|
height: 260px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
.icon-selector-warp-title {
|
.icon-selector-warp-title {
|
||||||
|
position: absolute;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
padding: 0 15px;
|
left: 15px;
|
||||||
}
|
}
|
||||||
.icon-selector-warp-row {
|
.el-tabs__header {
|
||||||
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 {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
border: 1px solid #ebeef5;
|
justify-content: flex-end;
|
||||||
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 {
|
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
margin-bottom: 10px;
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
}
|
margin: 0 !important;
|
||||||
&-tabs {
|
.el-tabs__nav-wrap {
|
||||||
display: flex;
|
&::after {
|
||||||
height: 30px;
|
height: 0 !important;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
|
||||||
// 布局配置
|
declare interface ThemeConfigState {
|
||||||
export interface ThemeConfigState {
|
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
isDrawer: boolean;
|
isDrawer: boolean;
|
||||||
primary: string;
|
primary: string;
|
||||||
@@ -30,6 +31,7 @@ export interface ThemeConfigState {
|
|||||||
isShowLogoChange: boolean;
|
isShowLogoChange: boolean;
|
||||||
isBreadcrumb: boolean;
|
isBreadcrumb: boolean;
|
||||||
isTagsview: boolean;
|
isTagsview: boolean;
|
||||||
|
isShareTagsView: boolean;
|
||||||
isBreadcrumbIcon: boolean;
|
isBreadcrumbIcon: boolean;
|
||||||
isTagsviewIcon: boolean;
|
isTagsviewIcon: boolean;
|
||||||
isCacheTagsView: boolean;
|
isCacheTagsView: boolean;
|
||||||
@@ -52,48 +54,24 @@ export interface ThemeConfigState {
|
|||||||
terminalBackground: string;
|
terminalBackground: string;
|
||||||
terminalCursor: string;
|
terminalCursor: string;
|
||||||
terminalFontSize: number;
|
terminalFontSize: number;
|
||||||
terminalFontWeight: string;
|
terminalFontWeight: string | any;
|
||||||
editorTheme: string;
|
editorTheme: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagsView 路由列表
|
||||||
|
declare interface TagsViewRoutesState<T = any> {
|
||||||
|
tagsViewRoutes: T[];
|
||||||
|
isTagsViewCurrenFull: Boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// 路由列表
|
// 路由列表
|
||||||
export interface RoutesListState {
|
declare interface RoutesListState {
|
||||||
routesList: Array<object>;
|
routesList: T[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路由缓存列表
|
// 路由缓存列表
|
||||||
export interface KeepAliveNamesState {
|
declare interface KeepAliveNamesState {
|
||||||
keepAliveNames: Array<string>;
|
keepAliveNames: string[];
|
||||||
}
|
cachedViews: string[];
|
||||||
|
|
||||||
// 用户信息
|
|
||||||
export interface UserInfosState {
|
|
||||||
userInfos: object;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据操作信息
|
|
||||||
export interface DbOptInfoState {
|
|
||||||
dbOptInfo: {
|
|
||||||
tagPath?: string,
|
|
||||||
dbId?: number,
|
|
||||||
db?: string,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 后端返回原始路由(未处理时)
|
|
||||||
// export interface RequestOldRoutesState {
|
|
||||||
// requestOldRoutes: Array<object>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 主接口(顶级类型声明)
|
|
||||||
export interface RootStateTypes {
|
|
||||||
themeConfig: ThemeConfigState;
|
|
||||||
routesList: RoutesListState;
|
|
||||||
keepAliveNames: KeepAliveNamesState;
|
|
||||||
userInfos: UserInfosState;
|
|
||||||
sqlExecInfo: DbOptInfoState;
|
|
||||||
redisDbOptInfo: DbOptInfoState;
|
|
||||||
mongoDbOptInfo: DbOptInfoState;
|
|
||||||
// requestOldRoutes: RequestOldRoutesState;
|
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,14 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import {IDisposable} from 'monaco-editor';
|
import {IDisposable} from 'monaco-editor';
|
||||||
|
|
||||||
declare module '*.vue' {
|
|
||||||
import type { DefineComponent } from 'vue';
|
|
||||||
const component: DefineComponent<{}, {}, any>;
|
|
||||||
export default component;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
completionItemProvider?: IDisposable | undefined;
|
completionItemProvider?: IDisposable | undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 申明外部 npm 插件模块
|
||||||
declare module 'sql-formatter';
|
declare module 'sql-formatter';
|
||||||
declare module 'jsoneditor';
|
declare module 'jsoneditor';
|
||||||
declare module 'asciinema-player';
|
declare module 'asciinema-player';
|
||||||
declare module 'monaco-editor';
|
declare module 'monaco-editor';
|
||||||
|
declare module 'vue-grid-layout';
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// 声明一个模块,防止引入文件时报错
|
||||||
declare module '*.json';
|
declare module '*.json';
|
||||||
declare module '*.png';
|
declare module '*.png';
|
||||||
declare module '*.jpg';
|
declare module '*.jpg';
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
<el-col :sm="6" class="mb15">
|
<el-col :sm="6" class="mb15">
|
||||||
<div @click="toPage({ id: 'personal' })" class="home-card-item home-card-first">
|
<div @click="toPage({ id: 'personal' })" class="home-card-item home-card-first">
|
||||||
<div class="flex-margin flex">
|
<div class="flex-margin flex">
|
||||||
<img :src="getUserInfos.photo" />
|
<img :src="userInfo.photo" />
|
||||||
<div class="home-card-first-right ml15">
|
<div class="home-card-first-right ml15">
|
||||||
<div class="flex-margin">
|
<div class="flex-margin">
|
||||||
<div class="home-card-first-right-title">{{ `${currentTime}, ${getUserInfos.username}`
|
<div class="home-card-first-right-title">{{ `${currentTime}, ${userInfo.username}`
|
||||||
}}</div>
|
}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,15 +29,17 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
|
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
|
||||||
import { useStore } from '@/store/index.ts';
|
|
||||||
// import * as echarts from 'echarts';
|
// import * as echarts from 'echarts';
|
||||||
import { CountUp } from 'countup.js';
|
import { CountUp } from 'countup.js';
|
||||||
import { formatAxis } from '@/common/utils/formatTime.ts';
|
import { formatAxis } from '@/common/utils/format.ts';
|
||||||
import { indexApi } from './api';
|
import { indexApi } from './api';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore();
|
const { userInfo } = storeToRefs(useUserInfo());
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
topCardItemList: [
|
topCardItemList: [
|
||||||
{
|
{
|
||||||
@@ -114,11 +116,6 @@ onMounted(() => {
|
|||||||
// initHomeLaboratory();
|
// initHomeLaboratory();
|
||||||
// initHomeOvertime();
|
// initHomeOvertime();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取用户信息 vuex
|
|
||||||
const getUserInfos = computed(() => {
|
|
||||||
return store.state.userInfos.userInfos;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Api from '@/common/Api';
|
import Api from '@/common/Api';
|
||||||
|
|
||||||
export const indexApi = {
|
export const indexApi = {
|
||||||
getIndexCount: Api.create("/common/index/count", 'get'),
|
getIndexCount: Api.newGet("/common/index/count"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,42 +1,43 @@
|
|||||||
<template>
|
<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" />
|
<Logo v-if="setShowLogo" />
|
||||||
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
|
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
|
||||||
<Vertical :menuList="menuList" :class="setCollapseWidth" />
|
<Vertical :menuList="state.menuList" :class="setCollapseWidth" />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</el-aside>
|
</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">
|
<el-aside class="layout-aside w100 h100">
|
||||||
<Logo v-if="setShowLogo" />
|
<Logo v-if="setShowLogo" />
|
||||||
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
|
<el-scrollbar class="flex-auto" ref="layoutAsideScrollbarRef">
|
||||||
<Vertical :menuList="menuList" />
|
<Vertical :menuList="state.menuList" />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutAside">
|
||||||
import { toRefs, reactive, computed, watch, getCurrentInstance, onBeforeMount, onUnmounted } from 'vue';
|
import { reactive, computed, watch, getCurrentInstance, onBeforeMount, onUnmounted } from 'vue';
|
||||||
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 Logo from '@/views/layout/logo/index.vue';
|
import Logo from '@/views/layout/logo/index.vue';
|
||||||
import Vertical from '@/views/layout/navMenu/vertical.vue';
|
import Vertical from '@/views/layout/navMenu/vertical.vue';
|
||||||
export default {
|
import mittBus from '@/common/utils/mitt';
|
||||||
name: 'layoutAside',
|
|
||||||
components: { Logo, Vertical },
|
|
||||||
setup() {
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { proxy } = getCurrentInstance() as any;
|
||||||
const store = useStore();
|
|
||||||
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
|
const { routesList } = storeToRefs(useRoutesList());
|
||||||
|
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
menuList: [],
|
menuList: [],
|
||||||
clientWidth: '',
|
clientWidth: '',
|
||||||
});
|
});
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 设置菜单展开/收起时的宽度
|
// 设置菜单展开/收起时的宽度
|
||||||
const setCollapseWidth = computed(() => {
|
const setCollapseWidth = computed(() => {
|
||||||
let { layout, isCollapse, menuBar } = store.state.themeConfig.themeConfig;
|
let { layout, isCollapse, menuBar } = themeConfig.value;
|
||||||
let asideBrColor =
|
let asideBrColor =
|
||||||
menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
|
menuBar === '#FFFFFF' || menuBar === '#FFF' || menuBar === '#fff' || menuBar === '#ffffff' ? 'layout-el-aside-br-color' : '';
|
||||||
if (layout === 'columns') {
|
if (layout === 'columns') {
|
||||||
@@ -55,16 +56,19 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置显示/隐藏 logo
|
// 设置显示/隐藏 logo
|
||||||
const setShowLogo = computed(() => {
|
const setShowLogo = computed(() => {
|
||||||
let { layout, isShowLogo } = store.state.themeConfig.themeConfig;
|
let { layout, isShowLogo } = themeConfig.value;
|
||||||
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
|
return (isShowLogo && layout === 'defaults') || (isShowLogo && layout === 'columns');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||||
const setFilterRoutes = () => {
|
const setFilterRoutes = () => {
|
||||||
if (store.state.themeConfig.themeConfig.layout === 'columns') return false;
|
if (themeConfig.value.layout === 'columns') return false;
|
||||||
state.menuList = filterRoutesFun(store.state.routesList.routesList);
|
state.menuList = filterRoutesFun(routesList.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 路由过滤递归函数
|
// 路由过滤递归函数
|
||||||
const filterRoutesFun = (arr: Array<object>) => {
|
const filterRoutesFun = (arr: Array<object>) => {
|
||||||
return arr
|
return arr
|
||||||
@@ -79,54 +83,49 @@ export default {
|
|||||||
const initMenuFixed = (clientWidth: number) => {
|
const initMenuFixed = (clientWidth: number) => {
|
||||||
state.clientWidth = clientWidth;
|
state.clientWidth = clientWidth;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||||
watch(store.state.themeConfig.themeConfig, (val) => {
|
watch(themeConfig.value, (val) => {
|
||||||
if (val.isShowLogoChange !== val.isShowLogo) {
|
if (val.isShowLogoChange !== val.isShowLogo) {
|
||||||
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
|
if (!proxy.$refs.layoutAsideScrollbarRef) return false;
|
||||||
proxy.$refs.layoutAsideScrollbarRef.update();
|
proxy.$refs.layoutAsideScrollbarRef.update();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听路由的变化,动态赋值给菜单中
|
// 监听路由的变化,动态赋值给菜单中
|
||||||
watch(store.state, (val) => {
|
watch(pinia.state, (val) => {
|
||||||
if (val.routesList.routesList.length === state.menuList.length) return false;
|
if (val.routesList.routesList.length === state.menuList.length) return false;
|
||||||
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
|
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
|
||||||
if (layout === 'classic' && isClassicSplitMenu) return false;
|
if (layout === 'classic' && isClassicSplitMenu) return false;
|
||||||
setFilterRoutes();
|
setFilterRoutes();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 页面加载前
|
// 页面加载前
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
initMenuFixed(document.body.clientWidth);
|
initMenuFixed(document.body.clientWidth);
|
||||||
setFilterRoutes();
|
setFilterRoutes();
|
||||||
proxy.mittBus.on('setSendColumnsChildren', (res: any) => {
|
mittBus.on('setSendColumnsChildren', (res: any) => {
|
||||||
state.menuList = res.children;
|
state.menuList = res.children;
|
||||||
});
|
});
|
||||||
proxy.mittBus.on('setSendClassicChildren', (res: any) => {
|
mittBus.on('setSendClassicChildren', (res: any) => {
|
||||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||||
if (layout === 'classic' && isClassicSplitMenu) {
|
if (layout === 'classic' && isClassicSplitMenu) {
|
||||||
state.menuList = [];
|
state.menuList = [];
|
||||||
state.menuList = res.children;
|
state.menuList = res.children;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||||
setFilterRoutes();
|
setFilterRoutes();
|
||||||
});
|
});
|
||||||
proxy.mittBus.on('layoutMobileResize', (res: any) => {
|
mittBus.on('layoutMobileResize', (res: any) => {
|
||||||
initMenuFixed(res.clientWidth);
|
initMenuFixed(res.clientWidth);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// 页面卸载时
|
// 页面卸载时
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
proxy.mittBus.off('setSendColumnsChildren');
|
mittBus.off('setSendColumnsChildren');
|
||||||
proxy.mittBus.off('setSendClassicChildren');
|
mittBus.off('setSendClassicChildren');
|
||||||
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
||||||
proxy.mittBus.off('layoutMobileResize');
|
mittBus.off('layoutMobileResize');
|
||||||
});
|
});
|
||||||
return {
|
|
||||||
setCollapseWidth,
|
|
||||||
setShowLogo,
|
|
||||||
getThemeConfig,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -2,18 +2,11 @@
|
|||||||
<div class="layout-columns-aside">
|
<div class="layout-columns-aside">
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li v-for="(v, k) in state.columnsAsideList" :key="k" @click="onColumnsAsideMenuClick(v, k)" :ref="
|
||||||
v-for="(v, k) in columnsAsideList"
|
|
||||||
:key="k"
|
|
||||||
@click="onColumnsAsideMenuClick(v, k)"
|
|
||||||
:ref="
|
|
||||||
(el) => {
|
(el) => {
|
||||||
if (el) columnsAsideOffsetTopRefs[k] = el;
|
if (el) columnsAsideOffsetTopRefs[k] = el;
|
||||||
}
|
}
|
||||||
"
|
" :class="{ 'layout-columns-active': state.liIndex === k }" :title="v.meta.title">
|
||||||
: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)">
|
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.isIframe)">
|
||||||
<i :class="v.meta.icon"></i>
|
<i :class="v.meta.icon"></i>
|
||||||
<div class="layout-columns-aside-li-box-title font12">
|
<div class="layout-columns-aside-li-box-title font12">
|
||||||
@@ -35,17 +28,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutColumnsAside">
|
||||||
import { reactive, toRefs, ref, computed, onMounted, nextTick, getCurrentInstance, watch } from 'vue';
|
import { reactive, ref, computed, onMounted, nextTick, getCurrentInstance, watch } from 'vue';
|
||||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||||
import { useStore } from '@/store/index.ts';
|
import pinia from '@/store/index';
|
||||||
export default {
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
name: 'layoutColumnsAside',
|
import { useRoutesList } from '@/store/routesList';
|
||||||
setup() {
|
import mittBus from '@/common/utils/mitt';
|
||||||
|
|
||||||
const columnsAsideOffsetTopRefs: any = ref([]);
|
const columnsAsideOffsetTopRefs: any = ref([]);
|
||||||
const columnsAsideActiveRef = ref();
|
const columnsAsideActiveRef = ref();
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const store = useStore();
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
@@ -56,7 +48,7 @@ export default {
|
|||||||
});
|
});
|
||||||
// 设置高亮样式
|
// 设置高亮样式
|
||||||
const setColumnsAsideStyle = computed(() => {
|
const setColumnsAsideStyle = computed(() => {
|
||||||
return store.state.themeConfig.themeConfig.columnsAsideStyle;
|
return useThemeConfig().themeConfig.columnsAsideStyle;
|
||||||
});
|
});
|
||||||
// 设置菜单高亮位置移动
|
// 设置菜单高亮位置移动
|
||||||
const setColumnsAsideMove = (k: number) => {
|
const setColumnsAsideMove = (k: number) => {
|
||||||
@@ -78,10 +70,10 @@ export default {
|
|||||||
};
|
};
|
||||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||||
const setFilterRoutes = () => {
|
const setFilterRoutes = () => {
|
||||||
state.columnsAsideList = filterRoutesFun(store.state.routesList.routesList);
|
state.columnsAsideList = filterRoutesFun(useRoutesList().routesList);
|
||||||
const resData: any = setSendChildren(route.path);
|
const resData: any = setSendChildren(route.path);
|
||||||
onColumnsAsideDown(resData.item[0].k);
|
onColumnsAsideDown(resData.item[0].k);
|
||||||
proxy.mittBus.emit('setSendColumnsChildren', resData);
|
mittBus.emit('setSendColumnsChildren', resData);
|
||||||
};
|
};
|
||||||
// 传送当前子级数据到菜单中
|
// 传送当前子级数据到菜单中
|
||||||
const setSendChildren = (path: string) => {
|
const setSendChildren = (path: string) => {
|
||||||
@@ -119,7 +111,7 @@ export default {
|
|||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
// 监听路由的变化,动态赋值给菜单中
|
// 监听路由的变化,动态赋值给菜单中
|
||||||
watch(store.state, (val) => {
|
watch(pinia.state, (val) => {
|
||||||
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
|
val.themeConfig.themeConfig.columnsAsideStyle === 'columnsRound' ? (state.difference = 3) : (state.difference = 0);
|
||||||
if (val.routesList.routesList.length === state.columnsAsideList.length) return false;
|
if (val.routesList.routesList.length === state.columnsAsideList.length) return false;
|
||||||
setFilterRoutes();
|
setFilterRoutes();
|
||||||
@@ -131,18 +123,8 @@ export default {
|
|||||||
// 路由更新时
|
// 路由更新时
|
||||||
onBeforeRouteUpdate((to) => {
|
onBeforeRouteUpdate((to) => {
|
||||||
setColumnsMenuHighlight(to.path);
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -150,8 +132,10 @@ export default {
|
|||||||
width: 64px;
|
width: 64px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--bg-columnsMenuBar);
|
background: var(--bg-columnsMenuBar);
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
color: var(--bg-columnsMenuBarColor);
|
color: var(--bg-columnsMenuBarColor);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -161,21 +145,26 @@ export default {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
.layout-columns-aside-li-box {
|
.layout-columns-aside-li-box {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
|
||||||
.layout-columns-aside-li-box-title {
|
.layout-columns-aside-li-box-title {
|
||||||
padding-top: 1px;
|
padding-top: 1px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--bg-columnsMenuBarColor);
|
color: var(--bg-columnsMenuBarColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-columns-active {
|
.layout-columns-active {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
transition: 0.3s ease-in-out;
|
transition: 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-round {
|
.columns-round {
|
||||||
background: var(--color-primary);
|
background: var(--color-primary);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
@@ -189,6 +178,7 @@ export default {
|
|||||||
transition: 0.3s ease-in-out;
|
transition: 0.3s ease-in-out;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-card {
|
.columns-card {
|
||||||
@extend .columns-round;
|
@extend .columns-round;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|||||||
@@ -6,16 +6,15 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useStore } from '@/store/index.ts';
|
|
||||||
import NavBarsIndex from '@/views/layout/navBars/index.vue';
|
import NavBarsIndex from '@/views/layout/navBars/index.vue';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutHeader',
|
name: 'layoutHeader',
|
||||||
components: { NavBarsIndex },
|
components: { NavBarsIndex },
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
|
||||||
// 设置 header 的高度
|
// 设置 header 的高度
|
||||||
const setHeaderHeight = computed(() => {
|
const setHeaderHeight = computed(() => {
|
||||||
let { isTagsview, layout } = store.state.themeConfig.themeConfig;
|
let { isTagsview, layout } = useThemeConfig().themeConfig;
|
||||||
if (isTagsview && layout !== 'classic') return '84px';
|
if (isTagsview && layout !== 'classic') return '84px';
|
||||||
else return '50px';
|
else return '50px';
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,52 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-main class="layout-main">
|
<el-main class="layout-main">
|
||||||
<el-scrollbar
|
<el-scrollbar class="layout-scrollbar" ref="layoutScrollbarRef"
|
||||||
class="layout-scrollbar"
|
v-show="!state.currentRouteMeta.link && !state.currentRouteMeta.isIframe"
|
||||||
ref="layoutScrollbarRef"
|
:style="{ minHeight: `calc(100vh - ${state.headerHeight}` }">
|
||||||
v-show="!currentRouteMeta.link && !currentRouteMeta.isIframe"
|
|
||||||
:style="{ minHeight: `calc(100vh - ${headerHeight}` }"
|
|
||||||
>
|
|
||||||
<LayoutParentView />
|
<LayoutParentView />
|
||||||
<Footer v-if="getThemeConfig.isFooter" />
|
<Footer v-if="themeConfig.isFooter" />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<Link
|
<Link :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
|
||||||
:style="{ height: `calc(100vh - ${headerHeight}` }"
|
v-if="state.currentRouteMeta.link && !state.currentRouteMeta.isIframe" />
|
||||||
:meta="currentRouteMeta"
|
<Iframes :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
|
||||||
v-if="currentRouteMeta.link && !currentRouteMeta.isIframe"
|
v-if="state.currentRouteMeta.link && state.currentRouteMeta.isIframe && state.isShowLink"
|
||||||
/>
|
@getCurrentRouteMeta="onGetCurrentRouteMeta" />
|
||||||
<Iframes
|
|
||||||
:style="{ height: `calc(100vh - ${headerHeight}` }"
|
|
||||||
:meta="currentRouteMeta"
|
|
||||||
v-if="currentRouteMeta.link && currentRouteMeta.isIframe && isShowLink"
|
|
||||||
@getCurrentRouteMeta="onGetCurrentRouteMeta"
|
|
||||||
/>
|
|
||||||
</el-main>
|
</el-main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts" name="layoutMain">
|
||||||
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
|
import { reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
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 LayoutParentView from '@/views/layout/routerView/parent.vue';
|
||||||
import Footer from '@/views/layout/footer/index.vue';
|
import Footer from '@/views/layout/footer/index.vue';
|
||||||
import Link from '@/views/layout/routerView/link.vue';
|
import Link from '@/views/layout/routerView/link.vue';
|
||||||
import Iframes from '@/views/layout/routerView/iframes.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 { proxy } = getCurrentInstance() as any;
|
||||||
const store = useStore();
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
headerHeight: '',
|
headerHeight: '',
|
||||||
currentRouteMeta: {},
|
currentRouteMeta: {} as any,
|
||||||
isShowLink: false,
|
isShowLink: false,
|
||||||
});
|
});
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 子组件触发更新
|
// 子组件触发更新
|
||||||
const onGetCurrentRouteMeta = () => {
|
const onGetCurrentRouteMeta = () => {
|
||||||
initCurrentRouteMeta(route.meta);
|
initCurrentRouteMeta(route.meta);
|
||||||
@@ -61,7 +47,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
// 设置 main 的高度
|
// 设置 main 的高度
|
||||||
const initHeaderHeight = () => {
|
const initHeaderHeight = () => {
|
||||||
let { isTagsview } = store.state.themeConfig.themeConfig;
|
let { isTagsview } = themeConfig.value;
|
||||||
if (isTagsview) return (state.headerHeight = `84px`);
|
if (isTagsview) return (state.headerHeight = `84px`);
|
||||||
else return (state.headerHeight = `50px`);
|
else return (state.headerHeight = `50px`);
|
||||||
};
|
};
|
||||||
@@ -71,7 +57,7 @@ export default defineComponent({
|
|||||||
initHeaderHeight();
|
initHeaderHeight();
|
||||||
});
|
});
|
||||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||||
watch(store.state.themeConfig.themeConfig, (val) => {
|
watch(themeConfig.value, (val) => {
|
||||||
state.headerHeight = val.isTagsview ? '84px' : '50px';
|
state.headerHeight = val.isTagsview ? '84px' : '50px';
|
||||||
if (val.isFixedHeaderChange !== val.isFixedHeader) {
|
if (val.isFixedHeaderChange !== val.isFixedHeader) {
|
||||||
if (!proxy.$refs.layoutScrollbarRef) return false;
|
if (!proxy.$refs.layoutScrollbarRef) return false;
|
||||||
@@ -86,12 +72,4 @@ export default defineComponent({
|
|||||||
proxy.$refs.layoutScrollbarRef.wrapRef.scrollTop = 0;
|
proxy.$refs.layoutScrollbarRef.wrapRef.scrollTop = 0;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return {
|
|
||||||
getThemeConfig,
|
|
||||||
initCurrentRouteMeta,
|
|
||||||
onGetCurrentRouteMeta,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,40 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<Defaults v-if="getThemeConfig.layout === 'defaults'" />
|
<Defaults v-if="themeConfig.layout === 'defaults'" />
|
||||||
<Classic v-else-if="getThemeConfig.layout === 'classic'" />
|
<Classic v-else-if="themeConfig.layout === 'classic'" />
|
||||||
<Transverse v-else-if="getThemeConfig.layout === 'transverse'" />
|
<Transverse v-else-if="themeConfig.layout === 'transverse'" />
|
||||||
<Columns v-else-if="getThemeConfig.layout === 'columns'" />
|
<Columns v-else-if="themeConfig.layout === 'columns'" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts" name="layout">
|
||||||
import { computed, onBeforeMount, onUnmounted, getCurrentInstance } from 'vue';
|
import { onBeforeMount, onUnmounted } from 'vue';
|
||||||
import { useStore } from '@/store/index.ts';
|
|
||||||
import { getLocal, setLocal } from '@/common/utils/storage.ts';
|
import { getLocal, setLocal } from '@/common/utils/storage.ts';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import Defaults from '@/views/layout/main/defaults.vue';
|
import Defaults from '@/views/layout/main/defaults.vue';
|
||||||
import Classic from '@/views/layout/main/classic.vue';
|
import Classic from '@/views/layout/main/classic.vue';
|
||||||
import Transverse from '@/views/layout/main/transverse.vue';
|
import Transverse from '@/views/layout/main/transverse.vue';
|
||||||
import Columns from '@/views/layout/main/columns.vue';
|
import Columns from '@/views/layout/main/columns.vue';
|
||||||
export default {
|
import mittBus from '@/common/utils/mitt';
|
||||||
name: 'layout',
|
|
||||||
components: { Defaults, Classic, Transverse, Columns },
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
setup() {
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const store = useStore();
|
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 窗口大小改变时(适配移动端)
|
// 窗口大小改变时(适配移动端)
|
||||||
const onLayoutResize = () => {
|
const onLayoutResize = () => {
|
||||||
if (!getLocal('oldLayout')) setLocal('oldLayout', getThemeConfig.value.layout);
|
if (!getLocal('oldLayout')) setLocal('oldLayout', themeConfig.value.layout);
|
||||||
const clientWidth = document.body.clientWidth;
|
const clientWidth = document.body.clientWidth;
|
||||||
if (clientWidth < 1000) {
|
if (clientWidth < 1000) {
|
||||||
getThemeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
proxy.mittBus.emit('layoutMobileResize', {
|
mittBus.emit('layoutMobileResize', {
|
||||||
layout: 'defaults',
|
layout: 'defaults',
|
||||||
clientWidth,
|
clientWidth,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
proxy.mittBus.emit('layoutMobileResize', {
|
mittBus.emit('layoutMobileResize', {
|
||||||
layout: getLocal('oldLayout') ? getLocal('oldLayout') : 'defaults',
|
layout: getLocal('oldLayout') ? getLocal('oldLayout') : 'defaults',
|
||||||
clientWidth,
|
clientWidth,
|
||||||
});
|
});
|
||||||
@@ -49,9 +44,4 @@ export default {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
window.removeEventListener('resize', onLayoutResize);
|
window.removeEventListener('resize', onLayoutResize);
|
||||||
});
|
});
|
||||||
return {
|
|
||||||
getThemeConfig,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,49 +1,57 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-show="isShowLockScreen">
|
<div v-show="state.isShowLockScreen">
|
||||||
<div class="layout-lock-screen-mask"></div>
|
<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">
|
||||||
<div
|
<div
|
||||||
class="layout-lock-screen-date"
|
class="layout-lock-screen-date"
|
||||||
ref="layoutLockScreenDateRef"
|
ref="layoutLockScreenDateRef"
|
||||||
@mousedown="onDown"
|
@mousedown="onDownPc"
|
||||||
@mousemove="onMove"
|
@mousemove="onMovePc"
|
||||||
@mouseup="onEnd"
|
@mouseup="onEnd"
|
||||||
@touchstart.stop="onDown"
|
@touchstart.stop="onDownApp"
|
||||||
@touchmove.stop="onMove"
|
@touchmove.stop="onMoveApp"
|
||||||
@touchend.stop="onEnd"
|
@touchend.stop="onEnd"
|
||||||
>
|
>
|
||||||
<div class="layout-lock-screen-date-box">
|
<div class="layout-lock-screen-date-box">
|
||||||
<div class="layout-lock-screen-date-box-time">
|
<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>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
<transition name="el-zoom-in-center">
|
<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">
|
||||||
<div class="layout-lock-screen-login-box-img">
|
<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>
|
||||||
<div class="layout-lock-screen-login-box-name">Administrator</div>
|
<div class="layout-lock-screen-login-box-name">Administrator</div>
|
||||||
<div class="layout-lock-screen-login-box-value">
|
<div class="layout-lock-screen-login-box-value">
|
||||||
<el-input
|
<el-input
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
ref="layoutLockScreenInputRef"
|
ref="layoutLockScreenInputRef"
|
||||||
v-model="lockScreenPassword"
|
v-model="state.lockScreenPassword"
|
||||||
@keyup.enter.stop="onLockScreenSubmit()"
|
@keyup.enter.native.stop="onLockScreenSubmit()"
|
||||||
>
|
>
|
||||||
<template #append>
|
<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>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-lock-screen-login-icon">
|
<div class="layout-lock-screen-login-icon">
|
||||||
<i class="el-icon-microphone"></i>
|
<SvgIcon name="ele-Microphone" :size="20" />
|
||||||
<i class="el-icon-alarm-clock"></i>
|
<SvgIcon name="ele-AlarmClock" :size="20" />
|
||||||
<i class="el-icon-switch-button"></i>
|
<SvgIcon name="ele-SwitchButton" :size="20" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -51,24 +59,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts" name="layoutLockScreen">
|
||||||
import { nextTick, onMounted, reactive, toRefs, ref, onUnmounted, getCurrentInstance } from 'vue';
|
import { nextTick, onMounted, reactive, ref, onUnmounted } from 'vue';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { formatDate } from '@/common/utils/format';
|
||||||
import { formatDate } from '@/common/utils/formatTime.ts';
|
import { setLocal } from '@/common/utils/storage';
|
||||||
import { setLocal } from '@/common/utils/storage.ts';
|
import { storeToRefs } from 'pinia';
|
||||||
export default {
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
name: 'layoutLockScreen',
|
|
||||||
setup() {
|
// 定义变量内容
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const layoutLockScreenDateRef = ref<null>();
|
||||||
const layoutLockScreenInputRef = ref();
|
const layoutLockScreenInputRef = ref();
|
||||||
const store = useStore();
|
const storesThemeConfig = useThemeConfig();
|
||||||
const state: any = reactive({
|
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||||
|
const state = reactive({
|
||||||
transparency: 1,
|
transparency: 1,
|
||||||
downClientY: 0,
|
downClientY: 0,
|
||||||
moveDifference: 0,
|
moveDifference: 0,
|
||||||
isShowLoockLogin: false,
|
isShowLoockLogin: false,
|
||||||
isFlags: false,
|
isFlags: false,
|
||||||
querySelectorEl: '',
|
querySelectorEl: '' as any,
|
||||||
time: {
|
time: {
|
||||||
hm: '',
|
hm: '',
|
||||||
s: '',
|
s: '',
|
||||||
@@ -79,21 +88,32 @@ export default {
|
|||||||
isShowLockScreenIntervalTime: 0,
|
isShowLockScreenIntervalTime: 0,
|
||||||
lockScreenPassword: '',
|
lockScreenPassword: '',
|
||||||
});
|
});
|
||||||
// 鼠标按下
|
|
||||||
const onDown = (down: any) => {
|
// 鼠标按下 pc
|
||||||
|
const onDownPc = (down: MouseEvent) => {
|
||||||
state.isFlags = true;
|
state.isFlags = true;
|
||||||
state.downClientY = down.touches ? down.touches[0].clientY : down.clientY;
|
state.downClientY = down.clientY;
|
||||||
};
|
};
|
||||||
// 鼠标移动
|
// 鼠标按下 app
|
||||||
const onMove = (move: any) => {
|
const onDownApp = (down: TouchEvent) => {
|
||||||
if (state.isFlags) {
|
state.isFlags = true;
|
||||||
const el = state.querySelectorEl;
|
state.downClientY = down.touches[0].clientY;
|
||||||
const opacitys = (state.transparency -= 1 / 200);
|
};
|
||||||
if (move.touches) {
|
// 鼠标移动 pc
|
||||||
state.moveDifference = move.touches[0].clientY - state.downClientY;
|
const onMovePc = (move: MouseEvent) => {
|
||||||
} else {
|
|
||||||
state.moveDifference = move.clientY - state.downClientY;
|
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;
|
if (state.moveDifference >= 0) return false;
|
||||||
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
|
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
|
||||||
if (state.moveDifference < -400) {
|
if (state.moveDifference < -400) {
|
||||||
@@ -114,13 +134,13 @@ export default {
|
|||||||
state.isFlags = false;
|
state.isFlags = false;
|
||||||
state.transparency = 1;
|
state.transparency = 1;
|
||||||
if (state.moveDifference >= -400) {
|
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 = () => {
|
const initGetElement = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
state.querySelectorEl = proxy.$refs.layoutLockScreenDateRef;
|
state.querySelectorEl = layoutLockScreenDateRef.value;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 时间初始化
|
// 时间初始化
|
||||||
@@ -138,14 +158,14 @@ export default {
|
|||||||
};
|
};
|
||||||
// 锁屏时间定时器
|
// 锁屏时间定时器
|
||||||
const initLockScreen = () => {
|
const initLockScreen = () => {
|
||||||
if (store.state.themeConfig.themeConfig.isLockScreen) {
|
if (themeConfig.value.isLockScreen) {
|
||||||
state.isShowLockScreenIntervalTime = window.setInterval(() => {
|
state.isShowLockScreenIntervalTime = window.setInterval(() => {
|
||||||
if (store.state.themeConfig.themeConfig.lockScreenTime <= 0) {
|
if (themeConfig.value.lockScreenTime <= 1) {
|
||||||
state.isShowLockScreen = true;
|
state.isShowLockScreen = true;
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
store.state.themeConfig.themeConfig.lockScreenTime--;
|
themeConfig.value.lockScreenTime--;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
clearInterval(state.isShowLockScreenIntervalTime);
|
clearInterval(state.isShowLockScreenIntervalTime);
|
||||||
@@ -153,13 +173,13 @@ export default {
|
|||||||
};
|
};
|
||||||
// 存储布局配置
|
// 存储布局配置
|
||||||
const setLocalThemeConfig = () => {
|
const setLocalThemeConfig = () => {
|
||||||
store.state.themeConfig.themeConfig.isDrawer = false;
|
themeConfig.value.isDrawer = false;
|
||||||
setLocal('themeConfig', store.state.themeConfig.themeConfig);
|
setLocal('themeConfig', themeConfig.value);
|
||||||
};
|
};
|
||||||
// 密码输入点击事件
|
// 密码输入点击事件
|
||||||
const onLockScreenSubmit = () => {
|
const onLockScreenSubmit = () => {
|
||||||
store.state.themeConfig.themeConfig.isLockScreen = false;
|
themeConfig.value.isLockScreen = false;
|
||||||
store.state.themeConfig.themeConfig.lockScreenTime = 30;
|
themeConfig.value.lockScreenTime = 30;
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
};
|
};
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
@@ -173,16 +193,6 @@ export default {
|
|||||||
window.clearInterval(state.setIntervalTime);
|
window.clearInterval(state.setIntervalTime);
|
||||||
window.clearInterval(state.isShowLockScreenIntervalTime);
|
window.clearInterval(state.isShowLockScreenIntervalTime);
|
||||||
});
|
});
|
||||||
return {
|
|
||||||
layoutLockScreenInputRef,
|
|
||||||
onDown,
|
|
||||||
onMove,
|
|
||||||
onEnd,
|
|
||||||
onLockScreenSubmit,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -194,20 +204,18 @@ export default {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.layout-lock-screen-filter {
|
.layout-lock-screen-filter {
|
||||||
filter: blur(5px);
|
filter: blur(1px);
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
}
|
||||||
.layout-lock-screen-mask {
|
.layout-lock-screen-mask {
|
||||||
background: rgba(255, 255, 255, 1);
|
background: var(--el-color-white);
|
||||||
@extend .layout-lock-screen-fixed;
|
@extend .layout-lock-screen-fixed;
|
||||||
z-index: 9999990;
|
z-index: 9999990;
|
||||||
}
|
}
|
||||||
.layout-lock-screen-img {
|
.layout-lock-screen-img {
|
||||||
@extend .layout-lock-screen-fixed;
|
@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%;
|
background-size: 100% 100%;
|
||||||
z-index: 9999991;
|
z-index: 9999991;
|
||||||
transition: all ease 0.3s 0.3s;
|
|
||||||
}
|
}
|
||||||
.layout-lock-screen {
|
.layout-lock-screen {
|
||||||
@extend .layout-lock-screen-fixed;
|
@extend .layout-lock-screen-fixed;
|
||||||
@@ -218,7 +226,7 @@ export default {
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: #ffffff;
|
color: var(--el-color-white);
|
||||||
z-index: 9999993;
|
z-index: 9999993;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
&-box {
|
&-box {
|
||||||
@@ -227,14 +235,64 @@ export default {
|
|||||||
bottom: 50px;
|
bottom: 50px;
|
||||||
&-time {
|
&-time {
|
||||||
font-size: 100px;
|
font-size: 100px;
|
||||||
|
color: var(--el-color-white);
|
||||||
}
|
}
|
||||||
&-info {
|
&-info {
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
|
color: var(--el-color-white);
|
||||||
}
|
}
|
||||||
&-minutes {
|
&-minutes {
|
||||||
font-size: 16px;
|
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 {
|
&-login {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -246,7 +304,7 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #ffffff;
|
color: var(--el-color-white);
|
||||||
&-box {
|
&-box {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -281,14 +339,14 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
::v-deep(.el-input-group__append) {
|
:deep(.el-input-group__append) {
|
||||||
background: #ffffff;
|
background: var(--el-color-white);
|
||||||
padding: 0px 15px;
|
padding: 0px 15px;
|
||||||
}
|
}
|
||||||
::v-deep(.el-input__inner) {
|
:deep(.el-input__inner) {
|
||||||
border-right-color: #f6f6f6;
|
border-right-color: var(--el-border-color-extra-light);
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #f6f6f6;
|
border-color: var(--el-border-color-extra-light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
|
<div class="layout-logo" v-if="setShowLogo" @click="onThemeConfigChange">
|
||||||
<img src="@/assets/image/logo.svg" class="layout-logo-medium-img" />
|
<img src="@/assets/image/logo.svg" class="layout-logo-medium-img" />
|
||||||
<span>
|
<span>
|
||||||
{{ `${getThemeConfig.globalTitle}` }}
|
{{ `${themeConfig.globalTitle}` }}
|
||||||
<sub><span style="font-size: 10px;color:goldenrod">{{ ` ${config.version}` }}</span></sub>
|
<sub><span style="font-size: 10px;color:goldenrod">{{ ` ${config.version}` }}</span></sub>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -11,37 +11,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts" name="layoutLogo">
|
||||||
import { computed, getCurrentInstance } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { storeToRefs } from 'pinia';
|
||||||
import config from '@/common/config.ts';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
export default {
|
import config from '@/common/config';
|
||||||
name: 'layoutLogo',
|
import mittBus from '@/common/utils/mitt';
|
||||||
setup() {
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
const store = useStore();
|
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 设置 logo 的显示。classic 经典布局默认显示 logo
|
// 设置 logo 的显示。classic 经典布局默认显示 logo
|
||||||
const setShowLogo = computed(() => {
|
const setShowLogo = computed(() => {
|
||||||
let { isCollapse, layout } = store.state.themeConfig.themeConfig;
|
let { isCollapse, layout } = themeConfig.value;
|
||||||
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
|
return !isCollapse || layout === 'classic' || document.body.clientWidth < 1000;
|
||||||
});
|
});
|
||||||
// logo 点击实现菜单展开/收起
|
// logo 点击实现菜单展开/收起
|
||||||
const onThemeConfigChange = () => {
|
const onThemeConfigChange = () => {
|
||||||
if (store.state.themeConfig.themeConfig.layout === 'transverse') return false;
|
if (themeConfig.value.layout === 'transverse') return false;
|
||||||
proxy.mittBus.emit('onMenuClick');
|
mittBus.emit('onMenuClick');
|
||||||
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
|
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
|
||||||
};
|
|
||||||
return {
|
|
||||||
config,
|
|
||||||
setShowLogo,
|
|
||||||
getThemeConfig,
|
|
||||||
onThemeConfigChange,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<el-container class="layout-mian-height-50">
|
<el-container class="layout-mian-height-50">
|
||||||
<Aside />
|
<Aside />
|
||||||
<div class="flex-center layout-backtop">
|
<div class="flex-center layout-backtop">
|
||||||
<TagsView v-if="getThemeConfig.isTagsview" />
|
<TagsView v-if="themeConfig.isTagsview" />
|
||||||
<Main />
|
<Main />
|
||||||
</div>
|
</div>
|
||||||
</el-container>
|
</el-container>
|
||||||
@@ -12,25 +12,13 @@
|
|||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutClassic">
|
||||||
import { computed } from 'vue';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import Aside from '@/views/layout/component/aside.vue';
|
import Aside from '@/views/layout/component/aside.vue';
|
||||||
import Header from '@/views/layout/component/header.vue';
|
import Header from '@/views/layout/component/header.vue';
|
||||||
import Main from '@/views/layout/component/main.vue';
|
import Main from '@/views/layout/component/main.vue';
|
||||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
||||||
export default {
|
|
||||||
name: 'layoutClassic',
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
components: { Aside, Header, Main, TagsView },
|
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
getThemeConfig,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -17,18 +17,17 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useStore } from '@/store/index.ts';
|
|
||||||
import Aside from '@/views/layout/component/aside.vue';
|
import Aside from '@/views/layout/component/aside.vue';
|
||||||
import Header from '@/views/layout/component/header.vue';
|
import Header from '@/views/layout/component/header.vue';
|
||||||
import Main from '@/views/layout/component/main.vue';
|
import Main from '@/views/layout/component/main.vue';
|
||||||
import ColumnsAside from '@/views/layout/component/columnsAside.vue';
|
import ColumnsAside from '@/views/layout/component/columnsAside.vue';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutColumns',
|
name: 'layoutColumns',
|
||||||
components: { Aside, Header, Main, ColumnsAside },
|
components: { Aside, Header, Main, ColumnsAside },
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
|
||||||
const isFixedHeader = computed(() => {
|
const isFixedHeader = computed(() => {
|
||||||
return store.state.themeConfig.themeConfig.isFixedHeader;
|
return useThemeConfig().themeConfig.isFixedHeader;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
isFixedHeader,
|
isFixedHeader,
|
||||||
|
|||||||
@@ -15,19 +15,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, getCurrentInstance, watch } from 'vue';
|
import { computed, getCurrentInstance, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useStore } from '@/store/index.ts';
|
|
||||||
import Aside from '@/views/layout/component/aside.vue';
|
import Aside from '@/views/layout/component/aside.vue';
|
||||||
import Header from '@/views/layout/component/header.vue';
|
import Header from '@/views/layout/component/header.vue';
|
||||||
import Main from '@/views/layout/component/main.vue';
|
import Main from '@/views/layout/component/main.vue';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutDefaults',
|
name: 'layoutDefaults',
|
||||||
components: { Aside, Header, Main },
|
components: { Aside, Header, Main },
|
||||||
setup() {
|
setup() {
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { proxy } = getCurrentInstance() as any;
|
||||||
const store = useStore();
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const isFixedHeader = computed(() => {
|
const isFixedHeader = computed(() => {
|
||||||
return store.state.themeConfig.themeConfig.isFixedHeader;
|
return useThemeConfig().themeConfig.isFixedHeader;
|
||||||
});
|
});
|
||||||
// 监听路由的变化
|
// 监听路由的变化
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-navbars-breadcrumb" v-show="getThemeConfig.isBreadcrumb">
|
<div class="layout-navbars-breadcrumb" v-show="themeConfig.isBreadcrumb">
|
||||||
<SvgIcon class="layout-navbars-breadcrumb-icon" :name="getThemeConfig.isCollapse ? 'expand' : 'fold'" @click="onThemeConfigChange" />
|
<SvgIcon class="layout-navbars-breadcrumb-icon" :name="themeConfig.isCollapse ? 'expand' : 'fold'"
|
||||||
|
@click="onThemeConfigChange" />
|
||||||
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
|
<el-breadcrumb class="layout-navbars-breadcrumb-hide">
|
||||||
<transition-group name="breadcrumb" mode="out-in">
|
<transition-group name="breadcrumb" mode="out-in">
|
||||||
<el-breadcrumb-item v-for="(v, k) in breadcrumbList" :key="v.meta.title">
|
<el-breadcrumb-item v-for="(v, k) in state.breadcrumbList" :key="v.meta.title">
|
||||||
<span v-if="k === breadcrumbList.length - 1" class="layout-navbars-breadcrumb-span">
|
<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="getThemeConfig.isBreadcrumbIcon" />
|
<SvgIcon :name="v.meta.icon" class="layout-navbars-breadcrumb-iconfont"
|
||||||
|
v-if="themeConfig.isBreadcrumbIcon" />
|
||||||
{{ v.meta.title }}
|
{{ v.meta.title }}
|
||||||
</span>
|
</span>
|
||||||
<a v-else @click.prevent="onBreadcrumbClick(v)">
|
<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 }}
|
{{ v.meta.title }}
|
||||||
</a>
|
</a>
|
||||||
</el-breadcrumb-item>
|
</el-breadcrumb-item>
|
||||||
@@ -18,15 +21,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutBreadcrumb">
|
||||||
import { toRefs, reactive, computed, getCurrentInstance, onMounted } from 'vue';
|
import { reactive, onMounted } from 'vue';
|
||||||
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { storeToRefs } from 'pinia';
|
||||||
export default {
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
name: 'layoutBreadcrumb',
|
import { useRoutesList } from '@/store/routesList';
|
||||||
setup() {
|
import mittBus from '@/common/utils/mitt';
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const store = useStore();
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
|
const { routesList } = storeToRefs(useRoutesList());
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
@@ -35,10 +39,7 @@ export default {
|
|||||||
routeSplitFirst: '',
|
routeSplitFirst: '',
|
||||||
routeSplitIndex: 1,
|
routeSplitIndex: 1,
|
||||||
});
|
});
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 面包屑点击时
|
// 面包屑点击时
|
||||||
const onBreadcrumbClick = (v: any) => {
|
const onBreadcrumbClick = (v: any) => {
|
||||||
const { redirect, path } = v;
|
const { redirect, path } = v;
|
||||||
@@ -47,8 +48,8 @@ export default {
|
|||||||
};
|
};
|
||||||
// 展开/收起左侧菜单点击
|
// 展开/收起左侧菜单点击
|
||||||
const onThemeConfigChange = () => {
|
const onThemeConfigChange = () => {
|
||||||
proxy.mittBus.emit('onMenuClick');
|
mittBus.emit('onMenuClick');
|
||||||
store.state.themeConfig.themeConfig.isCollapse = !store.state.themeConfig.themeConfig.isCollapse;
|
themeConfig.value.isCollapse = !themeConfig.value.isCollapse;
|
||||||
};
|
};
|
||||||
// 处理面包屑数据
|
// 处理面包屑数据
|
||||||
const getBreadcrumbList = (arr: Array<object>) => {
|
const getBreadcrumbList = (arr: Array<object>) => {
|
||||||
@@ -65,13 +66,13 @@ export default {
|
|||||||
};
|
};
|
||||||
// 当前路由字符串切割成数组,并删除第一项空内容
|
// 当前路由字符串切割成数组,并删除第一项空内容
|
||||||
const initRouteSplit = (path: string) => {
|
const initRouteSplit = (path: string) => {
|
||||||
if (!store.state.themeConfig.themeConfig.isBreadcrumb) return false;
|
if (!themeConfig.value.isBreadcrumb) return false;
|
||||||
state.breadcrumbList = [store.state.routesList.routesList[0]];
|
state.breadcrumbList = [routesList.value[0]];
|
||||||
state.routeSplit = path.split('/');
|
state.routeSplit = path.split('/');
|
||||||
state.routeSplit.shift();
|
state.routeSplit.shift();
|
||||||
state.routeSplitFirst = `/${state.routeSplit[0]}`;
|
state.routeSplitFirst = `/${state.routeSplit[0]}`;
|
||||||
state.routeSplitIndex = 1;
|
state.routeSplitIndex = 1;
|
||||||
getBreadcrumbList(store.state.routesList.routesList);
|
getBreadcrumbList(routesList.value);
|
||||||
};
|
};
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -81,14 +82,6 @@ export default {
|
|||||||
onBeforeRouteUpdate((to) => {
|
onBeforeRouteUpdate((to) => {
|
||||||
initRouteSplit(to.path);
|
initRouteSplit(to.path);
|
||||||
});
|
});
|
||||||
return {
|
|
||||||
onThemeConfigChange,
|
|
||||||
getThemeConfig,
|
|
||||||
onBreadcrumbClick,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -98,20 +91,24 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
|
|
||||||
.layout-navbars-breadcrumb-icon {
|
.layout-navbars-breadcrumb-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
color: var(--bg-topBarColor);
|
color: var(--bg-topBarColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-navbars-breadcrumb-span {
|
.layout-navbars-breadcrumb-span {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
color: var(--bg-topBarColor);
|
color: var(--bg-topBarColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-navbars-breadcrumb-iconfont {
|
.layout-navbars-breadcrumb-iconfont {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-breadcrumb__separator) {
|
::v-deep(.el-breadcrumb__separator) {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
color: var(--bg-topBarColor);
|
color: var(--bg-topBarColor);
|
||||||
|
|||||||
@@ -2,52 +2,51 @@
|
|||||||
<div class="layout-navbars-breadcrumb-index">
|
<div class="layout-navbars-breadcrumb-index">
|
||||||
<Logo v-if="setIsShowLogo" />
|
<Logo v-if="setIsShowLogo" />
|
||||||
<Breadcrumb />
|
<Breadcrumb />
|
||||||
<Horizontal :menuList="menuList" v-if="isLayoutTransverse" />
|
<Horizontal :menuList="state.menuList" v-if="isLayoutTransverse" />
|
||||||
<User />
|
<User />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutBreadcrumbIndex">
|
||||||
import { computed, reactive, toRefs, onMounted, onUnmounted, getCurrentInstance, watch } from 'vue';
|
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
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 Breadcrumb from '@/views/layout/navBars/breadcrumb/breadcrumb.vue';
|
||||||
import User from '@/views/layout/navBars/breadcrumb/user.vue';
|
import User from '@/views/layout/navBars/breadcrumb/user.vue';
|
||||||
import Logo from '@/views/layout/logo/index.vue';
|
import Logo from '@/views/layout/logo/index.vue';
|
||||||
import Horizontal from '@/views/layout/navMenu/horizontal.vue';
|
import Horizontal from '@/views/layout/navMenu/horizontal.vue';
|
||||||
export default {
|
import mittBus from '@/common/utils/mitt';
|
||||||
name: 'layoutBreadcrumbIndex',
|
|
||||||
components: { Breadcrumb, User, Logo, Horizontal },
|
|
||||||
setup() {
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { routesList } = storeToRefs(useRoutesList());
|
||||||
const store = useStore();
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
menuList: [],
|
menuList: [],
|
||||||
});
|
});
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 设置 logo 显示/隐藏
|
// 设置 logo 显示/隐藏
|
||||||
const setIsShowLogo = computed(() => {
|
const setIsShowLogo = computed(() => {
|
||||||
let { isShowLogo, layout } = store.state.themeConfig.themeConfig;
|
let { isShowLogo, layout } = themeConfig.value;
|
||||||
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
|
return (isShowLogo && layout === 'classic') || (isShowLogo && layout === 'transverse');
|
||||||
});
|
});
|
||||||
// 设置是否显示横向导航菜单
|
// 设置是否显示横向导航菜单
|
||||||
const isLayoutTransverse = computed(() => {
|
const isLayoutTransverse = computed(() => {
|
||||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||||
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
|
return layout === 'transverse' || (isClassicSplitMenu && layout === 'classic');
|
||||||
});
|
});
|
||||||
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
// 设置/过滤路由(非静态路由/是否显示在菜单中)
|
||||||
const setFilterRoutes = () => {
|
const setFilterRoutes = () => {
|
||||||
let { layout, isClassicSplitMenu } = store.state.themeConfig.themeConfig;
|
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||||
if (layout === 'classic' && isClassicSplitMenu) {
|
if (layout === 'classic' && isClassicSplitMenu) {
|
||||||
state.menuList = delClassicChildren(filterRoutesFun(store.state.routesList.routesList));
|
state.menuList = delClassicChildren(filterRoutesFun(routesList.value));
|
||||||
const resData = setSendClassicChildren(route.path);
|
const resData = setSendClassicChildren(route.path);
|
||||||
proxy.mittBus.emit('setSendClassicChildren', resData);
|
mittBus.emit('setSendClassicChildren', resData);
|
||||||
} else {
|
} else {
|
||||||
state.menuList = filterRoutesFun(store.state.routesList.routesList);
|
state.menuList = filterRoutesFun(routesList.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 设置了分割菜单时,删除底下 children
|
// 设置了分割菜单时,删除底下 children
|
||||||
@@ -71,7 +70,7 @@ export default {
|
|||||||
const setSendClassicChildren = (path: string) => {
|
const setSendClassicChildren = (path: string) => {
|
||||||
const currentPathSplit = path.split('/');
|
const currentPathSplit = path.split('/');
|
||||||
let currentData: any = {};
|
let currentData: any = {};
|
||||||
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
|
filterRoutesFun(routesList.value).map((v, k) => {
|
||||||
if (v.path === `/${currentPathSplit[1]}`) {
|
if (v.path === `/${currentPathSplit[1]}`) {
|
||||||
v['k'] = k;
|
v['k'] = k;
|
||||||
currentData['item'] = [{ ...v }];
|
currentData['item'] = [{ ...v }];
|
||||||
@@ -82,29 +81,21 @@ export default {
|
|||||||
return currentData;
|
return currentData;
|
||||||
};
|
};
|
||||||
// 监听路由的变化,动态赋值给菜单中
|
// 监听路由的变化,动态赋值给菜单中
|
||||||
watch(store.state, (val) => {
|
watch(pinia.state, (val) => {
|
||||||
if (val.routesList.routesList.length === state.menuList.length) return false;
|
if (val.routesList.routesList.length === state.menuList.length) return false;
|
||||||
setFilterRoutes();
|
setFilterRoutes();
|
||||||
});
|
});
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setFilterRoutes();
|
setFilterRoutes();
|
||||||
proxy.mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
|
||||||
setFilterRoutes();
|
setFilterRoutes();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// 页面卸载时
|
// 页面卸载时
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
proxy.mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
mittBus.off('getBreadcrumbIndexSetFilterRoutes');
|
||||||
});
|
});
|
||||||
return {
|
|
||||||
getThemeConfig,
|
|
||||||
setIsShowLogo,
|
|
||||||
isLayoutTransverse,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,37 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-search-dialog">
|
<div class="layout-search-dialog">
|
||||||
<el-dialog v-model="isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
|
<el-dialog v-model="state.isShowSearch" width="300px" destroy-on-close :modal="false" fullscreen :show-close="false">
|
||||||
<el-autocomplete
|
<el-autocomplete v-model="state.menuQuery" :fetch-suggestions="menuSearch" placeholder="菜单搜索"
|
||||||
v-model="menuQuery"
|
prefix-icon="el-icon-search" ref="layoutMenuAutocompleteRef" @select="onHandleSelect" @blur="onSearchBlur">
|
||||||
:fetch-suggestions="menuSearch"
|
|
||||||
placeholder="菜单搜索"
|
|
||||||
prefix-icon="el-icon-search"
|
|
||||||
ref="layoutMenuAutocompleteRef"
|
|
||||||
@select="onHandleSelect"
|
|
||||||
@blur="onSearchBlur"
|
|
||||||
>
|
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<el-icon class="el-input__icon">
|
<el-icon class="el-input__icon">
|
||||||
<search />
|
<search />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</template>
|
</template>
|
||||||
<template #default="{ item }">
|
<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>
|
</template>
|
||||||
</el-autocomplete>
|
</el-autocomplete>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutBreadcrumbSearch">
|
||||||
import { reactive, toRefs, defineComponent, ref, nextTick } from 'vue';
|
import { reactive, ref, nextTick } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { useRoutesList } from '@/store/routesList';
|
||||||
export default defineComponent({
|
|
||||||
name: 'layoutBreadcrumbSearch',
|
const layoutMenuAutocompleteRef: any = ref(null);;
|
||||||
setup() {
|
|
||||||
const layoutMenuAutocompleteRef: any = ref(null);
|
|
||||||
const store = useStore();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
isShowSearch: false,
|
isShowSearch: false,
|
||||||
@@ -70,7 +62,7 @@ export default defineComponent({
|
|||||||
// 初始化菜单数据
|
// 初始化菜单数据
|
||||||
const initTageView = () => {
|
const initTageView = () => {
|
||||||
if (state.tagsViewList.length > 0) return false;
|
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) {
|
if (!v.meta.isHide) {
|
||||||
state.tagsViewList.push({ ...v });
|
state.tagsViewList.push({ ...v });
|
||||||
}
|
}
|
||||||
@@ -104,17 +96,8 @@ export default defineComponent({
|
|||||||
const onSearchBlur = () => {
|
const onSearchBlur = () => {
|
||||||
closeSearch();
|
closeSearch();
|
||||||
};
|
};
|
||||||
return {
|
|
||||||
layoutMenuAutocompleteRef,
|
defineExpose({openSearch})
|
||||||
openSearch,
|
|
||||||
closeSearch,
|
|
||||||
menuSearch,
|
|
||||||
onHandleSelect,
|
|
||||||
onSearchBlur,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -124,6 +107,7 @@ export default defineComponent({
|
|||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-autocomplete) {
|
::v-deep(.el-autocomplete) {
|
||||||
width: 560px;
|
width: 560px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,49 +1,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-breadcrumb-seting">
|
<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">
|
<el-scrollbar class="layout-breadcrumb-seting-bar">
|
||||||
<!-- ssh终端主题 -->
|
<!-- ssh终端主题 -->
|
||||||
<el-divider content-position="left">终端主题</el-divider>
|
<el-divider content-position="left">终端主题</el-divider>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">字体颜色</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</el-color-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">背景颜色</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</el-color-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">cursor颜色</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</el-color-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">字体大小</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-input-number
|
<el-input-number v-model="themeConfig.terminalFontSize" controls-position="right" :min="12"
|
||||||
v-model="getThemeConfig.terminalFontSize"
|
:max="24" @change="setLocalThemeConfig" size="small" style="width: 90px">
|
||||||
controls-position="right"
|
|
||||||
:min="12"
|
|
||||||
:max="24"
|
|
||||||
@change="setLocalThemeConfig"
|
|
||||||
size="small"
|
|
||||||
style="width: 90px"
|
|
||||||
>
|
|
||||||
</el-input-number>
|
</el-input-number>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">字体粗细</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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="normal" value="normal"> </el-option>
|
||||||
<el-option label="bold" value="bold"> </el-option>
|
<el-option label="bold" value="bold"> </el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -54,7 +52,8 @@
|
|||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-select @change="setLocalThemeConfig" v-model="getThemeConfig.editorTheme" size="small" style="width: 130px">
|
<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" value="vs"> </el-option>
|
||||||
<el-option label="vs-dark" value="vs-dark"> </el-option>
|
<el-option label="vs-dark" value="vs-dark"> </el-option>
|
||||||
<el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
|
<el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
|
||||||
@@ -67,31 +66,36 @@
|
|||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">primary</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">success</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">success</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">info</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">info</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">warning</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">warning</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">danger</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">danger</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -100,69 +104,73 @@
|
|||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单背景</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">菜单背景</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</el-color-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏默认字体颜色</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏默认字体颜色</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</el-color-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单默认字体颜色</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">菜单默认字体颜色</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</el-color-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单默认字体颜色</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单默认字体颜色</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-color-picker
|
<el-color-picker v-model="themeConfig.columnsMenuBarColor" size="small"
|
||||||
v-model="getThemeConfig.columnsMenuBarColor"
|
@change="onBgColorPickerChange('columnsMenuBarColor')">
|
||||||
size="small"
|
|
||||||
@change="onBgColorPickerChange('columnsMenuBarColor')"
|
|
||||||
>
|
|
||||||
</el-color-picker>
|
</el-color-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt10">
|
<div class="layout-breadcrumb-seting-bar-flex mt10">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景渐变</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">顶栏背景渐变</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单背景渐变</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">菜单背景渐变</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景渐变</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">分栏菜单背景渐变</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
<div class="layout-breadcrumb-seting-bar-flex mt14">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单字体背景高亮</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">菜单字体背景高亮</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -171,50 +179,41 @@
|
|||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单水平折叠</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">菜单水平折叠</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">菜单手风琴</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">菜单手风琴</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">固定 Header</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</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-label">经典布局分割菜单</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-switch
|
<el-switch v-model="themeConfig.isClassicSplitMenu" :disabled="themeConfig.layout !== 'classic'"
|
||||||
v-model="getThemeConfig.isClassicSplitMenu"
|
@change="onClassicSplitMenuChange">
|
||||||
:disabled="getThemeConfig.layout !== 'classic'"
|
|
||||||
@change="onClassicSplitMenuChange"
|
|
||||||
>
|
|
||||||
</el-switch>
|
</el-switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">开启锁屏</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">开启锁屏</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt11">
|
<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-label">自动锁屏(s/秒)</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-input-number
|
<el-input-number v-model="themeConfig.lockScreenTime" controls-position="right" :min="0" :max="9999"
|
||||||
v-model="getThemeConfig.lockScreenTime"
|
@change="setLocalThemeConfig" size="small" style="width: 90px">
|
||||||
controls-position="right"
|
|
||||||
:min="0"
|
|
||||||
:max="9999"
|
|
||||||
@change="setLocalThemeConfig"
|
|
||||||
size="small"
|
|
||||||
style="width: 90px"
|
|
||||||
>
|
|
||||||
</el-input-number>
|
</el-input-number>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -224,65 +223,63 @@
|
|||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">侧边栏 Logo</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</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-label">开启Breadcrumb</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-switch
|
<el-switch v-model="themeConfig.isBreadcrumb" :disabled="themeConfig.layout === 'transverse'"
|
||||||
v-model="getThemeConfig.isBreadcrumb"
|
@change="onIsBreadcrumbChange"></el-switch>
|
||||||
:disabled="getThemeConfig.layout === 'transverse'"
|
|
||||||
@change="onIsBreadcrumbChange"
|
|
||||||
></el-switch>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">开启Breadcrumb图标</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">开启 Tagsview</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">开启 Tagsview图标</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">开启 TagsView缓存</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">开启 TagsView拖拽</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">开启 Footer</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">灰色模式</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">灰色模式</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">色弱模式</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">色弱模式</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -291,30 +288,19 @@
|
|||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<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-label">Tagsview 风格</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-select
|
<el-select v-model="themeConfig.tagsStyle" placeholder="请选择" size="small" style="width: 90px"
|
||||||
v-model="getThemeConfig.tagsStyle"
|
@change="setLocalThemeConfig">
|
||||||
placeholder="请选择"
|
|
||||||
size="small"
|
|
||||||
style="width: 90px"
|
|
||||||
@change="setLocalThemeConfig"
|
|
||||||
>
|
|
||||||
<el-option label="风格1" value="tags-style-one"></el-option>
|
<el-option label="风格1" value="tags-style-one"></el-option>
|
||||||
<el-option label="风格2" value="tags-style-two"></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="风格3" value="tags-style-three"></el-option>
|
||||||
<el-option label="风格4" value="tags-style-four"></el-option>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">主页面切换动画</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">主页面切换动画</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-select
|
<el-select v-model="themeConfig.animation" placeholder="请选择" size="small" style="width: 90px"
|
||||||
v-model="getThemeConfig.animation"
|
@change="setLocalThemeConfig">
|
||||||
placeholder="请选择"
|
|
||||||
size="small"
|
|
||||||
style="width: 90px"
|
|
||||||
@change="setLocalThemeConfig"
|
|
||||||
>
|
|
||||||
<el-option label="slide-right" value="slide-right"></el-option>
|
<el-option label="slide-right" value="slide-right"></el-option>
|
||||||
<el-option label="slide-left" value="slide-left"></el-option>
|
<el-option label="slide-left" value="slide-left"></el-option>
|
||||||
<el-option label="opacitys" value="opacitys"></el-option>
|
<el-option label="opacitys" value="opacitys"></el-option>
|
||||||
@@ -324,13 +310,8 @@
|
|||||||
<div class="layout-breadcrumb-seting-bar-flex mt15 mb28">
|
<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-label">分栏高亮风格</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-select
|
<el-select v-model="themeConfig.columnsAsideStyle" placeholder="请选择" size="small"
|
||||||
v-model="getThemeConfig.columnsAsideStyle"
|
style="width: 90px" @change="setLocalThemeConfig">
|
||||||
placeholder="请选择"
|
|
||||||
size="small"
|
|
||||||
style="width: 90px"
|
|
||||||
@change="setLocalThemeConfig"
|
|
||||||
>
|
|
||||||
<el-option label="圆角" value="columns-round"></el-option>
|
<el-option label="圆角" value="columns-round"></el-option>
|
||||||
<el-option label="卡片" value="columns-card"></el-option>
|
<el-option label="卡片" value="columns-card"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -342,14 +323,16 @@
|
|||||||
<div class="layout-drawer-content-flex">
|
<div class="layout-drawer-content-flex">
|
||||||
<!-- defaults 布局 -->
|
<!-- defaults 布局 -->
|
||||||
<div class="layout-drawer-content-item" @click="onSetLayout('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>
|
<aside class="el-aside" style="width: 20px"></aside>
|
||||||
<section class="el-container is-vertical">
|
<section class="el-container is-vertical">
|
||||||
<header class="el-header" style="height: 10px"></header>
|
<header class="el-header" style="height: 10px"></header>
|
||||||
<main class="el-main"></main>
|
<main class="el-main"></main>
|
||||||
</section>
|
</section>
|
||||||
</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">
|
<div class="layout-tips-box">
|
||||||
<p class="layout-tips-txt">默认</p>
|
<p class="layout-tips-txt">默认</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -357,10 +340,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- classic 布局 -->
|
<!-- classic 布局 -->
|
||||||
<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
|
<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
|
||||||
<section
|
<section class="el-container is-vertical el-circular"
|
||||||
class="el-container is-vertical el-circular"
|
:class="{ 'drawer-layout-active': themeConfig.layout === 'classic' }">
|
||||||
:class="{ 'drawer-layout-active': getThemeConfig.layout === 'classic' }"
|
|
||||||
>
|
|
||||||
<header class="el-header" style="height: 10px"></header>
|
<header class="el-header" style="height: 10px"></header>
|
||||||
<section class="el-container">
|
<section class="el-container">
|
||||||
<aside class="el-aside" style="width: 20px"></aside>
|
<aside class="el-aside" style="width: 20px"></aside>
|
||||||
@@ -369,7 +350,8 @@
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</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">
|
<div class="layout-tips-box">
|
||||||
<p class="layout-tips-txt">经典</p>
|
<p class="layout-tips-txt">经典</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -377,10 +359,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- transverse 布局 -->
|
<!-- transverse 布局 -->
|
||||||
<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
|
<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
|
||||||
<section
|
<section class="el-container is-vertical el-circular"
|
||||||
class="el-container is-vertical el-circular"
|
:class="{ 'drawer-layout-active': themeConfig.layout === 'transverse' }">
|
||||||
:class="{ 'drawer-layout-active': getThemeConfig.layout === 'transverse' }"
|
|
||||||
>
|
|
||||||
<header class="el-header" style="height: 10px"></header>
|
<header class="el-header" style="height: 10px"></header>
|
||||||
<section class="el-container">
|
<section class="el-container">
|
||||||
<section class="el-container is-vertical">
|
<section class="el-container is-vertical">
|
||||||
@@ -388,7 +368,8 @@
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</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">
|
<div class="layout-tips-box">
|
||||||
<p class="layout-tips-txt">横向</p>
|
<p class="layout-tips-txt">横向</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -396,7 +377,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- columns 布局 -->
|
<!-- columns 布局 -->
|
||||||
<div class="layout-drawer-content-item" @click="onSetLayout('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-dark" style="width: 10px"></aside>
|
||||||
<aside class="el-aside" style="width: 20px"></aside>
|
<aside class="el-aside" style="width: 20px"></aside>
|
||||||
<section class="el-container is-vertical">
|
<section class="el-container is-vertical">
|
||||||
@@ -404,7 +386,8 @@
|
|||||||
<main class="el-main"></main>
|
<main class="el-main"></main>
|
||||||
</section>
|
</section>
|
||||||
</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">
|
<div class="layout-tips-box">
|
||||||
<p class="layout-tips-txt">分栏</p>
|
<p class="layout-tips-txt">分栏</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -414,14 +397,8 @@
|
|||||||
<div class="copy-config">
|
<div class="copy-config">
|
||||||
<el-alert title="点击下方按钮,复制布局配置去 /src/store/modules/themeConfig.ts中修改" type="warning" :closable="false">
|
<el-alert title="点击下方按钮,复制布局配置去 /src/store/modules/themeConfig.ts中修改" type="warning" :closable="false">
|
||||||
</el-alert>
|
</el-alert>
|
||||||
<el-button
|
<el-button size="small" class="copy-config-btn" icon="el-icon-document-copy" type="primary"
|
||||||
size="small"
|
ref="copyConfigBtnRef" @click="onCopyConfigClick($event.target)">一键复制配置
|
||||||
class="copy-config-btn"
|
|
||||||
icon="el-icon-document-copy"
|
|
||||||
type="primary"
|
|
||||||
ref="copyConfigBtnRef"
|
|
||||||
@click="onCopyConfigClick($event.target)"
|
|
||||||
>一键复制配置
|
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
@@ -429,26 +406,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutBreadcrumbSeting">
|
||||||
import { nextTick, onUnmounted, onMounted, getCurrentInstance, defineComponent, computed, ref } from 'vue';
|
import { nextTick, onUnmounted, onMounted, ref } from 'vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import ClipboardJS from 'clipboard';
|
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 { getLightColor } from '@/common/utils/theme.ts';
|
||||||
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage.ts';
|
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage.ts';
|
||||||
export default defineComponent({
|
import mittBus from '@/common/utils/mitt';
|
||||||
name: 'layoutBreadcrumbSeting',
|
|
||||||
setup() {
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const copyConfigBtnRef = ref();
|
const copyConfigBtnRef = ref();
|
||||||
const store = useStore();
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 1、全局主题
|
// 1、全局主题
|
||||||
const onColorPickerChange = (color: string) => {
|
const onColorPickerChange = (color: string) => {
|
||||||
setPropertyFun(`--color-${color}`, getThemeConfig.value[color]);
|
setPropertyFun(`--color-${color}`, themeConfig.value[color]);
|
||||||
setDispatchThemeConfig();
|
setDispatchThemeConfig();
|
||||||
};
|
};
|
||||||
// 1、全局主题设置函数
|
// 1、全局主题设置函数
|
||||||
@@ -460,7 +433,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
// 2、菜单 / 顶栏
|
// 2、菜单 / 顶栏
|
||||||
const onBgColorPickerChange = (bg: string) => {
|
const onBgColorPickerChange = (bg: string) => {
|
||||||
document.documentElement.style.setProperty(`--bg-${bg}`, getThemeConfig.value[bg]);
|
document.documentElement.style.setProperty(`--bg-${bg}`, themeConfig.value[bg]);
|
||||||
onTopBarGradualChange();
|
onTopBarGradualChange();
|
||||||
onMenuBarGradualChange();
|
onMenuBarGradualChange();
|
||||||
onColumnsMenuBarGradualChange();
|
onColumnsMenuBarGradualChange();
|
||||||
@@ -468,18 +441,18 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
// 2、菜单 / 顶栏 --> 顶栏背景渐变
|
// 2、菜单 / 顶栏 --> 顶栏背景渐变
|
||||||
const onTopBarGradualChange = () => {
|
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、菜单 / 顶栏 --> 菜单背景渐变
|
// 2、菜单 / 顶栏 --> 菜单背景渐变
|
||||||
const onMenuBarGradualChange = () => {
|
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、菜单 / 顶栏 --> 分栏菜单背景渐变
|
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
|
||||||
const onColumnsMenuBarGradualChange = () => {
|
const onColumnsMenuBarGradualChange = () => {
|
||||||
setGraduaFun(
|
setGraduaFun(
|
||||||
'.layout-container .layout-columns-aside',
|
'.layout-container .layout-columns-aside',
|
||||||
getThemeConfig.value.isColumnsMenuBarColorGradual,
|
themeConfig.value.isColumnsMenuBarColorGradual,
|
||||||
getThemeConfig.value.columnsMenuBar
|
themeConfig.value.columnsMenuBar
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
// 2、菜单 / 顶栏 --> 背景渐变函数
|
// 2、菜单 / 顶栏 --> 背景渐变函数
|
||||||
@@ -505,7 +478,7 @@ export default defineComponent({
|
|||||||
let elsItems = document.querySelectorAll('.el-menu-item');
|
let elsItems = document.querySelectorAll('.el-menu-item');
|
||||||
let elActive = document.querySelector('.el-menu-item.is-active');
|
let elActive = document.querySelector('.el-menu-item.is-active');
|
||||||
if (!elActive) return false;
|
if (!elActive) return false;
|
||||||
if (getThemeConfig.value.isMenuBarColorHighlight) {
|
if (themeConfig.value.isMenuBarColorHighlight) {
|
||||||
elsItems.forEach((el: any) => el.setAttribute('id', ``));
|
elsItems.forEach((el: any) => el.setAttribute('id', ``));
|
||||||
elActive.setAttribute('id', `add-is-active`);
|
elActive.setAttribute('id', `add-is-active`);
|
||||||
setLocal('menuBarHighlightId', elActive.getAttribute('id'));
|
setLocal('menuBarHighlightId', elActive.getAttribute('id'));
|
||||||
@@ -523,43 +496,43 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
// 3、界面设置 --> 固定 Header
|
// 3、界面设置 --> 固定 Header
|
||||||
const onIsFixedHeaderChange = () => {
|
const onIsFixedHeaderChange = () => {
|
||||||
getThemeConfig.value.isFixedHeaderChange = getThemeConfig.value.isFixedHeader ? false : true;
|
themeConfig.value.isFixedHeaderChange = themeConfig.value.isFixedHeader ? false : true;
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
};
|
};
|
||||||
// 3、界面设置 --> 经典布局分割菜单
|
// 3、界面设置 --> 经典布局分割菜单
|
||||||
const onClassicSplitMenuChange = () => {
|
const onClassicSplitMenuChange = () => {
|
||||||
getThemeConfig.value.isBreadcrumb = false;
|
themeConfig.value.isBreadcrumb = false;
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
proxy.mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
|
mittBus.emit('getBreadcrumbIndexSetFilterRoutes');
|
||||||
};
|
};
|
||||||
// 4、界面显示 --> 侧边栏 Logo
|
// 4、界面显示 --> 侧边栏 Logo
|
||||||
const onIsShowLogoChange = () => {
|
const onIsShowLogoChange = () => {
|
||||||
getThemeConfig.value.isShowLogoChange = getThemeConfig.value.isShowLogo ? false : true;
|
themeConfig.value.isShowLogoChange = themeConfig.value.isShowLogo ? false : true;
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
};
|
};
|
||||||
// 4、界面显示 --> 面包屑 Breadcrumb
|
// 4、界面显示 --> 面包屑 Breadcrumb
|
||||||
const onIsBreadcrumbChange = () => {
|
const onIsBreadcrumbChange = () => {
|
||||||
if (getThemeConfig.value.layout === 'classic') {
|
if (themeConfig.value.layout === 'classic') {
|
||||||
getThemeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
}
|
}
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
};
|
};
|
||||||
// 4、界面显示 --> 开启 TagsView 拖拽
|
// 4、界面显示 --> 开启 TagsView 拖拽
|
||||||
const onSortableTagsViewChange = () => {
|
const onSortableTagsViewChange = () => {
|
||||||
proxy.mittBus.emit('openOrCloseSortable');
|
mittBus.emit('openOrCloseSortable');
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
};
|
};
|
||||||
// 4、界面显示 --> 灰色模式/色弱模式
|
// 4、界面显示 --> 灰色模式/色弱模式
|
||||||
const onAddFilterChange = (attr: string) => {
|
const onAddFilterChange = (attr: string) => {
|
||||||
if (attr === 'grayscale') {
|
if (attr === 'grayscale') {
|
||||||
if (getThemeConfig.value.isGrayscale) getThemeConfig.value.isInvert = false;
|
if (themeConfig.value.isGrayscale) themeConfig.value.isInvert = false;
|
||||||
} else {
|
} else {
|
||||||
if (getThemeConfig.value.isInvert) getThemeConfig.value.isGrayscale = false;
|
if (themeConfig.value.isInvert) themeConfig.value.isGrayscale = false;
|
||||||
}
|
}
|
||||||
const cssAttr =
|
const cssAttr =
|
||||||
attr === 'grayscale'
|
attr === 'grayscale'
|
||||||
? `grayscale(${getThemeConfig.value.isGrayscale ? 1 : 0})`
|
? `grayscale(${themeConfig.value.isGrayscale ? 1 : 0})`
|
||||||
: `invert(${getThemeConfig.value.isInvert ? '80%' : '0%'})`;
|
: `invert(${themeConfig.value.isInvert ? '80%' : '0%'})`;
|
||||||
const appEle: any = document.querySelector('#app');
|
const appEle: any = document.querySelector('#app');
|
||||||
appEle.setAttribute('style', `filter: ${cssAttr}`);
|
appEle.setAttribute('style', `filter: ${cssAttr}`);
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
@@ -568,55 +541,55 @@ export default defineComponent({
|
|||||||
// 5、布局切换
|
// 5、布局切换
|
||||||
const onSetLayout = (layout: string) => {
|
const onSetLayout = (layout: string) => {
|
||||||
setLocal('oldLayout', layout);
|
setLocal('oldLayout', layout);
|
||||||
if (getThemeConfig.value.layout === layout) return false;
|
if (themeConfig.value.layout === layout) return false;
|
||||||
getThemeConfig.value.layout = layout;
|
themeConfig.value.layout = layout;
|
||||||
getThemeConfig.value.isDrawer = false;
|
themeConfig.value.isDrawer = false;
|
||||||
initSetLayoutChange();
|
initSetLayoutChange();
|
||||||
onMenuBarHighlightChange();
|
onMenuBarHighlightChange();
|
||||||
};
|
};
|
||||||
// 设置布局切换,重置主题样式
|
// 设置布局切换,重置主题样式
|
||||||
const initSetLayoutChange = () => {
|
const initSetLayoutChange = () => {
|
||||||
if (getThemeConfig.value.layout === 'classic') {
|
if (themeConfig.value.layout === 'classic') {
|
||||||
getThemeConfig.value.isShowLogo = true;
|
themeConfig.value.isShowLogo = true;
|
||||||
getThemeConfig.value.isBreadcrumb = true;
|
themeConfig.value.isBreadcrumb = true;
|
||||||
getThemeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
getThemeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
getThemeConfig.value.menuBar = '#FFFFFF';
|
themeConfig.value.menuBar = '#FFFFFF';
|
||||||
getThemeConfig.value.menuBarColor = '#606266';
|
themeConfig.value.menuBarColor = '#606266';
|
||||||
getThemeConfig.value.topBar = '#ffffff';
|
themeConfig.value.topBar = '#ffffff';
|
||||||
getThemeConfig.value.topBarColor = '#606266';
|
themeConfig.value.topBarColor = '#606266';
|
||||||
initLayoutChangeFun();
|
initLayoutChangeFun();
|
||||||
} else if (getThemeConfig.value.layout === 'transverse') {
|
} else if (themeConfig.value.layout === 'transverse') {
|
||||||
getThemeConfig.value.isShowLogo = true;
|
themeConfig.value.isShowLogo = true;
|
||||||
getThemeConfig.value.isBreadcrumb = false;
|
themeConfig.value.isBreadcrumb = false;
|
||||||
getThemeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
getThemeConfig.value.isTagsview = false;
|
themeConfig.value.isTagsview = false;
|
||||||
getThemeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
getThemeConfig.value.menuBarColor = '#FFFFFF';
|
themeConfig.value.menuBarColor = '#FFFFFF';
|
||||||
getThemeConfig.value.topBar = '#545c64';
|
themeConfig.value.topBar = '#545c64';
|
||||||
getThemeConfig.value.topBarColor = '#FFFFFF';
|
themeConfig.value.topBarColor = '#FFFFFF';
|
||||||
initLayoutChangeFun();
|
initLayoutChangeFun();
|
||||||
} else if (getThemeConfig.value.layout === 'columns') {
|
} else if (themeConfig.value.layout === 'columns') {
|
||||||
getThemeConfig.value.isShowLogo = true;
|
themeConfig.value.isShowLogo = true;
|
||||||
getThemeConfig.value.isBreadcrumb = true;
|
themeConfig.value.isBreadcrumb = true;
|
||||||
getThemeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
getThemeConfig.value.isTagsview = true;
|
themeConfig.value.isTagsview = true;
|
||||||
getThemeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
getThemeConfig.value.menuBar = '#FFFFFF';
|
themeConfig.value.menuBar = '#FFFFFF';
|
||||||
getThemeConfig.value.menuBarColor = '#606266';
|
themeConfig.value.menuBarColor = '#606266';
|
||||||
getThemeConfig.value.topBar = '#ffffff';
|
themeConfig.value.topBar = '#ffffff';
|
||||||
getThemeConfig.value.topBarColor = '#606266';
|
themeConfig.value.topBarColor = '#606266';
|
||||||
initLayoutChangeFun();
|
initLayoutChangeFun();
|
||||||
} else {
|
} else {
|
||||||
getThemeConfig.value.isShowLogo = false;
|
themeConfig.value.isShowLogo = false;
|
||||||
getThemeConfig.value.isBreadcrumb = true;
|
themeConfig.value.isBreadcrumb = true;
|
||||||
getThemeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
getThemeConfig.value.isTagsview = true;
|
themeConfig.value.isTagsview = true;
|
||||||
getThemeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
getThemeConfig.value.menuBar = '#545c64';
|
themeConfig.value.menuBar = '#545c64';
|
||||||
getThemeConfig.value.menuBarColor = '#eaeaea';
|
themeConfig.value.menuBarColor = '#eaeaea';
|
||||||
getThemeConfig.value.topBar = '#FFFFFF';
|
themeConfig.value.topBar = '#FFFFFF';
|
||||||
getThemeConfig.value.topBarColor = '#606266';
|
themeConfig.value.topBarColor = '#606266';
|
||||||
initLayoutChangeFun();
|
initLayoutChangeFun();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -629,14 +602,14 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
|
// 关闭弹窗时,初始化变量。变量用于处理 proxy.$refs.layoutScrollbarRef.update()
|
||||||
const onDrawerClose = () => {
|
const onDrawerClose = () => {
|
||||||
getThemeConfig.value.isFixedHeaderChange = false;
|
themeConfig.value.isFixedHeaderChange = false;
|
||||||
getThemeConfig.value.isShowLogoChange = false;
|
themeConfig.value.isShowLogoChange = false;
|
||||||
getThemeConfig.value.isDrawer = false;
|
themeConfig.value.isDrawer = false;
|
||||||
setLocalThemeConfig();
|
setLocalThemeConfig();
|
||||||
};
|
};
|
||||||
// 布局配置弹窗打开
|
// 布局配置弹窗打开
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
getThemeConfig.value.isDrawer = true;
|
themeConfig.value.isDrawer = true;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 初始化复制功能,防止点击两次才可以复制
|
// 初始化复制功能,防止点击两次才可以复制
|
||||||
onCopyConfigClick(copyConfigBtnRef.value.$el);
|
onCopyConfigClick(copyConfigBtnRef.value.$el);
|
||||||
@@ -650,7 +623,7 @@ export default defineComponent({
|
|||||||
// 存储布局配置
|
// 存储布局配置
|
||||||
const setLocalThemeConfig = () => {
|
const setLocalThemeConfig = () => {
|
||||||
removeLocal('themeConfig');
|
removeLocal('themeConfig');
|
||||||
setLocal('themeConfig', getThemeConfig.value);
|
setLocal('themeConfig', themeConfig.value);
|
||||||
};
|
};
|
||||||
// 存储布局配置全局主题样式(html根标签)
|
// 存储布局配置全局主题样式(html根标签)
|
||||||
const setLocalThemeConfigStyle = () => {
|
const setLocalThemeConfigStyle = () => {
|
||||||
@@ -664,7 +637,7 @@ export default defineComponent({
|
|||||||
text: () => JSON.stringify(copyThemeConfig),
|
text: () => JSON.stringify(copyThemeConfig),
|
||||||
});
|
});
|
||||||
clipboard.on('success', () => {
|
clipboard.on('success', () => {
|
||||||
getThemeConfig.value.isDrawer = false;
|
themeConfig.value.isDrawer = false;
|
||||||
ElMessage.success('复制成功');
|
ElMessage.success('复制成功');
|
||||||
clipboard.destroy();
|
clipboard.destroy();
|
||||||
});
|
});
|
||||||
@@ -676,37 +649,37 @@ export default defineComponent({
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 监听菜单点击,菜单字体背景高亮
|
// 监听菜单点击,菜单字体背景高亮
|
||||||
proxy.mittBus.on('onMenuClick', () => {
|
mittBus.on('onMenuClick', () => {
|
||||||
onMenuBarHighlightChange();
|
onMenuBarHighlightChange();
|
||||||
});
|
});
|
||||||
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
|
// 监听窗口大小改变,非默认布局,设置成默认布局(适配移动端)
|
||||||
proxy.mittBus.on('layoutMobileResize', (res: any) => {
|
mittBus.on('layoutMobileResize', (res: any) => {
|
||||||
getThemeConfig.value.layout = res.layout;
|
themeConfig.value.layout = res.layout;
|
||||||
getThemeConfig.value.isDrawer = false;
|
themeConfig.value.isDrawer = false;
|
||||||
initSetLayoutChange();
|
initSetLayoutChange();
|
||||||
onMenuBarHighlightChange();
|
onMenuBarHighlightChange();
|
||||||
getThemeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
});
|
});
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
// 刷新页面时,设置了值,直接取缓存中的值进行初始化
|
// 刷新页面时,设置了值,直接取缓存中的值进行初始化
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 顶栏背景渐变
|
// 顶栏背景渐变
|
||||||
if (getLocal('navbarsBgStyle') && getThemeConfig.value.isTopBarColorGradual) {
|
if (getLocal('navbarsBgStyle') && themeConfig.value.isTopBarColorGradual) {
|
||||||
const breadcrumbIndexEl: any = document.querySelector('.layout-navbars-breadcrumb-index');
|
const breadcrumbIndexEl: any = document.querySelector('.layout-navbars-breadcrumb-index');
|
||||||
breadcrumbIndexEl.style.cssText = getLocal('navbarsBgStyle');
|
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');
|
const asideEl: any = document.querySelector('.layout-container .el-aside');
|
||||||
asideEl.style.cssText = getLocal('asideBgStyle');
|
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');
|
const asideEl: any = document.querySelector('.layout-container .layout-columns-aside');
|
||||||
asideEl.style.cssText = getLocal('columnsBgStyle');
|
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');
|
let els = document.querySelector('.el-menu-item.is-active');
|
||||||
if (!els) return false;
|
if (!els) return false;
|
||||||
els.setAttribute('id', getLocal('menuBarHighlightId'));
|
els.setAttribute('id', getLocal('menuBarHighlightId'));
|
||||||
@@ -724,56 +697,39 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 取消监听菜单点击,菜单字体背景高亮
|
// 取消监听菜单点击,菜单字体背景高亮
|
||||||
proxy.mittBus.off('onMenuClick');
|
mittBus.off('onMenuClick');
|
||||||
proxy.mittBus.off('layoutMobileResize');
|
mittBus.off('layoutMobileResize');
|
||||||
});
|
|
||||||
return {
|
|
||||||
openDrawer,
|
|
||||||
onColorPickerChange,
|
|
||||||
onBgColorPickerChange,
|
|
||||||
onTopBarGradualChange,
|
|
||||||
onMenuBarGradualChange,
|
|
||||||
onColumnsMenuBarGradualChange,
|
|
||||||
onMenuBarHighlightChange,
|
|
||||||
onThemeConfigChange,
|
|
||||||
onIsFixedHeaderChange,
|
|
||||||
onIsShowLogoChange,
|
|
||||||
getThemeConfig,
|
|
||||||
onDrawerClose,
|
|
||||||
onAddFilterChange,
|
|
||||||
onSetLayout,
|
|
||||||
setLocalThemeConfig,
|
|
||||||
onClassicSplitMenuChange,
|
|
||||||
onIsBreadcrumbChange,
|
|
||||||
onSortableTagsViewChange,
|
|
||||||
copyConfigBtnRef,
|
|
||||||
onCopyConfigClick,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({openDrawer})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.layout-breadcrumb-seting-bar {
|
.layout-breadcrumb-seting-bar {
|
||||||
height: calc(100vh - 50px);
|
height: calc(100vh - 50px);
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
|
|
||||||
::v-deep(.el-scrollbar__view) {
|
::v-deep(.el-scrollbar__view) {
|
||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-breadcrumb-seting-bar-flex {
|
.layout-breadcrumb-seting-bar-flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&-label {
|
&-label {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-drawer-content-flex {
|
.layout-drawer-content-flex {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-content: flex-start;
|
align-content: flex-start;
|
||||||
margin: 0 -5px;
|
margin: 0 -5px;
|
||||||
|
|
||||||
.layout-drawer-content-item {
|
.layout-drawer-content-item {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
@@ -781,31 +737,39 @@ export default defineComponent({
|
|||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|
||||||
.el-container {
|
.el-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.el-aside-dark {
|
.el-aside-dark {
|
||||||
background-color: #b3c0d1;
|
background-color: #b3c0d1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-aside {
|
.el-aside {
|
||||||
background-color: #d3dce6;
|
background-color: #d3dce6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-header {
|
.el-header {
|
||||||
background-color: #b3c0d1;
|
background-color: #b3c0d1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-main {
|
.el-main {
|
||||||
background-color: #e9eef3;
|
background-color: #e9eef3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-circular {
|
.el-circular {
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer-layout-active {
|
.drawer-layout-active {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-tips-warp,
|
.layout-tips-warp,
|
||||||
.layout-tips-warp-active {
|
.layout-tips-warp-active {
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
@@ -817,6 +781,7 @@ export default defineComponent({
|
|||||||
border-color: var(--color-primary-light-4);
|
border-color: var(--color-primary-light-4);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
|
||||||
.layout-tips-box {
|
.layout-tips-box {
|
||||||
transition: inherit;
|
transition: inherit;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
@@ -825,6 +790,7 @@ export default defineComponent({
|
|||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary-light-4);
|
border-color: var(--color-primary-light-4);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
|
||||||
.layout-tips-txt {
|
.layout-tips-txt {
|
||||||
transition: inherit;
|
transition: inherit;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -844,30 +810,37 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-tips-warp-active {
|
.layout-tips-warp-active {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
|
|
||||||
.layout-tips-box {
|
.layout-tips-box {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
|
|
||||||
.layout-tips-txt {
|
.layout-tips-txt {
|
||||||
color: var(--color-primary) !important;
|
color: var(--color-primary) !important;
|
||||||
background-color: #e9eef3 !important;
|
background-color: #e9eef3 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.el-circular {
|
.el-circular {
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-tips-warp {
|
.layout-tips-warp {
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
|
|
||||||
.layout-tips-box {
|
.layout-tips-box {
|
||||||
transition: inherit;
|
transition: inherit;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
|
|
||||||
.layout-tips-txt {
|
.layout-tips-txt {
|
||||||
transition: inherit;
|
transition: inherit;
|
||||||
color: var(--color-primary) !important;
|
color: var(--color-primary) !important;
|
||||||
@@ -878,12 +851,15 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-config {
|
.copy-config {
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
|
|
||||||
.copy-config-btn {
|
.copy-config-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-config-last-btn {
|
.copy-config-last-btn {
|
||||||
margin: 10px 0 0;
|
margin: 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,44 +8,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item command="" :disabled="disabledSize === ''">默认</el-dropdown-item>
|
<el-dropdown-item command="" :disabled="state.disabledSize === ''">默认</el-dropdown-item>
|
||||||
<el-dropdown-item command="large" :disabled="disabledSize === 'large'">大型</el-dropdown-item>
|
<el-dropdown-item command="large" :disabled="state.disabledSize === 'large'">大型</el-dropdown-item>
|
||||||
<el-dropdown-item command="small" :disabled="disabledSize === 'small'">小型</el-dropdown-item>
|
<el-dropdown-item command="small" :disabled="state.disabledSize === 'small'">小型</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
<!-- <div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
|
<div class="layout-navbars-breadcrumb-user-icon" @click="onSearchClick">
|
||||||
<el-icon title="菜单搜索">
|
<el-icon title="菜单搜索">
|
||||||
<search />
|
<search />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div> -->
|
</div>
|
||||||
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
|
<div class="layout-navbars-breadcrumb-user-icon" @click="onLayoutSetingClick">
|
||||||
<el-icon title="布局设置">
|
<el-icon title="布局设置">
|
||||||
<setting />
|
<setting />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-navbars-breadcrumb-user-icon">
|
<div class="layout-navbars-breadcrumb-user-icon">
|
||||||
<el-popover
|
<el-popover placement="bottom" trigger="click" :visible="state.isShowUserNewsPopover" :width="300"
|
||||||
placement="bottom"
|
popper-class="el-popover-pupop-user-news">
|
||||||
trigger="click"
|
|
||||||
:visible="isShowUserNewsPopover"
|
|
||||||
:width="300"
|
|
||||||
popper-class="el-popover-pupop-user-news"
|
|
||||||
>
|
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-badge :is-dot="false" @click="isShowUserNewsPopover = !isShowUserNewsPopover">
|
<el-badge :is-dot="false" @click="state.isShowUserNewsPopover = !state.isShowUserNewsPopover">
|
||||||
<el-icon title="消息">
|
<el-icon title="消息">
|
||||||
<bell />
|
<bell />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</el-badge>
|
</el-badge>
|
||||||
</template>
|
</template>
|
||||||
<transition name="el-zoom-in-top">
|
<transition name="el-zoom-in-top">
|
||||||
<UserNews v-show="isShowUserNewsPopover" />
|
<UserNews v-show="state.isShowUserNewsPopover" />
|
||||||
</transition>
|
</transition>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-navbars-breadcrumb-user-icon mr10" @click="onScreenfullClick">
|
<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 />
|
<full-screen />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
<el-icon v-else title="开全屏">
|
<el-icon v-else title="开全屏">
|
||||||
@@ -54,8 +49,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
||||||
<span class="layout-navbars-breadcrumb-user-link" style="cursor: pointer">
|
<span class="layout-navbars-breadcrumb-user-link" style="cursor: pointer">
|
||||||
<img :src="getUserInfos.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
<img :src="userInfo.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||||
{{ getUserInfos.name || getUserInfos.username }}
|
{{ userInfo.name || userInfo.username }}
|
||||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||||
</span>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
@@ -70,23 +65,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts" name="layoutBreadcrumbUser">
|
||||||
import { ref, getCurrentInstance, computed, reactive, toRefs, onMounted } from 'vue';
|
import { ref, computed, reactive, onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus';
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||||
import screenfull from 'screenfull';
|
import screenfull from 'screenfull';
|
||||||
import { resetRoute } from '@/router/index.ts';
|
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 { clearSession, setLocal, getLocal, removeLocal } from '@/common/utils/storage.ts';
|
||||||
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
|
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
|
||||||
import Search from '@/views/layout/navBars/breadcrumb/search.vue';
|
import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
|
||||||
export default {
|
import mittBus from '@/common/utils/mitt';
|
||||||
name: 'layoutBreadcrumbUser',
|
|
||||||
components: { UserNews, SearchMenu: Search },
|
|
||||||
setup() {
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore();
|
|
||||||
const searchRef = ref();
|
const searchRef = ref();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
isScreenfull: false,
|
isScreenfull: false,
|
||||||
@@ -94,17 +87,12 @@ export default {
|
|||||||
disabledI18n: 'zh-cn',
|
disabledI18n: 'zh-cn',
|
||||||
disabledSize: '',
|
disabledSize: '',
|
||||||
});
|
});
|
||||||
// 获取用户信息 vuex
|
const { userInfo } = storeToRefs(useUserInfo());
|
||||||
const getUserInfos = computed(() => {
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
return store.state.userInfos.userInfos;
|
|
||||||
});
|
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 设置分割样式
|
// 设置分割样式
|
||||||
const layoutUserFlexNum = computed(() => {
|
const layoutUserFlexNum = computed(() => {
|
||||||
let { layout, isClassicSplitMenu } = getThemeConfig.value;
|
let { layout, isClassicSplitMenu } = themeConfig.value;
|
||||||
let num = '';
|
let num = '';
|
||||||
if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = '1';
|
if (layout === 'defaults' || (layout === 'classic' && !isClassicSplitMenu) || layout === 'columns') num = '1';
|
||||||
else num = '';
|
else num = '';
|
||||||
@@ -121,7 +109,7 @@ export default {
|
|||||||
};
|
};
|
||||||
// 布局配置 icon 点击时
|
// 布局配置 icon 点击时
|
||||||
const onLayoutSetingClick = () => {
|
const onLayoutSetingClick = () => {
|
||||||
proxy.mittBus.emit('openSetingsDrawer');
|
mittBus.emit('openSetingsDrawer');
|
||||||
};
|
};
|
||||||
// 下拉菜单点击时
|
// 下拉菜单点击时
|
||||||
const onHandleCommandClick = (path: string) => {
|
const onHandleCommandClick = (path: string) => {
|
||||||
@@ -162,19 +150,22 @@ export default {
|
|||||||
router.push(path);
|
router.push(path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 菜单搜索点击
|
|
||||||
|
// // 菜单搜索点击
|
||||||
const onSearchClick = () => {
|
const onSearchClick = () => {
|
||||||
searchRef.value.openSearch();
|
searchRef.value.openSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 组件大小改变
|
// 组件大小改变
|
||||||
const onComponentSizeChange = (size: string) => {
|
const onComponentSizeChange = (size: string) => {
|
||||||
removeLocal('themeConfig');
|
removeLocal('themeConfig');
|
||||||
getThemeConfig.value.globalComponentSize = size;
|
themeConfig.value.globalComponentSize = size;
|
||||||
setLocal('themeConfig', getThemeConfig.value);
|
setLocal('themeConfig', themeConfig.value);
|
||||||
// proxy.$ELEMENT.size = size;
|
// proxy.$ELEMENT.size = size;
|
||||||
initComponentSize();
|
initComponentSize();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化全局组件大小
|
// 初始化全局组件大小
|
||||||
const initComponentSize = () => {
|
const initComponentSize = () => {
|
||||||
switch (getLocal('themeConfig').globalComponentSize) {
|
switch (getLocal('themeConfig').globalComponentSize) {
|
||||||
@@ -192,25 +183,13 @@ export default {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (getLocal('themeConfig')) {
|
if (getLocal('themeConfig')) {
|
||||||
initComponentSize();
|
initComponentSize();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
|
||||||
getUserInfos,
|
|
||||||
onLayoutSetingClick,
|
|
||||||
onHandleCommandClick,
|
|
||||||
onScreenfullClick,
|
|
||||||
onSearchClick,
|
|
||||||
onComponentSizeChange,
|
|
||||||
searchRef,
|
|
||||||
layoutUserFlexNum,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -218,17 +197,20 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
&-link {
|
&-link {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
&-photo {
|
&-photo {
|
||||||
width: 25px;
|
width: 25px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-icon {
|
&-icon {
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -237,25 +219,29 @@ export default {
|
|||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.04);
|
background: rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
i {
|
i {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
animation: logoAnimation 0.3s ease-in-out;
|
animation: logoAnimation 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-dropdown) {
|
::v-deep(.el-dropdown) {
|
||||||
color: var(--bg-topBarColor);
|
color: var(--bg-topBarColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-badge) {
|
::v-deep(.el-badge) {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-badge__content.is-fixed) {
|
::v-deep(.el-badge__content.is-fixed) {
|
||||||
top: 12px;
|
top: 12px;
|
||||||
}
|
}
|
||||||
}
|
}</style>
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -7,17 +7,16 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
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 BreadcrumbIndex from '@/views/layout/navBars/breadcrumb/index.vue';
|
||||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutNavBars',
|
name: 'layoutNavBars',
|
||||||
components: { BreadcrumbIndex, TagsView },
|
components: { BreadcrumbIndex, TagsView },
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore();
|
|
||||||
// 是否显示 tagsView
|
// 是否显示 tagsView
|
||||||
const setShowTagsView = computed(() => {
|
const setShowTagsView = computed(() => {
|
||||||
let { layout, isTagsview } = store.state.themeConfig.themeConfig;
|
let { layout, isTagsview } = useThemeConfig().themeConfig;
|
||||||
return layout !== 'classic' && isTagsview;
|
return layout !== 'classic' && isTagsview;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,66 +7,86 @@
|
|||||||
data-popper-placement="bottom"
|
data-popper-placement="bottom"
|
||||||
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
||||||
:key="Math.random()"
|
:key="Math.random()"
|
||||||
v-show="isShow"
|
v-show="state.isShow"
|
||||||
>
|
>
|
||||||
<ul class="el-dropdown-menu">
|
<ul class="el-dropdown-menu">
|
||||||
<template v-for="(v, k) in dropdownList">
|
<template v-for="(v, k) in state.dropdownList">
|
||||||
<li
|
<li
|
||||||
class="el-dropdown-menu__item"
|
class="el-dropdown-menu__item"
|
||||||
aria-disabled="false"
|
aria-disabled="false"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:key="k"
|
:key="k"
|
||||||
v-if="!v.affix"
|
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>
|
<span>{{ v.txt }}</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="el-popper__arrow" style="left: 10px"></div>
|
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
||||||
import { computed, defineComponent, reactive, toRefs, onMounted, onUnmounted } from 'vue';
|
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||||
export default defineComponent({
|
|
||||||
name: 'layoutTagsViewContextmenu',
|
// 定义父组件传过来的值
|
||||||
props: {
|
const props = defineProps({
|
||||||
dropdown: {
|
dropdown: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
});
|
||||||
|
|
||||||
|
// 定义子组件向父组件传值/事件
|
||||||
|
const emit = defineEmits(['currentContextmenuClick']);
|
||||||
|
|
||||||
|
// 定义变量内容
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
isShow: false,
|
isShow: false,
|
||||||
dropdownList: [
|
dropdownList: [
|
||||||
{ id: 0, txt: '刷新', affix: false, icon: 'el-icon-refresh-right' },
|
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
|
||||||
{ id: 1, txt: '关闭', affix: false, icon: 'el-icon-close' },
|
{ contextMenuClickId: 1, txt: '关闭', affix: false, icon: 'Close' },
|
||||||
{ id: 2, txt: '关闭其他', affix: false, icon: 'el-icon-circle-close' },
|
{ contextMenuClickId: 2, txt: '关闭其他', affix: false, icon: 'CircleClose' },
|
||||||
{ id: 3, txt: '关闭所有', affix: false, icon: 'el-icon-folder-delete' },
|
{ contextMenuClickId: 3, txt: '关闭所有', affix: false, icon: 'FolderDelete' },
|
||||||
{
|
{
|
||||||
id: 4,
|
contextMenuClickId: 4,
|
||||||
txt: '当前页全屏',
|
txt: '当前页全屏',
|
||||||
affix: false,
|
affix: false,
|
||||||
icon: 'el-icon-full-screen',
|
icon: 'full-screen',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
path: {},
|
item: {} as any,
|
||||||
|
arrowLeft: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 父级传过来的坐标 x,y 值
|
// 父级传过来的坐标 x,y 值
|
||||||
const dropdowns = computed(() => {
|
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;
|
return props.dropdown;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// 当前项菜单点击
|
// 当前项菜单点击
|
||||||
const onCurrentContextmenuClick = (id: number) => {
|
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
|
||||||
emit('currentContextmenuClick', { id, path: state.path });
|
emit('currentContextmenuClick', { id: contextMenuClickId, path: state.item.fullPath });
|
||||||
};
|
};
|
||||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||||
const openContextmenu = (item: any) => {
|
const openContextmenu = (item: any) => {
|
||||||
state.path = item.fullPath;
|
state.item = item;
|
||||||
item.meta.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
|
item.meta?.isAffix ? (state.dropdownList[1].affix = true) : (state.dropdownList[1].affix = false);
|
||||||
closeContextmenu();
|
closeContextmenu();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
state.isShow = true;
|
state.isShow = true;
|
||||||
@@ -84,14 +104,21 @@ export default defineComponent({
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.body.removeEventListener('click', closeContextmenu);
|
document.body.removeEventListener('click', closeContextmenu);
|
||||||
});
|
});
|
||||||
return {
|
// 监听下拉菜单位置
|
||||||
dropdowns,
|
watch(
|
||||||
openContextmenu,
|
() => props.dropdown,
|
||||||
closeContextmenu,
|
({ x }) => {
|
||||||
onCurrentContextmenuClick,
|
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
|
||||||
...toRefs(state),
|
else state.arrowLeft = 10;
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 暴露变量
|
||||||
|
defineExpose({
|
||||||
|
openContextmenu,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -102,6 +129,7 @@ export default defineComponent({
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
.el-dropdown-menu__item {
|
.el-dropdown-menu__item {
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
|
white-space: nowrap;
|
||||||
i {
|
i {
|
||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +1,90 @@
|
|||||||
<template>
|
<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">
|
<el-scrollbar ref="scrollbarRef" @wheel.prevent="onHandleScroll">
|
||||||
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
|
<ul class="layout-navbars-tagsview-ul" :class="setTagsStyle" ref="tagsUlRef">
|
||||||
<li
|
<li v-for="(v, k) in state.tagsViewList" :key="k" class="layout-navbars-tagsview-ul-li" :data-name="v.name"
|
||||||
v-for="(v, k) in tagsViewList"
|
:class="{ 'is-active': isActive(v) }" @contextmenu.prevent="onContextmenu(v, $event)"
|
||||||
:key="k"
|
@click="onTagsClick(v, k)" :ref="
|
||||||
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) => {
|
(el) => {
|
||||||
if (el) tagsRefs[k] = el;
|
if (el) tagsRefs[k] = el;
|
||||||
}
|
}
|
||||||
"
|
">
|
||||||
>
|
<SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14"
|
||||||
<i class="iconfont icon-webicon318 layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)"></i>
|
v-if="isActive(v)" />
|
||||||
<SvgIcon
|
<SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont"
|
||||||
:name="v.meta.icon"
|
v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
|
||||||
class="layout-navbars-tagsview-ul-li-iconfont"
|
|
||||||
v-if="!isActive(v) && getThemeConfig.isTagsviewIcon"
|
|
||||||
/>
|
|
||||||
<span>{{ v.meta.title }}</span>
|
<span>{{ v.meta.title }}</span>
|
||||||
<template v-if="isActive(v)">
|
<template v-if="isActive(v)">
|
||||||
<SvgIcon
|
<SvgIcon name="RefreshRight" class="font14 ml5 layout-navbars-tagsview-ul-li-refresh"
|
||||||
name="RefreshRight"
|
@click.stop="refreshCurrentTagsView($route.fullPath)" />
|
||||||
class="ml5 layout-navbars-tagsview-ul-li-refresh"
|
<SvgIcon name="Close" class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-active"
|
||||||
@click.stop="refreshCurrentTagsView($route.fullPath)"
|
|
||||||
/>
|
|
||||||
<SvgIcon
|
|
||||||
name="Close"
|
|
||||||
class="layout-navbars-tagsview-ul-li-icon layout-icon-active"
|
|
||||||
v-if="!v.meta.isAffix"
|
v-if="!v.meta.isAffix"
|
||||||
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.path)"
|
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)" />
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<SvgIcon
|
|
||||||
name="Close"
|
<SvgIcon name="Close" class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-three"
|
||||||
class="layout-navbars-tagsview-ul-li-icon layout-icon-three"
|
|
||||||
v-if="!v.meta.isAffix"
|
v-if="!v.meta.isAffix"
|
||||||
@click.stop="closeCurrentTagsView(getThemeConfig.isShareTagsView ? v.path : v.path)"
|
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)" />
|
||||||
/>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<Contextmenu :dropdown="dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
|
<Contextmenu :dropdown="state.dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutTagsView">
|
||||||
import { toRefs, reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
|
import { reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
|
||||||
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute, useRouter, onBeforeRouteUpdate } from 'vue-router';
|
||||||
import screenfull from 'screenfull';
|
import screenfull from 'screenfull';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { storeToRefs } from 'pinia';
|
||||||
import { setSession, getSession, removeSession } from '@/common/utils/storage.ts';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
|
import { getSession, setSession, removeSession } from '@/common/utils/storage.ts';
|
||||||
|
import mittBus from '@/common/utils/mitt';
|
||||||
import Sortable from 'sortablejs';
|
import Sortable from 'sortablejs';
|
||||||
import Contextmenu from '@/views/layout/navBars/tagsView/contextmenu.vue';
|
import Contextmenu from '@/views/layout/navBars/tagsView/contextmenu.vue';
|
||||||
export default {
|
|
||||||
name: 'layoutTagsView',
|
|
||||||
components: { Contextmenu },
|
|
||||||
setup() {
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { proxy } = getCurrentInstance() as any;
|
||||||
const tagsRefs = ref([]);
|
const tagsRefs = ref([]) as any;
|
||||||
const scrollbarRef = ref();
|
const scrollbarRef = ref();
|
||||||
const contextmenuRef = ref();
|
const contextmenuRef = ref();
|
||||||
const tagsUlRef = ref();
|
const tagsUlRef = ref();
|
||||||
const store = useStore();
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state: any = reactive({
|
|
||||||
|
const state = reactive({
|
||||||
routePath: route.fullPath,
|
routePath: route.fullPath,
|
||||||
dropdown: { x: '', y: '' },
|
dropdown: { x: '', y: '' },
|
||||||
tagsRefsIndex: 0,
|
tagsRefsIndex: 0,
|
||||||
tagsViewList: [],
|
tagsViewList: [] as any,
|
||||||
sortable: '',
|
sortable: '' as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 动态设置 tagsView 风格样式
|
// 动态设置 tagsView 风格样式
|
||||||
const setTagsStyle = computed(() => {
|
const setTagsStyle = computed(() => {
|
||||||
return store.state.themeConfig.themeConfig.tagsStyle;
|
return themeConfig.value.tagsStyle;
|
||||||
});
|
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
||||||
const addBrowserSetSession = (tagsViewList: Array<object>) => {
|
const addBrowserSetSession = (tagsViewList: Array<object>) => {
|
||||||
setSession('tagsViewList', tagsViewList);
|
setSession('tagsViewList', tagsViewList);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取 vuex 中的 tagsViewRoutes 列表
|
// 获取 vuex 中的 tagsViewRoutes 列表
|
||||||
const getTagsViewRoutes = () => {
|
const getTagsViewRoutes = () => {
|
||||||
state.routePath = route.fullPath;
|
state.routePath = route.fullPath;
|
||||||
state.tagsViewList = [];
|
state.tagsViewList = [];
|
||||||
if (!store.state.themeConfig.themeConfig.isCacheTagsView) removeSession('tagsViewList');
|
if (!themeConfig.value.isCacheTagsView) removeSession('tagsViewList');
|
||||||
initTagsView();
|
initTagsView();
|
||||||
};
|
};
|
||||||
// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
||||||
const initTagsView = () => {
|
const initTagsView = () => {
|
||||||
if (getSession('tagsViewList') && store.state.themeConfig.themeConfig.isCacheTagsView) {
|
if (getSession('tagsViewList') && themeConfig.value.isCacheTagsView) {
|
||||||
state.tagsViewList = getSession('tagsViewList');
|
state.tagsViewList = getSession('tagsViewList');
|
||||||
} else {
|
} else {
|
||||||
// state.tagsViews.map((v: any) => {
|
state.tagsViewList?.map((v: any) => {
|
||||||
// if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
||||||
// });
|
});
|
||||||
addTagsView(route.fullPath);
|
addTagsView(route.fullPath);
|
||||||
}
|
}
|
||||||
// 初始化当前元素(li)的下标
|
// 初始化当前元素(li)的下标
|
||||||
@@ -123,34 +105,50 @@ export default {
|
|||||||
return false;
|
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:
|
// 2、刷新当前 tagsView:
|
||||||
// path为fullPath
|
// path为fullPath
|
||||||
const refreshCurrentTagsView = (path: string) => {
|
const refreshCurrentTagsView = (path: string) => {
|
||||||
proxy.mittBus.emit('onTagsViewRefreshRouterView', path);
|
mittBus.emit('onTagsViewRefreshRouterView', path);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
|
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
|
||||||
// path为fullPath
|
// path为fullPath
|
||||||
const closeCurrentTagsView = (path: string) => {
|
const closeCurrentTagsView = (path: string) => {
|
||||||
console.log(path)
|
|
||||||
state.tagsViewList.map((v: any, k: number, arr: any) => {
|
state.tagsViewList.map((v: any, k: number, arr: any) => {
|
||||||
if (!v.meta.isAffix) {
|
if (!v.meta.isAffix) {
|
||||||
if (v.fullPath === path) {
|
if (v.fullPath === path) {
|
||||||
state.tagsViewList.splice(k, 1);
|
state.tagsViewList.splice(k, 1);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 最后一个
|
if (state.routePath !== path) {
|
||||||
if (state.tagsViewList.length === k) router.push({ path: arr[arr.length - 1].path, query: arr[arr.length - 1].query });
|
return;
|
||||||
// 否则,跳转到下一个
|
}
|
||||||
else router.push({ path: arr[k].path, query: arr[k].query });
|
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);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// addBrowserSetSession(state.tagsViewList);
|
addBrowserSetSession(state.tagsViewList);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||||
@@ -174,7 +172,7 @@ export default {
|
|||||||
else router.push({ path: v.path, query: route.query });
|
else router.push({ path: v.path, query: route.query });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// addBrowserSetSession(state.tagsViewList);
|
addBrowserSetSession(state.tagsViewList);
|
||||||
};
|
};
|
||||||
// 6、开启当前页面全屏
|
// 6、开启当前页面全屏
|
||||||
const openCurrenFullscreen = (path: string) => {
|
const openCurrenFullscreen = (path: string) => {
|
||||||
@@ -298,8 +296,8 @@ export default {
|
|||||||
const initSortable = () => {
|
const initSortable = () => {
|
||||||
const el: any = document.querySelector('.layout-navbars-tagsview-ul');
|
const el: any = document.querySelector('.layout-navbars-tagsview-ul');
|
||||||
if (!el) return false;
|
if (!el) return false;
|
||||||
if (!getThemeConfig.value.isSortableTagsView) state.sortable && state.sortable.destroy();
|
if (!themeConfig.value.isSortableTagsView) state.sortable && state.sortable.destroy();
|
||||||
if (getThemeConfig.value.isSortableTagsView) {
|
if (themeConfig.value.isSortableTagsView) {
|
||||||
state.sortable = Sortable.create(el, {
|
state.sortable = Sortable.create(el, {
|
||||||
animation: 300,
|
animation: 300,
|
||||||
dataIdAttr: 'data-name',
|
dataIdAttr: 'data-name',
|
||||||
@@ -310,33 +308,41 @@ export default {
|
|||||||
if (v.name === val) sortEndList.push({ ...v });
|
if (v.name === val) sortEndList.push({ ...v });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// addBrowserSetSession(sortEndList);
|
addBrowserSetSession(sortEndList);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听路由的变化,动态赋值给 tagsView
|
// 监听路由的变化,动态赋值给 tagsView
|
||||||
// watch(store.state, (val) => {
|
// watch(
|
||||||
// if (val.tagsViews.tagsViews.length === state.tagsViewList.length) return false;
|
// pinia.state,
|
||||||
|
// (val) => {
|
||||||
|
// if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
|
||||||
// getTagsViewRoutes();
|
// getTagsViewRoutes();
|
||||||
// });
|
// },
|
||||||
|
// {
|
||||||
|
// deep: true,
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
// 页面加载前
|
// 页面加载前
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
|
// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
|
||||||
proxy.mittBus.on('onCurrentContextmenuClick', (data: object) => {
|
mittBus.on('onCurrentContextmenuClick', (data: object) => {
|
||||||
onCurrentContextmenuClick(data);
|
onCurrentContextmenuClick(data);
|
||||||
});
|
});
|
||||||
// 监听布局配置界面开启/关闭拖拽
|
// 监听布局配置界面开启/关闭拖拽
|
||||||
proxy.mittBus.on('openOrCloseSortable', () => {
|
mittBus.on('openOrCloseSortable', () => {
|
||||||
initSortable();
|
initSortable();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// 页面卸载时
|
// 页面卸载时
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 取消非本页面调用监听
|
// 取消非本页面调用监听
|
||||||
proxy.mittBus.off('onCurrentContextmenuClick');
|
mittBus.off('onCurrentContextmenuClick');
|
||||||
// 取消监听布局配置界面开启/关闭拖拽
|
// 取消监听布局配置界面开启/关闭拖拽
|
||||||
proxy.mittBus.off('openOrCloseSortable');
|
mittBus.off('openOrCloseSortable');
|
||||||
});
|
});
|
||||||
// 页面更新时
|
// 页面更新时
|
||||||
onBeforeUpdate(() => {
|
onBeforeUpdate(() => {
|
||||||
@@ -355,35 +361,19 @@ export default {
|
|||||||
getTagsRefsIndex(to.fullPath);
|
getTagsRefsIndex(to.fullPath);
|
||||||
tagsViewmoveToCurrentTag();
|
tagsViewmoveToCurrentTag();
|
||||||
});
|
});
|
||||||
return {
|
|
||||||
isActive,
|
|
||||||
onContextmenu,
|
|
||||||
getTagsViewRoutes,
|
|
||||||
onTagsClick,
|
|
||||||
tagsRefs,
|
|
||||||
contextmenuRef,
|
|
||||||
scrollbarRef,
|
|
||||||
tagsUlRef,
|
|
||||||
onHandleScroll,
|
|
||||||
getThemeConfig,
|
|
||||||
setTagsStyle,
|
|
||||||
refreshCurrentTagsView,
|
|
||||||
closeCurrentTagsView,
|
|
||||||
onCurrentContextmenuClick,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.layout-navbars-tagsview {
|
.layout-navbars-tagsview {
|
||||||
flex: 1;
|
background-color: var(--el-color-white);
|
||||||
background-color: #ffffff;
|
border-bottom: 1px solid var(--next-border-color-light);
|
||||||
border-bottom: 1px solid #f1f2f3;
|
position: relative;
|
||||||
::v-deep(.el-scrollbar__wrap) {
|
z-index: 4;
|
||||||
|
|
||||||
|
:deep(.el-scrollbar__wrap) {
|
||||||
overflow-x: auto !important;
|
overflow-x: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-ul {
|
&-ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -391,16 +381,17 @@ export default {
|
|||||||
height: 34px;
|
height: 34px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #606266;
|
color: var(--el-text-color-regular);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
|
|
||||||
&-li {
|
&-li {
|
||||||
height: 26px;
|
height: 26px;
|
||||||
line-height: 26px;
|
line-height: 26px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid #e6e6e6;
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
@@ -408,16 +399,19 @@ export default {
|
|||||||
z-index: 0;
|
z-index: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-primary-light-9);
|
background-color: var(--el-color-primary-light-9);
|
||||||
color: var(--color-primary);
|
color: var(--el-color-primary);
|
||||||
border-color: var(--color-primary-light-6);
|
border-color: var(--el-color-primary-light-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-iconfont {
|
&-iconfont {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -5px;
|
left: -5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-icon {
|
&-icon {
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -426,99 +420,101 @@ export default {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
right: -5px;
|
right: -5px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #fff;
|
color: var(--el-color-white);
|
||||||
background-color: var(--color-primary-light-3);
|
background-color: var(--el-color-primary-light-3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-icon-active {
|
.layout-icon-active {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-icon-three {
|
.layout-icon-three {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-active {
|
.is-active {
|
||||||
color: #ffffff;
|
color: var(--el-color-white);
|
||||||
background: var(--color-primary);
|
background: var(--el-color-primary);
|
||||||
border-color: var(--color-primary);
|
border-color: var(--el-color-primary);
|
||||||
|
transition: border-color 3s ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 风格2
|
// 风格2
|
||||||
.tags-style-two {
|
.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 {
|
.layout-navbars-tagsview-ul-li {
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 3px !important;
|
border-radius: 3px !important;
|
||||||
|
|
||||||
.layout-icon-active {
|
.layout-icon-active {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-icon-three {
|
.layout-icon-three {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: none !important;
|
background: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-active {
|
.is-active {
|
||||||
background: none !important;
|
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 {
|
.layout-navbars-tagsview-shadow {
|
||||||
box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
|
box-shadow: rgb(0 21 41 / 4%) 0px 1px 4px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="el-menu-horizontal-warp">
|
<div class="el-menu-horizontal-warp">
|
||||||
<el-scrollbar @wheel.prevent="onElMenuHorizontalScroll" ref="elMenuHorizontalScrollRef">
|
<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">
|
<template v-for="val in menuLists">
|
||||||
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
<el-submenu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
|
||||||
<template #title>
|
<template #title>
|
||||||
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
|
<SvgIcon :name="val.meta.icon"/>
|
||||||
<span>{{ val.meta.title }}</span>
|
<span>{{ val.meta.title }}</span>
|
||||||
</template>
|
</template>
|
||||||
<SubItem :chil="val.children" />
|
<SubItem :chil="val.children" />
|
||||||
</el-submenu>
|
</el-submenu>
|
||||||
<el-menu-item :index="val.path" :key="val.path" v-else>
|
<el-menu-item :index="val.path" :key="val?.path" v-else>
|
||||||
<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.isIframe)">
|
||||||
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
|
<SvgIcon :name="val.meta.icon"/>
|
||||||
{{ val.meta.title }}
|
{{ val.meta.title }}
|
||||||
</template>
|
</template>
|
||||||
<template #title v-else>
|
<template #title v-else>
|
||||||
<a :href="val.meta.link" target="_blank">
|
<a :href="val.meta.link" target="_blank">
|
||||||
<i :class="val.meta.icon ? val.meta.icon : ''"></i>
|
<SvgIcon :name="val.meta.icon"/>
|
||||||
{{ val.meta.title }}
|
{{ val.meta.title }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
@@ -28,24 +29,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="navMenuHorizontal">
|
||||||
import { toRefs, reactive, computed, defineComponent, getCurrentInstance, onMounted, nextTick } from 'vue';
|
import { reactive, computed, getCurrentInstance, onMounted, nextTick } from 'vue';
|
||||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||||
import { useStore } from '@/store/index.ts';
|
|
||||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
||||||
export default defineComponent({
|
import { useRoutesList } from '@/store/routesList';
|
||||||
name: 'navMenuHorizontal',
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
components: { SubItem },
|
import mittBus from '@/common/utils/mitt';
|
||||||
props: {
|
|
||||||
|
// 定义父组件传过来的值
|
||||||
|
const props = defineProps({
|
||||||
|
// 菜单列表
|
||||||
menuList: {
|
menuList: {
|
||||||
type: Array,
|
type: Array<any>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup(props) {
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { proxy } = getCurrentInstance() as any;
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const store = useStore();
|
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
defaultActive: null,
|
defaultActive: null,
|
||||||
});
|
});
|
||||||
@@ -70,7 +72,7 @@ export default defineComponent({
|
|||||||
// 设置页面当前路由高亮
|
// 设置页面当前路由高亮
|
||||||
const setCurrentRouterHighlight = (path: string) => {
|
const setCurrentRouterHighlight = (path: string) => {
|
||||||
const currentPathSplit = path.split('/');
|
const currentPathSplit = path.split('/');
|
||||||
if (store.state.themeConfig.themeConfig.layout === 'classic') {
|
if (useThemeConfig().themeConfig.layout === 'classic') {
|
||||||
state.defaultActive = `/${currentPathSplit[1]}`;
|
state.defaultActive = `/${currentPathSplit[1]}`;
|
||||||
} else {
|
} else {
|
||||||
state.defaultActive = path;
|
state.defaultActive = path;
|
||||||
@@ -90,7 +92,7 @@ export default defineComponent({
|
|||||||
const setSendClassicChildren = (path: string) => {
|
const setSendClassicChildren = (path: string) => {
|
||||||
const currentPathSplit = path.split('/');
|
const currentPathSplit = path.split('/');
|
||||||
let currentData: any = {};
|
let currentData: any = {};
|
||||||
filterRoutesFun(store.state.routesList.routesList).map((v, k) => {
|
filterRoutesFun(useRoutesList().routesList).map((v, k) => {
|
||||||
if (v.path === `/${currentPathSplit[1]}`) {
|
if (v.path === `/${currentPathSplit[1]}`) {
|
||||||
v['k'] = k;
|
v['k'] = k;
|
||||||
currentData['item'] = [{ ...v }];
|
currentData['item'] = [{ ...v }];
|
||||||
@@ -102,7 +104,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
// 菜单激活回调
|
// 菜单激活回调
|
||||||
const onHorizontalSelect = (path: string) => {
|
const onHorizontalSelect = (path: string) => {
|
||||||
proxy.mittBus.emit('setSendClassicChildren', setSendClassicChildren(path));
|
mittBus.emit('setSendClassicChildren', setSendClassicChildren(path));
|
||||||
};
|
};
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -112,15 +114,7 @@ export default defineComponent({
|
|||||||
// 路由更新时
|
// 路由更新时
|
||||||
onBeforeRouteUpdate((to) => {
|
onBeforeRouteUpdate((to) => {
|
||||||
setCurrentRouterHighlight(to.path);
|
setCurrentRouterHighlight(to.path);
|
||||||
proxy.mittBus.emit('onMenuClick');
|
mittBus.emit('onMenuClick');
|
||||||
});
|
|
||||||
return {
|
|
||||||
menuLists,
|
|
||||||
onElMenuHorizontalScroll,
|
|
||||||
onHorizontalSelect,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -129,12 +123,15 @@ export default defineComponent({
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
|
|
||||||
::v-deep(.el-scrollbar__bar.is-vertical) {
|
::v-deep(.el-scrollbar__bar.is-vertical) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(a) {
|
::v-deep(a) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu.el-menu--horizontal {
|
.el-menu.el-menu--horizontal {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<sub-item :chil="val.children" />
|
<sub-item :chil="val.children" />
|
||||||
</el-sub-menu>
|
</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>
|
||||||
<template v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
|
<template v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
|
||||||
<SvgIcon :name="val.meta.icon"/>
|
<SvgIcon :name="val.meta.icon"/>
|
||||||
<span>{{ val.meta.title }}</span>
|
<span>{{ val.meta.title }}</span>
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, defineComponent } from 'vue';
|
||||||
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'navMenuSubItem',
|
name: 'navMenuSubItem',
|
||||||
props: {
|
props: {
|
||||||
@@ -35,7 +36,7 @@ export default defineComponent({
|
|||||||
setup(props) {
|
setup(props) {
|
||||||
// 获取父级菜单数据
|
// 获取父级菜单数据
|
||||||
const chils = computed(() => {
|
const chils = computed(() => {
|
||||||
return props.chil;
|
return props.chil as any;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
chils,
|
chils,
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-menu
|
<el-menu router :default-active="state.defaultActive" background-color="transparent" :collapse="setIsCollapse"
|
||||||
router
|
:unique-opened="themeConfig.isUniqueOpened" :collapse-transition="false">
|
||||||
:default-active="defaultActive"
|
|
||||||
background-color="transparent"
|
|
||||||
:collapse="setIsCollapse"
|
|
||||||
:unique-opened="getThemeConfig.isUniqueOpened"
|
|
||||||
:collapse-transition="false"
|
|
||||||
>
|
|
||||||
<template v-for="val in menuLists">
|
<template v-for="val in menuLists">
|
||||||
<el-sub-menu :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>
|
<template #title>
|
||||||
@@ -15,36 +9,36 @@
|
|||||||
</template>
|
</template>
|
||||||
<SubItem :chil="val.children" />
|
<SubItem :chil="val.children" />
|
||||||
</el-sub-menu>
|
</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"/>
|
<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.isIframe)">
|
||||||
<span>{{ val.meta.title }}</span>
|
<span>{{ val.meta.title }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #title v-else>
|
<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>
|
</el-menu-item>
|
||||||
</template>
|
</template>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="navMenuVertical">
|
||||||
import { toRefs, reactive, computed, defineComponent, getCurrentInstance } from 'vue';
|
import { reactive, computed } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||||
import { useStore } from '@/store/index.ts';
|
|
||||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
||||||
export default defineComponent({
|
import mittBus from '@/common/utils/mitt';
|
||||||
name: 'navMenuVertical',
|
|
||||||
components: { SubItem },
|
// 定义父组件传过来的值
|
||||||
props: {
|
const props = defineProps({
|
||||||
|
// 菜单列表
|
||||||
menuList: {
|
menuList: {
|
||||||
type: Array,
|
type: Array<any>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
setup(props) {
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
const store = useStore();
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
defaultActive: route.path,
|
defaultActive: route.path,
|
||||||
@@ -53,27 +47,15 @@ export default defineComponent({
|
|||||||
const menuLists = computed(() => {
|
const menuLists = computed(() => {
|
||||||
return props.menuList;
|
return props.menuList;
|
||||||
});
|
});
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
// 设置菜单的收起/展开
|
// 设置菜单的收起/展开
|
||||||
const setIsCollapse = computed(() => {
|
const setIsCollapse = computed(() => {
|
||||||
return document.body.clientWidth < 1000 ? false : getThemeConfig.value.isCollapse;
|
return document.body.clientWidth < 1000 ? false : themeConfig.value.isCollapse;
|
||||||
});
|
});
|
||||||
// 路由更新时
|
// 路由更新时
|
||||||
onBeforeRouteUpdate((to) => {
|
onBeforeRouteUpdate((to) => {
|
||||||
state.defaultActive = to.path;
|
state.defaultActive = to.path;
|
||||||
proxy.mittBus.emit('onMenuClick');
|
mittBus.emit('onMenuClick');
|
||||||
const clientWidth = document.body.clientWidth;
|
const clientWidth = document.body.clientWidth;
|
||||||
if (clientWidth < 1000) getThemeConfig.value.isCollapse = false;
|
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
|
||||||
});
|
|
||||||
return {
|
|
||||||
menuLists,
|
|
||||||
getThemeConfig,
|
|
||||||
setIsCollapse,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, onMounted, onBeforeMount, onUnmounted, nextTick, getCurrentInstance } from 'vue';
|
import { defineComponent, reactive, toRefs, onMounted, onBeforeMount, onUnmounted, nextTick, getCurrentInstance } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
|
import mittBus from '@/common/utils/mitt';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'layoutIfameView',
|
name: 'layoutIfameView',
|
||||||
props: {
|
props: {
|
||||||
@@ -18,7 +19,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
iframeLoading: true,
|
iframeLoading: true,
|
||||||
@@ -38,7 +38,7 @@ export default defineComponent({
|
|||||||
// 页面加载前
|
// 页面加载前
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
state.iframeUrl = props.meta.link;
|
state.iframeUrl = props.meta.link;
|
||||||
proxy.mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
||||||
if (route.path !== path) return false;
|
if (route.path !== path) return false;
|
||||||
emit('getCurrentRouteMeta');
|
emit('getCurrentRouteMeta');
|
||||||
});
|
});
|
||||||
@@ -49,7 +49,7 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
// 页面卸载时
|
// 页面卸载时
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
proxy.mittBus.off('onTagsViewRefreshRouterView', () => {});
|
mittBus.off('onTagsViewRefreshRouterView', () => {});
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
|
|||||||
@@ -2,24 +2,25 @@
|
|||||||
<div class="h100">
|
<div class="h100">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<transition :name="setTransitionName" mode="out-in">
|
<transition :name="setTransitionName" mode="out-in">
|
||||||
<keep-alive :include="keepAliveNameList">
|
<keep-alive :include="state.keepAliveNameList">
|
||||||
<component :is="Component" :key="refreshRouterViewKey" class="w100" />
|
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
</router-view>
|
</router-view>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup name="layoutParentView">
|
||||||
import { computed, defineComponent, toRefs, reactive, getCurrentInstance, onBeforeMount, onUnmounted, nextTick, ref } from 'vue';
|
import { computed, reactive, onBeforeMount, onUnmounted, nextTick } from 'vue';
|
||||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { storeToRefs } from 'pinia';
|
||||||
export default defineComponent({
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
name: 'layoutParentView',
|
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||||
setup() {
|
import mittBus from '@/common/utils/mitt';
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const store = useStore();
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
|
const { keepAliveNames } = storeToRefs(useKeepALiveNames());
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
refreshRouterViewKey: null,
|
refreshRouterViewKey: null,
|
||||||
keepAliveNameList: [],
|
keepAliveNameList: [],
|
||||||
@@ -29,41 +30,25 @@ export default defineComponent({
|
|||||||
// onBeforeRouteUpdate((to: any) => {
|
// onBeforeRouteUpdate((to: any) => {
|
||||||
// state.refreshRouterViewKey = decodeURI(to.fullPath);
|
// 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(() => {
|
onBeforeMount(() => {
|
||||||
state.keepAliveNameList = getKeepAliveNames.value;
|
state.keepAliveNameList = keepAliveNames.value;
|
||||||
proxy.mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
mittBus.on('onTagsViewRefreshRouterView', (path: string) => {
|
||||||
if (decodeURI(route.fullPath) !== path) return false;
|
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;
|
state.refreshRouterViewKey = route.path;
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
state.refreshRouterViewKey = null;
|
state.refreshRouterViewKey = null;
|
||||||
state.keepAliveNameList = getKeepAliveNames.value;
|
state.keepAliveNameList = keepAliveNames.value;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// 设置主界面切换动画
|
||||||
|
const setTransitionName = computed(() => {
|
||||||
|
return themeConfig.value.animation;
|
||||||
|
});
|
||||||
// 页面卸载时
|
// 页面卸载时
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
proxy.mittBus.off('onTagsViewRefreshRouterView');
|
mittBus.off('onTagsViewRefreshRouterView');
|
||||||
});
|
|
||||||
return {
|
|
||||||
getThemeConfig,
|
|
||||||
getKeepAliveNames,
|
|
||||||
setTransitionName,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="password">
|
<el-form-item prop="password">
|
||||||
<el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password"
|
<el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password"
|
||||||
autocomplete="off" show-password>
|
autocomplete="off" @keyup.enter="login" show-password>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="isUseLoginCaptcha" prop="captcha">
|
<el-form-item v-if="isUseLoginCaptcha" prop="captcha">
|
||||||
@@ -63,14 +63,14 @@
|
|||||||
import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
|
import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { initBackEndControlRoutesFun } from '@/router/index.ts';
|
import { initRouter } from '@/router/index';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage';
|
||||||
import { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage.ts';
|
import { formatAxis } from '@/common/utils/format';
|
||||||
import { formatAxis } from '@/common/utils/formatTime.ts';
|
|
||||||
import openApi from '@/common/openApi';
|
import openApi from '@/common/openApi';
|
||||||
import { RsaEncrypt } from '@/common/rsa';
|
import { RsaEncrypt } from '@/common/rsa';
|
||||||
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
|
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
|
||||||
import { letterAvatar } from '@/common/utils/string';
|
import { letterAvatar } from '@/common/utils/string';
|
||||||
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||||
@@ -78,7 +78,6 @@ const rules = {
|
|||||||
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = useStore();
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const loginFormRef: any = ref(null);
|
const loginFormRef: any = ref(null);
|
||||||
@@ -138,7 +137,7 @@ const getCaptcha = async () => {
|
|||||||
if (!state.isUseLoginCaptcha) {
|
if (!state.isUseLoginCaptcha) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let res: any = await openApi.captcha();
|
let res: any = await openApi.captcha.request();
|
||||||
state.captchaImage = res.base64Captcha;
|
state.captchaImage = res.base64Captcha;
|
||||||
state.loginForm.cid = res.cid;
|
state.loginForm.cid = res.cid;
|
||||||
};
|
};
|
||||||
@@ -167,10 +166,9 @@ const onSignIn = async () => {
|
|||||||
try {
|
try {
|
||||||
const loginReq = { ...state.loginForm };
|
const loginReq = { ...state.loginForm };
|
||||||
loginReq.password = await RsaEncrypt(originPwd);
|
loginReq.password = await RsaEncrypt(originPwd);
|
||||||
loginRes = await openApi.login(loginReq);
|
loginRes = await openApi.login.request(loginReq);
|
||||||
// 存储 token 到浏览器缓存
|
// 存储 token 到浏览器缓存
|
||||||
setSession('token', loginRes.token);
|
setSession('token', loginRes.token);
|
||||||
setSession('menus', loginRes.menus);
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
state.loading.signIn = false;
|
state.loading.signIn = false;
|
||||||
state.loginForm.captcha = '';
|
state.loginForm.captcha = '';
|
||||||
@@ -192,8 +190,6 @@ const onSignIn = async () => {
|
|||||||
// 头像
|
// 头像
|
||||||
photo: letterAvatar(state.loginForm.username),
|
photo: letterAvatar(state.loginForm.username),
|
||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
// // 菜单资源code数组
|
|
||||||
// menus: loginRes.menus,
|
|
||||||
permissions: loginRes.permissions,
|
permissions: loginRes.permissions,
|
||||||
lastLoginTime: loginRes.lastLoginTime,
|
lastLoginTime: loginRes.lastLoginTime,
|
||||||
lastLoginIp: loginRes.lastLoginIp,
|
lastLoginIp: loginRes.lastLoginIp,
|
||||||
@@ -202,19 +198,9 @@ const onSignIn = async () => {
|
|||||||
// 存储用户信息到浏览器缓存
|
// 存储用户信息到浏览器缓存
|
||||||
setUserInfo2Session(userInfos);
|
setUserInfo2Session(userInfos);
|
||||||
// 1、请注意执行顺序(存储用户信息到vuex)
|
// 1、请注意执行顺序(存储用户信息到vuex)
|
||||||
store.dispatch('userInfos/setUserInfos', userInfos);
|
useUserInfo().setUserInfo(userInfos);
|
||||||
if (!store.state.themeConfig.themeConfig.isRequestRoutes) {
|
await initRouter();
|
||||||
// 前端控制路由,2、请注意执行顺序
|
|
||||||
// await initAllFun();
|
|
||||||
await initBackEndControlRoutesFun();
|
|
||||||
signInSuccess();
|
signInSuccess();
|
||||||
} else {
|
|
||||||
// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
|
||||||
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
|
|
||||||
await initBackEndControlRoutesFun();
|
|
||||||
// 执行完 initBackEndControlRoutesFun,再执行 signInSuccess
|
|
||||||
signInSuccess();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 登录成功后的跳转
|
// 登录成功后的跳转
|
||||||
@@ -247,7 +233,7 @@ const changePwd = () => {
|
|||||||
const changePwdReq: any = { ...form };
|
const changePwdReq: any = { ...form };
|
||||||
changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
|
changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
|
||||||
changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
|
changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
|
||||||
await openApi.changePwd(changePwdReq);
|
await openApi.changePwd.request(changePwdReq);
|
||||||
ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
|
ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
|
||||||
state.loginForm.password = state.changePwdDialog.form.newPassword;
|
state.loginForm.password = state.changePwdDialog.form.newPassword;
|
||||||
state.changePwdDialog.visible = false;
|
state.changePwdDialog.visible = false;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<div class="login-logo">
|
<div class="login-logo">
|
||||||
<span>{{ getThemeConfig.globalViceTitle }}</span>
|
<span>{{ themeConfig.globalViceTitle }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-content" :class="{ 'login-content-mobile': tabsActiveName === 'mobile' }">
|
<div class="login-content" :class="{ 'login-content-mobile': tabsActiveName === 'mobile' }">
|
||||||
<div class="login-content-main">
|
<div class="login-content-main">
|
||||||
@@ -24,19 +24,20 @@
|
|||||||
</div> -->
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-copyright">
|
<!-- <div class="login-copyright">
|
||||||
<div class="mb5 login-copyright-company">mayfly</div>
|
<div class="mb5 login-copyright-company">mayfly</div>
|
||||||
<div class="login-copyright-msg">mayfly</div>
|
<div class="login-copyright-msg">mayfly</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toRefs, reactive, computed } from 'vue';
|
import { toRefs, reactive } from 'vue';
|
||||||
import Account from '@/views/login/component/AccountLogin.vue';
|
import Account from '@/views/login/component/AccountLogin.vue';
|
||||||
import { useStore } from '@/store/index.ts';
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
|
|
||||||
const store = useStore();
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
tabsActiveName: 'account',
|
tabsActiveName: 'account',
|
||||||
isTabPaneShow: true,
|
isTabPaneShow: true,
|
||||||
@@ -47,10 +48,6 @@ const {
|
|||||||
tabsActiveName,
|
tabsActiveName,
|
||||||
} = toRefs(state)
|
} = toRefs(state)
|
||||||
|
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
|
||||||
return store.state.themeConfig.themeConfig;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 切换密码、手机登录
|
// 切换密码、手机登录
|
||||||
const onTabsClick = () => {
|
const onTabsClick = () => {
|
||||||
|
|||||||
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>
|
||||||
65
mayfly_go_web/src/views/ops/component/TagInfo.vue
Normal file
65
mayfly_go_web/src/views/ops/component/TagInfo.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<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 (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,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags"
|
<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"
|
:render-after-expand="true" :default-expanded-keys="[selectTags]" show-checkbox check-strictly node-key="id"
|
||||||
:props="{
|
:props="{
|
||||||
value: 'id',
|
value: 'id',
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -311,10 +311,10 @@ const addDefaultRows = () => {
|
|||||||
{ name: 'id', type: 'bigint', length: '20', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
|
{ 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_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: 'creator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人姓名' },
|
||||||
{ name: 'creat_time', type: 'datetime', length: '', 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: 'updater_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
|
{ name: 'updator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
|
||||||
{ name: 'updater', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名' },
|
{ name: 'updator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名' },
|
||||||
{ name: 'update_time', type: 'datetime', length: '', 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: '修改时间' },
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -406,7 +406,8 @@ const genSql = () => {
|
|||||||
let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : '\'' + cl.value + '\'') : '';
|
let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : '\'' + cl.value + '\'') : '';
|
||||||
let defVal = `${val ? ('DEFAULT ' + val) : ''}`;
|
let defVal = `${val ? ('DEFAULT ' + val) : ''}`;
|
||||||
let length = cl.length ? `(${cl.length})` : '';
|
let length = cl.length ? `(${cl.length})` : '';
|
||||||
return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${cl.auto_increment ? 'AUTO_INCREMENT' : ''} ${defVal} comment '${cl.remark || ''}' `
|
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;
|
let data = state.tableData;
|
||||||
@@ -416,7 +417,7 @@ const genSql = () => {
|
|||||||
let primary_key = '';
|
let primary_key = '';
|
||||||
let fields: string[] = [];
|
let fields: string[] = [];
|
||||||
data.fields.res.forEach((item) => {
|
data.fields.res.forEach((item) => {
|
||||||
fields.push(genColumnBasicSql(item));
|
item.name && fields.push(genColumnBasicSql(item));
|
||||||
if (item.pri) {
|
if (item.pri) {
|
||||||
primary_key += `${item.name},`;
|
primary_key += `${item.name},`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
|
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
|
||||||
:destroy-on-close="true" width="38%">
|
:destroy-on-close="true" width="38%">
|
||||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
|
<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>
|
<el-form-item prop="tagId" label="标签:" required>
|
||||||
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
|
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -43,14 +45,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<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="database" label="数据库名:" required>
|
<el-form-item prop="database" label="数据库名:" required>
|
||||||
<el-col :span="19">
|
<el-col :span="19">
|
||||||
<el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
|
<el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
|
||||||
@@ -70,21 +64,24 @@
|
|||||||
<el-form-item prop="remark" label="备注:">
|
<el-form-item prop="remark" label="备注:">
|
||||||
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
|
<el-input v-model.trim="form.remark" auto-complete="off" type="textarea"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
|
<el-tab-pane label="其他配置" name="other">
|
||||||
<el-col :span="3">
|
<el-form-item prop="params" label="连接参数:">
|
||||||
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
|
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
|
||||||
:false-label="-1"></el-checkbox>
|
<template #suffix>
|
||||||
</el-col>
|
<el-link target="_blank" href="https://github.com/go-sql-driver/mysql#parameters"
|
||||||
<el-col :span="5" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
|
:underline="false" type="primary" class="mr5">参数参考</el-link>
|
||||||
<el-col :span="16" v-if="form.enableSshTunnel == 1">
|
</template>
|
||||||
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
|
</el-input>
|
||||||
<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-form-item>
|
</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>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -100,11 +97,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toRefs, reactive, watch, ref } from 'vue';
|
import { toRefs, reactive, watch, ref } from 'vue';
|
||||||
import { dbApi } from './api';
|
import { dbApi } from './api';
|
||||||
import { machineApi } from '../machine/api.ts';
|
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { notBlank } from '@/common/assert';
|
import { notBlank } from '@/common/assert';
|
||||||
import { RsaEncrypt } from '@/common/rsa';
|
import { RsaEncrypt } from '@/common/rsa';
|
||||||
import TagSelect from '../component/TagSelect.vue';
|
import TagSelect from '../component/TagSelect.vue';
|
||||||
|
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@@ -170,9 +167,9 @@ const dbForm: any = ref(null);
|
|||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
|
tabActiveName: 'basic',
|
||||||
allDatabases: [] as any,
|
allDatabases: [] as any,
|
||||||
databaseList: [] as any,
|
databaseList: [] as any,
|
||||||
sshTunnelMachineList: [] as any,
|
|
||||||
form: {
|
form: {
|
||||||
id: null,
|
id: null,
|
||||||
tagId: null as any,
|
tagId: null as any,
|
||||||
@@ -185,13 +182,8 @@ const state = reactive({
|
|||||||
password: null,
|
password: null,
|
||||||
params: null,
|
params: null,
|
||||||
database: '',
|
database: '',
|
||||||
project: null,
|
|
||||||
projectId: null,
|
|
||||||
envId: null,
|
|
||||||
env: null,
|
|
||||||
remark: '',
|
remark: '',
|
||||||
enableSshTunnel: null,
|
sshTunnelMachineId: null as any,
|
||||||
sshTunnelMachineId: null,
|
|
||||||
},
|
},
|
||||||
// 原密码
|
// 原密码
|
||||||
pwd: '',
|
pwd: '',
|
||||||
@@ -200,9 +192,9 @@ const state = reactive({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
dialogVisible,
|
dialogVisible,
|
||||||
|
tabActiveName,
|
||||||
allDatabases,
|
allDatabases,
|
||||||
databaseList,
|
databaseList,
|
||||||
sshTunnelMachineList,
|
|
||||||
form,
|
form,
|
||||||
pwd,
|
pwd,
|
||||||
btnLoading,
|
btnLoading,
|
||||||
@@ -213,15 +205,15 @@ watch(props, (newValue: any) => {
|
|||||||
if (!state.dialogVisible) {
|
if (!state.dialogVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
state.tabActiveName = 'basic';
|
||||||
if (newValue.db) {
|
if (newValue.db) {
|
||||||
state.form = { ...newValue.db };
|
state.form = { ...newValue.db };
|
||||||
// 将数据库名使用空格切割,获取所有数据库列表
|
// 将数据库名使用空格切割,获取所有数据库列表
|
||||||
state.databaseList = newValue.db.database.split(' ');
|
state.databaseList = newValue.db.database.split(' ');
|
||||||
} else {
|
} else {
|
||||||
state.form = { port: 3306, enableSshTunnel: -1 } as any;
|
state.form = { port: 3306 } as any;
|
||||||
state.databaseList = [];
|
state.databaseList = [];
|
||||||
}
|
}
|
||||||
getSshTunnelMachines();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,13 +223,6 @@ const changeDatabase = () => {
|
|||||||
state.form.database = state.databaseList.length == 0 ? '' : state.databaseList.join(' ');
|
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 getAllDatabase = async () => {
|
const getAllDatabase = async () => {
|
||||||
const reqForm = { ...state.form };
|
const reqForm = { ...state.form };
|
||||||
reqForm.password = await RsaEncrypt(reqForm.password);
|
reqForm.password = await RsaEncrypt(reqForm.password);
|
||||||
@@ -257,6 +242,9 @@ const btnOk = async () => {
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
const reqForm = { ...state.form };
|
const reqForm = { ...state.form };
|
||||||
reqForm.password = await RsaEncrypt(reqForm.password);
|
reqForm.password = await RsaEncrypt(reqForm.password);
|
||||||
|
if (!state.form.sshTunnelMachineId) {
|
||||||
|
reqForm.sshTunnelMachineId = -1;
|
||||||
|
}
|
||||||
dbApi.saveDb.request(reqForm).then(() => {
|
dbApi.saveDb.request(reqForm).then(() => {
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
emit('val-change', state.form);
|
emit('val-change', state.form);
|
||||||
@@ -287,6 +275,4 @@ const cancel = () => {
|
|||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss"></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
|
<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-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
|
||||||
</el-select>
|
</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>
|
</div>
|
||||||
<el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip stripe>
|
<el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip stripe>
|
||||||
<el-table-column label="选择" width="60px">
|
<el-table-column label="选择" width="60px">
|
||||||
@@ -20,7 +20,14 @@
|
|||||||
</el-radio>
|
</el-radio>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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 prop="name" label="名称" min-width="160" show-overflow-tooltip></el-table-column>
|
||||||
<el-table-column min-width="170" label="host:port" show-overflow-tooltip>
|
<el-table-column min-width="170" label="host:port" show-overflow-tooltip>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
@@ -45,10 +52,9 @@
|
|||||||
</el-input>
|
</el-input>
|
||||||
<div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
|
<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">
|
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"
|
<el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
|
||||||
@click="showTableInfo(scope.row, db)">{{ db }}</el-link>
|
|
||||||
<el-link type="primary" plain size="small" :underline="false"
|
<el-link type="primary" plain size="small" :underline="false"
|
||||||
@click="openSqlExec(scope.row, db)" style="position: absolute; right: 4px">数据操作
|
@click="showTableInfo(scope.row, db)" style="position: absolute; right: 4px">操作
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
@@ -181,6 +187,8 @@
|
|||||||
size="small">DELETE</el-tag>
|
size="small">DELETE</el-tag>
|
||||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['INSERT'].value" color="#A8DEE0"
|
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['INSERT'].value" color="#A8DEE0"
|
||||||
size="small">INSERT</el-tag>
|
size="small">INSERT</el-tag>
|
||||||
|
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['QUERY'].value" color="#A8DEE0"
|
||||||
|
size="small">QUERY</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
|
<el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
|
||||||
@@ -256,7 +264,7 @@
|
|||||||
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</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="数据库">{{ infoDialog.data.database }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.enableSshTunnel == 1 ? '是' : '否' }}
|
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}
|
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}
|
||||||
@@ -278,22 +286,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang='ts' setup>
|
<script lang='ts' setup>
|
||||||
import { toRefs, reactive, computed, onMounted } from 'vue';
|
import { toRefs, reactive, computed, onMounted, defineAsyncComponent } from 'vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { formatByteSize } from '@/common/utils/format';
|
import { formatByteSize } from '@/common/utils/format';
|
||||||
import DbEdit from './DbEdit.vue';
|
|
||||||
import CreateTable from './CreateTable.vue';
|
|
||||||
import { dbApi } from './api';
|
import { dbApi } from './api';
|
||||||
import enums from './enums';
|
import enums from './enums';
|
||||||
import SqlExecBox from './component/SqlExecBox.ts';
|
import SqlExecBox from './component/SqlExecBox';
|
||||||
import config from '@/common/config';
|
import config from '@/common/config';
|
||||||
import { getSession } from '@/common/utils/storage';
|
import { getSession } from '@/common/utils/storage';
|
||||||
import { isTrue } from '@/common/assert';
|
import { isTrue } from '@/common/assert';
|
||||||
import { Search as SearchIcon } from '@element-plus/icons-vue'
|
import { Search as SearchIcon } from '@element-plus/icons-vue'
|
||||||
import router from '@/router';
|
import { tagApi } from '../tag/api';
|
||||||
import { store } from '@/store';
|
|
||||||
import { tagApi } from '../tag/api.ts';
|
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { dateFormat } from '@/common/utils/date';
|
||||||
|
import TagInfo from '../component/TagInfo.vue';
|
||||||
|
|
||||||
|
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
||||||
|
const CreateTable = defineAsyncComponent(() => import('./CreateTable.vue'));
|
||||||
|
|
||||||
const permissions = {
|
const permissions = {
|
||||||
saveDb: 'db:save',
|
saveDb: 'db:save',
|
||||||
@@ -315,7 +323,6 @@ const state = reactive({
|
|||||||
*/
|
*/
|
||||||
query: {
|
query: {
|
||||||
tagPath: null,
|
tagPath: null,
|
||||||
projectId: null,
|
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
},
|
},
|
||||||
@@ -695,20 +702,6 @@ const dropTable = async (row: any) => {
|
|||||||
});
|
});
|
||||||
} catch (err) { }
|
} catch (err) { }
|
||||||
};
|
};
|
||||||
const openSqlExec = (row: any, db: any) => {
|
|
||||||
// 判断db是否发生改变
|
|
||||||
let oldDb = store.state.sqlExecInfo.dbOptInfo.db;
|
|
||||||
if (db && oldDb !== db) {
|
|
||||||
const { tagPath, id } = row;
|
|
||||||
let params = {
|
|
||||||
tagPath,
|
|
||||||
dbId: id,
|
|
||||||
db
|
|
||||||
}
|
|
||||||
store.dispatch('sqlExecInfo/setSqlExecInfo', params);
|
|
||||||
}
|
|
||||||
router.push({ name: 'SqlExec' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击查看时初始化数据
|
// 点击查看时初始化数据
|
||||||
const selectDb = (row: any) => {
|
const selectDb = (row: any) => {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2,27 +2,27 @@ import Api from '@/common/Api';
|
|||||||
|
|
||||||
export const dbApi = {
|
export const dbApi = {
|
||||||
// 获取权限列表
|
// 获取权限列表
|
||||||
dbs: Api.create("/dbs", 'get'),
|
dbs: Api.newGet("/dbs"),
|
||||||
saveDb: Api.create("/dbs", 'post'),
|
saveDb: Api.newPost("/dbs"),
|
||||||
getAllDatabase: Api.create("/dbs/databases", 'post'),
|
getAllDatabase: Api.newPost("/dbs/databases"),
|
||||||
getDbPwd: Api.create("/dbs/{id}/pwd", 'get'),
|
getDbPwd: Api.newGet("/dbs/{id}/pwd"),
|
||||||
deleteDb: Api.create("/dbs/{id}", 'delete'),
|
deleteDb: Api.newDelete("/dbs/{id}"),
|
||||||
dumpDb: Api.create("/dbs/{id}/dump", 'post'),
|
dumpDb: Api.newPost("/dbs/{id}/dump"),
|
||||||
tableInfos: Api.create("/dbs/{id}/t-infos", 'get'),
|
tableInfos: Api.newGet("/dbs/{id}/t-infos"),
|
||||||
tableIndex: Api.create("/dbs/{id}/t-index", 'get'),
|
tableIndex: Api.newGet("/dbs/{id}/t-index"),
|
||||||
tableDdl: Api.create("/dbs/{id}/t-create-ddl", 'get'),
|
tableDdl: Api.newGet("/dbs/{id}/t-create-ddl"),
|
||||||
tableMetadata: Api.create("/dbs/{id}/t-metadata", 'get'),
|
tableMetadata: Api.newGet("/dbs/{id}/t-metadata"),
|
||||||
columnMetadata: Api.create("/dbs/{id}/c-metadata", 'get'),
|
columnMetadata: Api.newGet("/dbs/{id}/c-metadata"),
|
||||||
// 获取表即列提示
|
// 获取表即列提示
|
||||||
hintTables: Api.create("/dbs/{id}/hint-tables", 'get'),
|
hintTables: Api.newGet("/dbs/{id}/hint-tables"),
|
||||||
sqlExec: Api.create("/dbs/{id}/exec-sql", 'post'),
|
sqlExec: Api.newPost("/dbs/{id}/exec-sql"),
|
||||||
// 保存sql
|
// 保存sql
|
||||||
saveSql: Api.create("/dbs/{id}/sql", 'post'),
|
saveSql: Api.newPost("/dbs/{id}/sql"),
|
||||||
// 获取保存的sql
|
// 获取保存的sql
|
||||||
getSql: Api.create("/dbs/{id}/sql", 'get'),
|
getSql: Api.newGet("/dbs/{id}/sql"),
|
||||||
// 获取保存的sql names
|
// 获取保存的sql names
|
||||||
getSqlNames: Api.create("/dbs/{id}/sql-names", 'get'),
|
getSqlNames: Api.newGet("/dbs/{id}/sql-names"),
|
||||||
deleteDbSql: Api.create("/dbs/{id}/sql", 'delete'),
|
deleteDbSql: Api.newDelete("/dbs/{id}/sql"),
|
||||||
// 获取数据库sql执行记录
|
// 获取数据库sql执行记录
|
||||||
getSqlExecs: Api.create("/dbs/{dbId}/sql-execs", 'get'),
|
getSqlExecs: Api.newGet("/dbs/{dbId}/sql-execs"),
|
||||||
}
|
}
|
||||||
323
mayfly_go_web/src/views/ops/db/component/DbTable.vue
Normal file
323
mayfly_go_web/src/views/ops/db/component/DbTable.vue
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-table @cell-dblclick="(row: any, column: any, cell: any, event: any) => cellClick(row, column, cell)"
|
||||||
|
@sort-change="(sort: any) => onTableSortChange(sort)" @selection-change="onDataSelectionChange"
|
||||||
|
:data="datas" size="small" :max-height="tableHeight" v-loading="loading" element-loading-text="查询中..."
|
||||||
|
:empty-text="emptyText" stripe border class="mt5">
|
||||||
|
<el-table-column v-if="datas.length > 0 && table" type="selection" width="35" />
|
||||||
|
<el-table-column min-width="100" :width="DbInst.flexColumnWidth(item, datas)" align="center"
|
||||||
|
v-for="item in columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
|
||||||
|
:sortable="sortable">
|
||||||
|
<template #header v-if="showColumnTip">
|
||||||
|
<el-tooltip raw-content placement="top" effect="customized">
|
||||||
|
<template #content> {{ getColumnTip(item) }} </template>
|
||||||
|
{{ item }}
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, watch, reactive, toRefs } from 'vue';
|
||||||
|
import { DbInst, UpdateFieldsMeta, FieldsMeta } from '../db';
|
||||||
|
|
||||||
|
const emits = defineEmits(['sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dbId: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dbType: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
db: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
columnNames: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
sortable: {
|
||||||
|
type: [String, Boolean],
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
emptyText: {
|
||||||
|
type: String,
|
||||||
|
default: '暂无数据',
|
||||||
|
},
|
||||||
|
showColumnTip: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '600'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
dbId: 0, // 当前选中操作的数据库实例
|
||||||
|
dbType: '',
|
||||||
|
db: '', // 数据库名
|
||||||
|
table: '', // 当前的表名
|
||||||
|
datas: [],
|
||||||
|
columnNames: [],
|
||||||
|
columns: [],
|
||||||
|
sortable: false,
|
||||||
|
loading: false,
|
||||||
|
selectionDatas: [] as any,
|
||||||
|
showColumnTip: false,
|
||||||
|
tableHeight: '600',
|
||||||
|
emptyText: '',
|
||||||
|
updatedFields: [] as UpdateFieldsMeta[],// 各个tab表被修改的字段信息
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
tableHeight,
|
||||||
|
datas,
|
||||||
|
sortable,
|
||||||
|
loading,
|
||||||
|
columnNames,
|
||||||
|
showColumnTip,
|
||||||
|
} = toRefs(state);
|
||||||
|
|
||||||
|
watch(props, (newValue: any) => {
|
||||||
|
setState(newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log('in DbTable mounted');
|
||||||
|
setState(props);
|
||||||
|
})
|
||||||
|
|
||||||
|
const setState = (props: any) => {
|
||||||
|
state.dbId = props.dbId;
|
||||||
|
state.dbType = props.dbType;
|
||||||
|
state.db = props.db;
|
||||||
|
state.table = props.table;
|
||||||
|
state.datas = props.data;
|
||||||
|
state.tableHeight = props.height;
|
||||||
|
state.sortable = props.sortable;
|
||||||
|
state.loading = props.loading;
|
||||||
|
state.columnNames = props.columnNames;
|
||||||
|
state.showColumnTip = props.showColumnTip;
|
||||||
|
state.emptyText = props.emptyText;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getColumnTip = (columnName: string) => {
|
||||||
|
// 优先从 table map中获取
|
||||||
|
let columns = getNowDb().getColumns(state.table);
|
||||||
|
if (!columns) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const column = columns.find((c: any) => c.columnName == columnName);
|
||||||
|
const comment = column.columnComment;
|
||||||
|
return `${column.columnType} ${comment ? ' | ' + comment : ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表排序字段变更
|
||||||
|
*/
|
||||||
|
const onTableSortChange = async (sort: any) => {
|
||||||
|
if (!sort.prop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cancelUpdateFields();
|
||||||
|
emits('sortChange', sort);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDataSelectionChange = (datas: []) => {
|
||||||
|
state.selectionDatas = datas;
|
||||||
|
emits('selectionChange', datas);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听单元格点击事件
|
||||||
|
const cellClick = (row: any, column: any, cell: any) => {
|
||||||
|
const property = column.property;
|
||||||
|
// 如果当前操作的表名不存在 或者 当前列的property不存在(如多选框),则不允许修改当前单元格内容
|
||||||
|
if (!state.table || !property) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let div: HTMLElement = cell.children[0];
|
||||||
|
if (div && div.tagName === 'DIV') {
|
||||||
|
// 转为字符串比较,可能存在数字等
|
||||||
|
let text = (row[property] || row[property] == 0 ? row[property] : '') + '';
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.setAttribute('value', text);
|
||||||
|
// 将表格width也赋值于输入框,避免输入框长度超过表格长度
|
||||||
|
input.setAttribute('style', 'height:23px;text-align:center;border:none;' + div.getAttribute('style'));
|
||||||
|
cell.replaceChildren(input);
|
||||||
|
input.focus();
|
||||||
|
input.addEventListener('blur', async () => {
|
||||||
|
row[property] = input.value;
|
||||||
|
cell.replaceChildren(div);
|
||||||
|
if (input.value !== text) {
|
||||||
|
let currentUpdatedFields = state.updatedFields
|
||||||
|
const dbInst = getNowDbInst();
|
||||||
|
// 主键
|
||||||
|
const primaryKey = await dbInst.loadTableColumn(state.db, state.table);
|
||||||
|
const primaryKeyValue = row[primaryKey.columnName];
|
||||||
|
// 更新字段列信息
|
||||||
|
const updateColumn = await dbInst.loadTableColumn(state.db, state.table, property);
|
||||||
|
const newField = {
|
||||||
|
div, row,
|
||||||
|
fieldName: column.rawColumnKey,
|
||||||
|
fieldType: updateColumn.columnType,
|
||||||
|
oldValue: text,
|
||||||
|
newValue: input.value
|
||||||
|
} as FieldsMeta;
|
||||||
|
|
||||||
|
// 被修改的字段
|
||||||
|
const primaryKeyFields = currentUpdatedFields.filter((meta) => meta.primaryKey === primaryKeyValue)
|
||||||
|
let hasKey = false;
|
||||||
|
if (primaryKeyFields.length <= 0) {
|
||||||
|
primaryKeyFields[0] = {
|
||||||
|
primaryKey: primaryKeyValue,
|
||||||
|
primaryKeyName: primaryKey.columnName,
|
||||||
|
primaryKeyType: primaryKey.columnType,
|
||||||
|
fields: [newField]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasKey = true
|
||||||
|
let hasField = primaryKeyFields[0].fields.some(a => {
|
||||||
|
if (a.fieldName === newField.fieldName) {
|
||||||
|
a.newValue = newField.newValue
|
||||||
|
}
|
||||||
|
return a.fieldName === newField.fieldName
|
||||||
|
})
|
||||||
|
if (!hasField) {
|
||||||
|
primaryKeyFields[0].fields.push(newField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let fields = primaryKeyFields[0].fields
|
||||||
|
|
||||||
|
const fieldsParam = fields.filter((a) => {
|
||||||
|
if (a.fieldName === column.rawColumnKey) {
|
||||||
|
a.newValue = input.value
|
||||||
|
}
|
||||||
|
return a.fieldName === column.rawColumnKey
|
||||||
|
})
|
||||||
|
|
||||||
|
const field = fieldsParam.length > 0 && fieldsParam[0] || {} as FieldsMeta
|
||||||
|
if (field.oldValue === input.value) { // 新值=旧值
|
||||||
|
// 删除数据
|
||||||
|
div.classList.remove('update_field_active')
|
||||||
|
let delIndex: number[] = [];
|
||||||
|
currentUpdatedFields.forEach((a, i) => {
|
||||||
|
if (a.primaryKey === primaryKeyValue) {
|
||||||
|
a.fields = a.fields && a.fields.length > 0 ? a.fields.filter(f => f.fieldName !== column.rawColumnKey) : [];
|
||||||
|
a.fields.length <= 0 && delIndex.push(i)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delIndex.forEach(i => delete currentUpdatedFields[i])
|
||||||
|
currentUpdatedFields = currentUpdatedFields.filter(a => a)
|
||||||
|
} else {
|
||||||
|
// 新增数据
|
||||||
|
div.classList.add('update_field_active')
|
||||||
|
if (hasKey) {
|
||||||
|
currentUpdatedFields.forEach((value, index, array) => {
|
||||||
|
if (value.primaryKey === primaryKeyValue) {
|
||||||
|
array[index].fields = fields
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
currentUpdatedFields.push({
|
||||||
|
primaryKey: primaryKeyValue,
|
||||||
|
primaryKeyName: primaryKey.columnName,
|
||||||
|
primaryKeyType: primaryKey.columnType,
|
||||||
|
fields
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.updatedFields = currentUpdatedFields;
|
||||||
|
changeUpdatedField();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitUpdateFields = () => {
|
||||||
|
let currentUpdatedFields = state.updatedFields;
|
||||||
|
if (currentUpdatedFields.length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const db = state.db;
|
||||||
|
let res = '';
|
||||||
|
let divs: HTMLElement[] = [];
|
||||||
|
currentUpdatedFields.forEach(a => {
|
||||||
|
let sql = `UPDATE ${state.table} SET `;
|
||||||
|
let primaryKey = a.primaryKey;
|
||||||
|
let primaryKeyType = a.primaryKeyType;
|
||||||
|
let primaryKeyName = a.primaryKeyName;
|
||||||
|
a.fields.forEach(f => {
|
||||||
|
sql += ` ${f.fieldName} = ${DbInst.wrapColumnValue(f.fieldType, f.newValue)},`
|
||||||
|
divs.push(f.div)
|
||||||
|
})
|
||||||
|
sql = sql.substring(0, sql.length - 1)
|
||||||
|
sql += ` WHERE ${primaryKeyName} = ${DbInst.wrapColumnValue(primaryKeyType, primaryKey)} ;`
|
||||||
|
res += sql;
|
||||||
|
})
|
||||||
|
|
||||||
|
DbInst.getInst(state.dbId).promptExeSql(db, res, () => { }, () => {
|
||||||
|
currentUpdatedFields = [];
|
||||||
|
divs.forEach(a => {
|
||||||
|
a.classList.remove('update_field_active');
|
||||||
|
})
|
||||||
|
state.updatedFields = [];
|
||||||
|
changeUpdatedField();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelUpdateFields = () => {
|
||||||
|
state.updatedFields.forEach((a: any) => {
|
||||||
|
a.fields.forEach((b: any) => {
|
||||||
|
b.div.classList.remove('update_field_active')
|
||||||
|
b.row[b.fieldName] = b.oldValue
|
||||||
|
})
|
||||||
|
})
|
||||||
|
state.updatedFields = [];
|
||||||
|
changeUpdatedField();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const changeUpdatedField = () => {
|
||||||
|
emits('changeUpdatedField', state.updatedFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNowDb = () => {
|
||||||
|
return DbInst.getInst(state.dbId).getDb(state.db);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNowDbInst = () => {
|
||||||
|
return DbInst.getInst(state.dbId);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
submitUpdateFields,
|
||||||
|
cancelUpdateFields
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.update_field_active {
|
||||||
|
background-color: var(--el-color-success)
|
||||||
|
}
|
||||||
|
</style>
|
||||||
573
mayfly_go_web/src/views/ops/db/component/tab/Query.vue
Normal file
573
mayfly_go_web/src/views/ops/db/component/tab/Query.vue
Normal file
@@ -0,0 +1,573 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div class="toolbar">
|
||||||
|
<div class="fl">
|
||||||
|
<el-link @click="onRunSql()" :underline="false" class="ml15" icon="VideoPlay">
|
||||||
|
</el-link>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="format sql" placement="top">
|
||||||
|
<el-link @click="formatSql()" type="primary" :underline="false" icon="MagicStick">
|
||||||
|
</el-link>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="commit" placement="top">
|
||||||
|
<el-link @click="onCommit()" type="success" :underline="false" icon="CircleCheck">
|
||||||
|
</el-link>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-upload class="sql-file-exec" :before-upload="beforeUpload" :on-success="execSqlFileSuccess"
|
||||||
|
:headers="{ Authorization: token }" :action="getUploadSqlFileUrl()" :show-file-list="false"
|
||||||
|
name="file" multiple :limit="100">
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="SQL脚本执行" placement="top">
|
||||||
|
<el-link type="success" :underline="false" icon="Document"></el-link>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-upload>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="float: right" class="fl">
|
||||||
|
<el-button @click="saveSql()" type="primary" icon="document-add" plain size="small">保存SQL
|
||||||
|
</el-button>
|
||||||
|
<el-button v-if="sqlName" @click="deleteSql()" type="danger" icon="delete" plain size="small">删除SQL
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt5 sqlEditor">
|
||||||
|
<div :id="'MonacoTextarea-' + ti.key" :style="{ height: editorHeight }">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-move-resize" @mousedown="onDragSetHeight">
|
||||||
|
<el-icon>
|
||||||
|
<Minus />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt5">
|
||||||
|
<el-row>
|
||||||
|
<el-link v-if="table" @click="onDeleteData()" class="ml5" type="danger" icon="delete"
|
||||||
|
:underline="false"></el-link>
|
||||||
|
|
||||||
|
<span v-if="execRes.data.length > 0">
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
<el-link type="success" :underline="false" @click="exportData"><span
|
||||||
|
style="font-size: 12px">导出</span></el-link>
|
||||||
|
</span>
|
||||||
|
<span v-if="hasUpdatedFileds">
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
<el-link type="success" :underline="false" @click="submitUpdateFields()"><span
|
||||||
|
style="font-size: 12px">提交</span></el-link>
|
||||||
|
</span>
|
||||||
|
<span v-if="hasUpdatedFileds">
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
<el-link type="warning" :underline="false" @click="cancelUpdateFields"><span
|
||||||
|
style="font-size: 12px">取消</span></el-link>
|
||||||
|
</span>
|
||||||
|
</el-row>
|
||||||
|
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="execRes.data" :table="state.table"
|
||||||
|
:column-names="execRes.tableColumn" :loading="loading" :height="tableDataHeight"
|
||||||
|
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" @selection-change="onDataSelectionChange"
|
||||||
|
@change-updated-field="changeUpdatedField"></db-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { nextTick, watch, onMounted, reactive, toRefs, ref, Ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
|
import { getSession } from '@/common/utils/storage';
|
||||||
|
import { isTrue, notBlank } from '@/common/assert';
|
||||||
|
import { format as sqlFormatter } from 'sql-formatter';
|
||||||
|
import config from '@/common/config';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
import { editor } from 'monaco-editor';
|
||||||
|
|
||||||
|
// 主题仓库 https://github.com/brijeshb42/monaco-themes
|
||||||
|
// 主题例子 https://editor.bitwiser.in/
|
||||||
|
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
|
||||||
|
import DbTable from '../DbTable.vue'
|
||||||
|
import { TabInfo } from '../../db';
|
||||||
|
import { exportCsv } from '@/common/utils/export';
|
||||||
|
import { dateStrFormat } from '@/common/utils/date';
|
||||||
|
import { dbApi } from '../../api';
|
||||||
|
|
||||||
|
const emits = defineEmits(['saveSqlSuccess', 'deleteSqlSuccess'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: TabInfo,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// sql脚本名,若有则去加载该sql内容
|
||||||
|
sqlName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
editorHeight: {
|
||||||
|
type: String,
|
||||||
|
default: '600'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
|
const token = getSession('token');
|
||||||
|
let monacoEditor = {} as editor.IStandaloneCodeEditor;
|
||||||
|
const dbTableRef = ref(null) as Ref;
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
token,
|
||||||
|
ti: {} as TabInfo,
|
||||||
|
dbs: [],
|
||||||
|
dbId: null, // 当前选中操作的数据库实例
|
||||||
|
table: '', // 当前单表操作sql的表信息
|
||||||
|
sqlName: '',
|
||||||
|
sql: '', // 当前编辑器的sql内容
|
||||||
|
loading: false, // 是否在加载数据
|
||||||
|
execRes: {
|
||||||
|
data: [],
|
||||||
|
tableColumn: []
|
||||||
|
},
|
||||||
|
selectionDatas: [] as any,
|
||||||
|
editorHeight: '500',
|
||||||
|
tableDataHeight: 250 as any,
|
||||||
|
hasUpdatedFileds: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
tableDataHeight,
|
||||||
|
editorHeight,
|
||||||
|
ti,
|
||||||
|
execRes,
|
||||||
|
table,
|
||||||
|
sqlName,
|
||||||
|
loading,
|
||||||
|
hasUpdatedFileds,
|
||||||
|
} = toRefs(state);
|
||||||
|
|
||||||
|
watch(() => props.editorHeight, (newValue: any) => {
|
||||||
|
state.editorHeight = newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log('in query mounted');
|
||||||
|
state.ti = props.data;
|
||||||
|
state.editorHeight = props.editorHeight;
|
||||||
|
const params = state.ti.params;
|
||||||
|
state.dbs = params && params.dbs;
|
||||||
|
|
||||||
|
if (params && params.sqlName) {
|
||||||
|
state.sqlName = params.sqlName;
|
||||||
|
const res = await dbApi.getSql.request({ id: state.ti.dbId, type: 1, name: state.sqlName, db: state.ti.db });
|
||||||
|
state.sql = res.sql;
|
||||||
|
}
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => initMonacoEditor(), 50)
|
||||||
|
})
|
||||||
|
await state.ti.getNowDbInst().loadDbHints(state.ti.db);
|
||||||
|
})
|
||||||
|
|
||||||
|
self.MonacoEnvironment = {
|
||||||
|
getWorker() {
|
||||||
|
return new EditorWorker();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initMonacoEditor = () => {
|
||||||
|
let monacoTextarea = document.getElementById('MonacoTextarea-' + state.ti.key) as HTMLElement
|
||||||
|
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
|
||||||
|
// 初始化一些主题
|
||||||
|
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
|
||||||
|
monacoEditor = monaco.editor.create(monacoTextarea, {
|
||||||
|
language: 'sql',
|
||||||
|
theme: themeConfig.value.editorTheme,
|
||||||
|
automaticLayout: true, //自适应宽高布局
|
||||||
|
folding: false,
|
||||||
|
roundedSelection: false, // 禁用选择文本背景的圆角
|
||||||
|
matchBrackets: 'near',
|
||||||
|
linkedEditing: true,
|
||||||
|
cursorBlinking: 'smooth',// 光标闪烁样式
|
||||||
|
mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
|
||||||
|
overviewRulerBorder: false, // 不要滚动条的边框
|
||||||
|
tabSize: 2, // tab 缩进长度
|
||||||
|
// fontFamily: 'JetBrainsMono', // 字体 暂时不要设置,否则光标容易错位
|
||||||
|
fontWeight: 'bold',
|
||||||
|
// letterSpacing: 1, 字符间距
|
||||||
|
// quickSuggestions:false, // 禁用代码提示
|
||||||
|
minimap: {
|
||||||
|
enabled: false, // 不要小地图
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注册快捷键:ctrl + R 运行选中的sql
|
||||||
|
monacoEditor.addAction({
|
||||||
|
// An unique identifier of the contributed action.
|
||||||
|
id: 'run-sql-action' + state.ti.key,
|
||||||
|
// A label of the action that will be presented to the user.
|
||||||
|
label: '执行SQL',
|
||||||
|
// A precondition for this action.
|
||||||
|
precondition: undefined,
|
||||||
|
// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
|
||||||
|
keybindingContext: undefined,
|
||||||
|
keybindings: [
|
||||||
|
// chord
|
||||||
|
monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, 0)
|
||||||
|
],
|
||||||
|
contextMenuGroupId: 'navigation',
|
||||||
|
contextMenuOrder: 1.5,
|
||||||
|
// Method that will be executed when the action is triggered.
|
||||||
|
// @param editor The editor instance is passed in as a convenience
|
||||||
|
run: async function () {
|
||||||
|
try {
|
||||||
|
await onRunSql();
|
||||||
|
} catch (e: any) {
|
||||||
|
e.message && ElMessage.error(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注册快捷键:ctrl + shift + f 格式化sql
|
||||||
|
monacoEditor.addAction({
|
||||||
|
// An unique identifier of the contributed action.
|
||||||
|
id: 'format-sql-action' + state.ti.key,
|
||||||
|
// A label of the action that will be presented to the user.
|
||||||
|
label: '格式化SQL',
|
||||||
|
// A precondition for this action.
|
||||||
|
precondition: undefined,
|
||||||
|
// A rule to evaluate on top of the precondition in order to dispatch the keybindings.
|
||||||
|
keybindingContext: undefined,
|
||||||
|
keybindings: [
|
||||||
|
// chord
|
||||||
|
monaco.KeyMod.chord(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyF, 0)
|
||||||
|
],
|
||||||
|
contextMenuGroupId: 'navigation',
|
||||||
|
contextMenuOrder: 2,
|
||||||
|
// Method that will be executed when the action is triggered.
|
||||||
|
// @param editor The editor instance is passed in as a convenience
|
||||||
|
run: async function () {
|
||||||
|
try {
|
||||||
|
await formatSql();
|
||||||
|
} catch (e: any) {
|
||||||
|
e.message && ElMessage.error(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 动态设置主题
|
||||||
|
// monaco.editor.setTheme('hc-black');
|
||||||
|
|
||||||
|
// 如果sql有值,则默认赋值
|
||||||
|
if (state.sql) {
|
||||||
|
monacoEditor.getModel()?.setValue(state.sql);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拖拽改变sql编辑区和查询结果区高度
|
||||||
|
*/
|
||||||
|
const onDragSetHeight = () => {
|
||||||
|
document.onmousemove = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
//得到鼠标拖动的宽高距离:取绝对值
|
||||||
|
state.editorHeight = `${document.getElementById('MonacoTextarea-' + state.ti.key)!.offsetHeight + e.movementY}px`
|
||||||
|
state.tableDataHeight -= e.movementY
|
||||||
|
}
|
||||||
|
document.onmouseup = () => {
|
||||||
|
document.onmousemove = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行sql
|
||||||
|
*/
|
||||||
|
const onRunSql = async () => {
|
||||||
|
// 没有选中的文本,则为全部文本
|
||||||
|
let sql = getSql() as string;
|
||||||
|
notBlank(sql && sql.trim(), '请选中需要执行的sql');
|
||||||
|
// 去除字符串前的空格、换行等
|
||||||
|
sql = sql.replace(/(^\s*)/g, '');
|
||||||
|
let execRemark = '';
|
||||||
|
let canRun = true;
|
||||||
|
if (
|
||||||
|
sql.startsWith('update') ||
|
||||||
|
sql.startsWith('UPDATE') ||
|
||||||
|
sql.startsWith('INSERT') ||
|
||||||
|
sql.startsWith('insert') ||
|
||||||
|
sql.startsWith('DELETE') ||
|
||||||
|
sql.startsWith('delete')
|
||||||
|
) {
|
||||||
|
const res: any = await ElMessageBox.prompt('请输入备注', 'Tip', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
inputPattern: /^[\s\S]*.*[^\s][\s\S]*$/,
|
||||||
|
inputErrorMessage: '请输入执行该sql的备注信息',
|
||||||
|
});
|
||||||
|
execRemark = res.value;
|
||||||
|
if (!execRemark) {
|
||||||
|
canRun = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!canRun) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
state.loading = true;
|
||||||
|
|
||||||
|
const colAndData: any = await state.ti.getNowDbInst().runSql(state.ti.db, sql, execRemark);
|
||||||
|
if (!colAndData.res || colAndData.res.length === 0) {
|
||||||
|
ElMessage.warning('未查询到结果集')
|
||||||
|
}
|
||||||
|
state.execRes.data = colAndData.res;
|
||||||
|
state.execRes.tableColumn = colAndData.colNames;
|
||||||
|
cancelUpdateFields()
|
||||||
|
} catch (e: any) {
|
||||||
|
state.execRes.data = [];
|
||||||
|
state.execRes.tableColumn = [];
|
||||||
|
state.table = '';
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
state.loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 即只有以该字符串开头的sql才可修改表数据内容
|
||||||
|
if (sql.startsWith('SELECT *') || sql.startsWith('select *') || sql.startsWith('SELECT\n *')) {
|
||||||
|
state.selectionDatas = [];
|
||||||
|
const tableName = sql.split(/from/i)[1];
|
||||||
|
if (tableName) {
|
||||||
|
const tn = tableName.trim().split(' ')[0].split('\n')[0];
|
||||||
|
state.table = tn;
|
||||||
|
state.table = tn;
|
||||||
|
} else {
|
||||||
|
state.table = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.table = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取sql,如果有鼠标选中,则返回选中内容,否则返回输入框内所有内容
|
||||||
|
*/
|
||||||
|
const getSql = () => {
|
||||||
|
let res = '' as string | undefined;
|
||||||
|
// 编辑器还没初始化
|
||||||
|
if (!monacoEditor?.getModel) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
// 选择选中的sql
|
||||||
|
let selection = monacoEditor.getSelection()
|
||||||
|
if (selection) {
|
||||||
|
res = monacoEditor.getModel()?.getValueInRange(selection)
|
||||||
|
}
|
||||||
|
// 整个编辑器的sql
|
||||||
|
if (!res) {
|
||||||
|
return monacoEditor.getModel()?.getValue()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveSql = async () => {
|
||||||
|
const sql = monacoEditor.getModel()?.getValue();
|
||||||
|
notBlank(sql, 'sql内容不能为空');
|
||||||
|
|
||||||
|
let sqlName = state.sqlName;
|
||||||
|
const newSql = !sqlName;
|
||||||
|
if (newSql) {
|
||||||
|
try {
|
||||||
|
const input = await ElMessageBox.prompt('请输入SQL脚本名', 'SQL名', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
inputPattern:
|
||||||
|
/\w+/,
|
||||||
|
inputErrorMessage: '请输入SQL脚本名',
|
||||||
|
});
|
||||||
|
sqlName = input.value;
|
||||||
|
state.sqlName = sqlName;
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbApi.saveSql.request({ id: state.ti.dbId, db: state.ti.db, sql: sql, type: 1, name: sqlName });
|
||||||
|
ElMessage.success('保存成功');
|
||||||
|
// 保存sql脚本成功事件
|
||||||
|
emits('saveSqlSuccess', state.ti.dbId, state.ti.db);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSql = async () => {
|
||||||
|
const sqlName = state.sqlName;
|
||||||
|
notBlank(sqlName, "该sql内容未保存");
|
||||||
|
const { dbId, db } = state.ti;
|
||||||
|
try {
|
||||||
|
await ElMessageBox.confirm(`确定删除【${sqlName}】该SQL内容?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
await dbApi.deleteDbSql.request({ id: dbId, db: db, name: sqlName });
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
emits('deleteSqlSuccess', dbId, db);
|
||||||
|
} catch (err) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化sql
|
||||||
|
*/
|
||||||
|
const formatSql = () => {
|
||||||
|
let selection = monacoEditor.getSelection()
|
||||||
|
if (!selection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let sql = monacoEditor.getModel()?.getValueInRange(selection)
|
||||||
|
// 有选中sql则格式化并替换选中sql, 否则格式化编辑器所有内容
|
||||||
|
if (sql) {
|
||||||
|
replaceSelection(sqlFormatter(sql), selection)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
monacoEditor.getModel()?.setValue(sqlFormatter(monacoEditor.getValue()));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交事务,用于没有开启自动提交事务
|
||||||
|
*/
|
||||||
|
const onCommit = () => {
|
||||||
|
state.ti.getNowDbInst().runSql(state.ti.db, 'COMMIT;');
|
||||||
|
ElMessage.success('COMMIT success');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换选中的内容
|
||||||
|
*/
|
||||||
|
const replaceSelection = (str: string, selection: any) => {
|
||||||
|
const model = monacoEditor.getModel();
|
||||||
|
if (!model) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!selection) {
|
||||||
|
model.setValue(str);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { startLineNumber, endLineNumber, startColumn, endColumn } = selection
|
||||||
|
|
||||||
|
const textBeforeSelection = model.getValueInRange({
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 0,
|
||||||
|
endLineNumber: startLineNumber,
|
||||||
|
endColumn: startColumn,
|
||||||
|
})
|
||||||
|
|
||||||
|
const textAfterSelection = model.getValueInRange({
|
||||||
|
startLineNumber: endLineNumber,
|
||||||
|
startColumn: endColumn,
|
||||||
|
endLineNumber: model.getLineCount(),
|
||||||
|
endColumn: model.getLineMaxColumn(model.getLineCount()),
|
||||||
|
})
|
||||||
|
|
||||||
|
monacoEditor.setValue(textBeforeSelection + str + textAfterSelection)
|
||||||
|
monacoEditor.focus()
|
||||||
|
monacoEditor.setPosition({
|
||||||
|
lineNumber: startLineNumber,
|
||||||
|
column: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出当前页数据
|
||||||
|
*/
|
||||||
|
const exportData = () => {
|
||||||
|
const dataList = state.execRes.data as any;
|
||||||
|
isTrue(dataList.length > 0, '没有数据可导出');
|
||||||
|
exportCsv(`数据查询导出-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.execRes.tableColumn, dataList)
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload = (file: File) => {
|
||||||
|
ElMessage.success(`'${file.name}' 正在上传执行, 请关注结果通知`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行sql成功
|
||||||
|
const execSqlFileSuccess = (res: any) => {
|
||||||
|
if (res.code !== 200) {
|
||||||
|
ElMessage.error(res.msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取sql文件上传执行url
|
||||||
|
const getUploadSqlFileUrl = () => {
|
||||||
|
return `${config.baseApiUrl}/dbs/${state.ti.dbId}/exec-sql-file?db=${state.ti.db}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const onDataSelectionChange = (datas: []) => {
|
||||||
|
state.selectionDatas = datas;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeUpdatedField = (updatedFields: []) => {
|
||||||
|
// 如果存在要更新字段,则显示提交和取消按钮
|
||||||
|
state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行删除数据事件
|
||||||
|
*/
|
||||||
|
const onDeleteData = async () => {
|
||||||
|
const deleteDatas = state.selectionDatas;
|
||||||
|
isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
|
||||||
|
const { db } = state.ti;
|
||||||
|
const dbInst = state.ti.getNowDbInst()
|
||||||
|
const primaryKey = await dbInst.loadTableColumn(db, state.table);
|
||||||
|
const primaryKeyColumnName = primaryKey.columnName;
|
||||||
|
dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
|
||||||
|
state.execRes.data = state.execRes.data.filter(
|
||||||
|
(d: any) => !(deleteDatas.findIndex((x: any) => x[primaryKeyColumnName] == d[primaryKeyColumnName]) != -1)
|
||||||
|
);
|
||||||
|
state.selectionDatas = [];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitUpdateFields = () => {
|
||||||
|
dbTableRef.value.submitUpdateFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelUpdateFields = () => {
|
||||||
|
dbTableRef.value.cancelUpdateFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sql-file-exec {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sqlEditor {
|
||||||
|
font-size: 8pt;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.update_field_active {
|
||||||
|
background-color: var(--el-color-success)
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-move-resize {
|
||||||
|
cursor: n-resize;
|
||||||
|
height: 3px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
399
mayfly_go_web/src/views/ops/db/component/tab/TableData.vue
Normal file
399
mayfly_go_web/src/views/ops/db/component/tab/TableData.vue
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-link @click="onRefresh()" icon="refresh" :underline="false" class="ml5">
|
||||||
|
</el-link>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-link @click="onShowAddDataDialog()" type="primary" icon="plus" :underline="false"></el-link>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-link @click="onDeleteData()" type="danger" icon="delete" :underline="false"></el-link>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="commit" placement="top">
|
||||||
|
<el-link @click="onCommit()" type="success" icon="CircleCheck" :underline="false">
|
||||||
|
</el-link>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="生成insert sql" placement="top">
|
||||||
|
<el-link @click="onGenerateInsertSql()" type="success" :underline="false">gi</el-link>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-tooltip class="box-item" effect="dark" content="导出当前页的csv文件" placement="top">
|
||||||
|
<el-link type="success" :underline="false" @click="exportData"><span class="f12">导出</span></el-link>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
|
<el-tooltip v-if="hasUpdatedFileds" class="box-item" effect="dark" content="提交修改" placement="top">
|
||||||
|
<el-link @click="submitUpdateFields()" type="success" :underline="false" class="f12">提交</el-link>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider v-if="hasUpdatedFileds" direction="vertical" border-style="dashed" />
|
||||||
|
<el-tooltip v-if="hasUpdatedFileds" class="box-item" effect="dark" content="取消修改" placement="top">
|
||||||
|
<el-link @click="cancelUpdateFields" type="warning" :underline="false" class="f12">取消</el-link>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="16">
|
||||||
|
<el-input v-model="condition" placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可" clearable
|
||||||
|
@clear="selectData" size="small" style="width: 100%">
|
||||||
|
<template #prepend>
|
||||||
|
<el-popover trigger="click" :width="320" placement="right">
|
||||||
|
<template #reference>
|
||||||
|
<el-link type="success" :underline="false">选择列</el-link>
|
||||||
|
</template>
|
||||||
|
<el-table :data="columns" max-height="500" size="small" @row-click="
|
||||||
|
(...event: any) => {
|
||||||
|
onConditionRowClick(event);
|
||||||
|
}
|
||||||
|
" style="cursor: pointer">
|
||||||
|
<el-table-column property="columnName" label="列名" show-overflow-tooltip>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column property="columnComment" label="备注" show-overflow-tooltip>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-popover>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onSelectByCondition()" icon="search" size="small"></el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="datas" :table="state.table"
|
||||||
|
:column-names="columnNames" :loading="loading" :height="tableHeight" :show-column-tip="true"
|
||||||
|
:sortable="'custom'" @sort-change="(sort: any) => onTableSortChange(sort)"
|
||||||
|
@selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
|
||||||
|
|
||||||
|
<el-row type="flex" class="mt5" justify="center">
|
||||||
|
<el-pagination small :total="count" @current-change="pageChange()" layout="prev, pager, next, total, jumper"
|
||||||
|
v-model:current-page="pageNum" :page-size="DbInst.DefaultLimit"></el-pagination>
|
||||||
|
</el-row>
|
||||||
|
<div style=" font-size: 12px; padding: 0 10px; color: #606266"><span>{{ state.sql }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog v-model="conditionDialog.visible" :title="conditionDialog.title" width="420px">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="5">
|
||||||
|
<el-select v-model="conditionDialog.condition">
|
||||||
|
<el-option label="=" value="="> </el-option>
|
||||||
|
<el-option label="LIKE" value="LIKE"> </el-option>
|
||||||
|
<el-option label=">" value=">"> </el-option>
|
||||||
|
<el-option label=">=" value=">="> </el-option>
|
||||||
|
<el-option label="<" value="<"> </el-option>
|
||||||
|
<el-option label="<=" value="<="> </el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="19">
|
||||||
|
<el-input v-model="conditionDialog.value" :placeholder="conditionDialog.placeholder" />
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="onCancelCondition">取消</el-button>
|
||||||
|
<el-button type="primary" @click="onConfirmCondition">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog v-model="addDataDialog.visible" :title="addDataDialog.title" :destroy-on-close="true" width="600px">
|
||||||
|
<el-form ref="dataForm" :model="addDataDialog.data" label-width="160px">
|
||||||
|
<el-form-item v-for="column in columns" class="w100" :prop="column.columnName" :label="column.columnName"
|
||||||
|
:required="column.nullable != 'YES' && column.columnKey != 'PRI'">
|
||||||
|
<el-input-number v-if="DbInst.isNumber(column.columnType)"
|
||||||
|
v-model="addDataDialog.data[`${column.columnName}`]"
|
||||||
|
:placeholder="`${column.columnType} ${column.columnComment}`" class="w100" />
|
||||||
|
|
||||||
|
<el-input v-else v-model="addDataDialog.data[`${column.columnName}`]"
|
||||||
|
:placeholder="`${column.columnType} ${column.columnComment}`" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<span class="dialog-footer">
|
||||||
|
<el-button @click="closeAddDataDialog">取消</el-button>
|
||||||
|
<el-button type="primary" @click="addRow">确定</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, watch, reactive, toRefs, ref, Ref } from 'vue';
|
||||||
|
import { isTrue, notEmpty, notBlank } from '@/common/assert';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import { DbInst, TabInfo } from '../../db';
|
||||||
|
import { exportCsv } from '@/common/utils/export';
|
||||||
|
import { dateStrFormat } from '@/common/utils/date';
|
||||||
|
import DbTable from '../DbTable.vue'
|
||||||
|
|
||||||
|
const emits = defineEmits(['genInsertSql'])
|
||||||
|
const dataForm: any = ref(null);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
data: {
|
||||||
|
type: TabInfo,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
tableHeight: {
|
||||||
|
type: [String],
|
||||||
|
default: '600'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const dbTableRef = ref(null) as Ref;
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
ti: {} as TabInfo,
|
||||||
|
table: '', // 当前的表名
|
||||||
|
datas: [],
|
||||||
|
sql: '', // 当前数据tab执行的sql
|
||||||
|
orderBy: '',
|
||||||
|
condition: '', // 当前条件框的条件
|
||||||
|
loading: false, // 是否在加载数据
|
||||||
|
columnNames: [],
|
||||||
|
columns: [] as any,
|
||||||
|
pageNum: 1,
|
||||||
|
count: 0,
|
||||||
|
selectionDatas: [] as any,
|
||||||
|
conditionDialog: {
|
||||||
|
title: '',
|
||||||
|
placeholder: '',
|
||||||
|
columnRow: null,
|
||||||
|
dataTab: null,
|
||||||
|
visible: false,
|
||||||
|
condition: '=',
|
||||||
|
value: null
|
||||||
|
},
|
||||||
|
addDataDialog: {
|
||||||
|
data: {},
|
||||||
|
title: '',
|
||||||
|
placeholder: '',
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
tableHeight: '600',
|
||||||
|
hasUpdatedFileds: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
datas,
|
||||||
|
condition,
|
||||||
|
loading,
|
||||||
|
columns,
|
||||||
|
columnNames,
|
||||||
|
pageNum,
|
||||||
|
count,
|
||||||
|
hasUpdatedFileds,
|
||||||
|
conditionDialog,
|
||||||
|
addDataDialog,
|
||||||
|
} = toRefs(state);
|
||||||
|
|
||||||
|
watch(() => props.tableHeight, (newValue: any) => {
|
||||||
|
state.tableHeight = newValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log('in table data mounted');
|
||||||
|
state.ti = props.data;
|
||||||
|
state.tableHeight = props.tableHeight;
|
||||||
|
state.table = state.ti.params.table;
|
||||||
|
notBlank(state.table, "TableData组件params.table信息不能为空")
|
||||||
|
|
||||||
|
const columns = await state.ti.getNowDbInst().loadColumns(state.ti.db, state.table);
|
||||||
|
state.columns = columns;
|
||||||
|
state.columnNames = columns.map((t: any) => t.columnName);
|
||||||
|
await onRefresh();
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
// 查询条件置空
|
||||||
|
state.condition = '';
|
||||||
|
state.pageNum = 1;
|
||||||
|
await selectData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据tab修改页数
|
||||||
|
*/
|
||||||
|
const pageChange = async () => {
|
||||||
|
await selectData();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单表数据信息查询数据
|
||||||
|
*/
|
||||||
|
const selectData = async () => {
|
||||||
|
state.loading = true;
|
||||||
|
const dbInst = state.ti.getNowDbInst();
|
||||||
|
const { db } = state.ti;
|
||||||
|
try {
|
||||||
|
const countRes = await dbInst.runSql(db, DbInst.getDefaultCountSql(state.table, state.condition));
|
||||||
|
state.count = countRes.res[0].count;
|
||||||
|
let sql = dbInst.getDefaultSelectSql(state.table, state.condition, state.orderBy, state.pageNum);
|
||||||
|
state.sql = sql;
|
||||||
|
if (state.count > 0) {
|
||||||
|
const colAndData: any = await dbInst.runSql(db, sql);
|
||||||
|
state.datas = colAndData.res;
|
||||||
|
} else {
|
||||||
|
state.datas = [];
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
state.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出当前页数据
|
||||||
|
*/
|
||||||
|
const exportData = () => {
|
||||||
|
const dataList = state.datas as any;
|
||||||
|
isTrue(dataList.length > 0, '没有数据可导出');
|
||||||
|
exportCsv(`数据导出-${state.table}-${dateStrFormat('yyyyMMddHHmm', new Date().toString())}`, state.columnNames, dataList)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件查询,点击列信息后显示输入对应的值
|
||||||
|
*/
|
||||||
|
const onConditionRowClick = (event: any) => {
|
||||||
|
const row = event[0];
|
||||||
|
state.conditionDialog.title = `请输入 [${row.columnName}] 的值`;
|
||||||
|
state.conditionDialog.placeholder = `${row.columnType} ${row.columnComment}`;
|
||||||
|
state.conditionDialog.columnRow = row;
|
||||||
|
state.conditionDialog.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认条件
|
||||||
|
const onConfirmCondition = () => {
|
||||||
|
const conditionDialog = state.conditionDialog;
|
||||||
|
let condition = state.condition;
|
||||||
|
if (condition) {
|
||||||
|
condition += ` AND `;
|
||||||
|
}
|
||||||
|
const row = conditionDialog.columnRow as any;
|
||||||
|
condition += `${row.columnName} ${conditionDialog.condition} `;
|
||||||
|
state.condition = condition + DbInst.wrapColumnValue(row.columnType, conditionDialog.value);
|
||||||
|
onCancelCondition();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancelCondition = () => {
|
||||||
|
state.conditionDialog.visible = false;
|
||||||
|
state.conditionDialog.title = ``;
|
||||||
|
state.conditionDialog.placeholder = ``;
|
||||||
|
state.conditionDialog.value = null;
|
||||||
|
state.conditionDialog.columnRow = null;
|
||||||
|
state.conditionDialog.dataTab = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交事务,用于没有开启自动提交事务
|
||||||
|
*/
|
||||||
|
const onCommit = () => {
|
||||||
|
state.ti.getNowDbInst().runSql(state.ti.db, 'COMMIT;');
|
||||||
|
ElMessage.success('COMMIT success');
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectByCondition = async () => {
|
||||||
|
notEmpty(state.condition, '条件不能为空');
|
||||||
|
state.pageNum = 1;
|
||||||
|
await selectData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表排序字段变更
|
||||||
|
*/
|
||||||
|
const onTableSortChange = async (sort: any) => {
|
||||||
|
if (!sort.prop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sortType = sort.order == 'descending' ? 'DESC' : 'ASC';
|
||||||
|
state.orderBy = `ORDER BY ${sort.prop} ${sortType}`;
|
||||||
|
await onRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDataSelectionChange = (datas: []) => {
|
||||||
|
state.selectionDatas = datas;
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeUpdatedField = (updatedFields: []) => {
|
||||||
|
// 如果存在要更新字段,则显示提交和取消按钮
|
||||||
|
state.hasUpdatedFileds = updatedFields && updatedFields.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行删除数据事件
|
||||||
|
*/
|
||||||
|
const onDeleteData = async () => {
|
||||||
|
const deleteDatas = state.selectionDatas;
|
||||||
|
isTrue(deleteDatas && deleteDatas.length > 0, '请先选择要删除的数据');
|
||||||
|
const { db } = state.ti;
|
||||||
|
const dbInst = state.ti.getNowDbInst()
|
||||||
|
dbInst.promptExeSql(db, dbInst.genDeleteByPrimaryKeysSql(db, state.table, deleteDatas), null, () => {
|
||||||
|
onRefresh();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onGenerateInsertSql = async () => {
|
||||||
|
isTrue(state.selectionDatas && state.selectionDatas.length > 0, '请先选择数据');
|
||||||
|
emits('genInsertSql', state.ti.getNowDbInst().genInsertSql(state.ti.db, state.table, state.selectionDatas));
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitUpdateFields = () => {
|
||||||
|
dbTableRef.value.submitUpdateFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelUpdateFields = () => {
|
||||||
|
dbTableRef.value.cancelUpdateFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onShowAddDataDialog = async () => {
|
||||||
|
state.addDataDialog.title = `添加'${state.table}'表数据`
|
||||||
|
state.addDataDialog.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAddDataDialog = () => {
|
||||||
|
state.addDataDialog.visible = false;
|
||||||
|
state.addDataDialog.data = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新数据行
|
||||||
|
const addRow = async () => {
|
||||||
|
dataForm.value.validate(async (valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
const data = state.addDataDialog.data;
|
||||||
|
// key: 字段名,value: 字段名提示
|
||||||
|
let obj: any = {};
|
||||||
|
for (let item of state.columns) {
|
||||||
|
const value = data[item.columnName]
|
||||||
|
if (!value) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
obj[`${item.columnName}`] = DbInst.wrapValueByType(value);
|
||||||
|
}
|
||||||
|
let columnNames = Object.keys(obj).join(',');
|
||||||
|
let values = Object.values(obj).join(',');
|
||||||
|
let sql = `INSERT INTO ${state.table} (${columnNames}) VALUES (${values});`;
|
||||||
|
state.ti.getNowDbInst().promptExeSql(state.ti.db, sql, null, () => {
|
||||||
|
closeAddDataDialog();
|
||||||
|
onRefresh();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请正确填写数据信息');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.update_field_active {
|
||||||
|
background-color: var(--el-color-success)
|
||||||
|
}
|
||||||
|
</style>
|
||||||
486
mayfly_go_web/src/views/ops/db/db.ts
Normal file
486
mayfly_go_web/src/views/ops/db/db.ts
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
/* eslint-disable no-unused-vars */
|
||||||
|
import { dbApi } from './api';
|
||||||
|
import SqlExecBox from './component/SqlExecBox';
|
||||||
|
|
||||||
|
const dbInstCache: Map<number, DbInst> = new Map();
|
||||||
|
|
||||||
|
export class DbInst {
|
||||||
|
/**
|
||||||
|
* 标签路径
|
||||||
|
*/
|
||||||
|
tagPath: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例id
|
||||||
|
*/
|
||||||
|
id: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例名
|
||||||
|
*/
|
||||||
|
name: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库类型, mysql postgres
|
||||||
|
*/
|
||||||
|
type: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* schema -> db
|
||||||
|
*/
|
||||||
|
dbs: Map<string, Db> = new Map()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认查询分页数量
|
||||||
|
*/
|
||||||
|
static DefaultLimit = 20;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定数据库实例,若不存在则新建并缓存
|
||||||
|
* @param dbName 数据库名
|
||||||
|
* @returns db实例
|
||||||
|
*/
|
||||||
|
getDb(dbName: string) {
|
||||||
|
if (!dbName) {
|
||||||
|
throw new Error('dbName不能为空')
|
||||||
|
}
|
||||||
|
let db = this.dbs.get(dbName)
|
||||||
|
if (db) {
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
console.info(`new db -> dbId: ${this.id}, dbName: ${dbName}`);
|
||||||
|
db = new Db();
|
||||||
|
db.name = dbName;
|
||||||
|
this.dbs.set(dbName, db);
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载数据库表信息
|
||||||
|
* @param dbName 数据库名
|
||||||
|
* @param reload 是否重新请求接口获取数据
|
||||||
|
* @returns 表信息
|
||||||
|
*/
|
||||||
|
async loadTables(dbName: string, reload?: boolean) {
|
||||||
|
const db = this.getDb(dbName);
|
||||||
|
// 优先从 table map中获取
|
||||||
|
let tables = db.tables;
|
||||||
|
if (!reload && tables) {
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
// 重置列信息缓存与表提示信息
|
||||||
|
db.columnsMap?.clear();
|
||||||
|
db.tableHints = null;
|
||||||
|
console.log(`load tables -> dbName: ${dbName}`);
|
||||||
|
tables = await dbApi.tableMetadata.request({ id: this.id, db: dbName });
|
||||||
|
db.tables = tables;
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表的所有列信息
|
||||||
|
* @param table 表名
|
||||||
|
*/
|
||||||
|
async loadColumns(dbName: string, table: string) {
|
||||||
|
const db = this.getDb(dbName);
|
||||||
|
// 优先从 table map中获取
|
||||||
|
let columns = db.getColumns(table);
|
||||||
|
if (columns) {
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
console.log(`load columns -> dbName: ${dbName}, table: ${table}`);
|
||||||
|
columns = await dbApi.columnMetadata.request({
|
||||||
|
id: this.id,
|
||||||
|
db: dbName,
|
||||||
|
tableName: table,
|
||||||
|
});
|
||||||
|
db.columnsMap.set(table, columns);
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定表的指定信息
|
||||||
|
* @param table 表名
|
||||||
|
*/
|
||||||
|
async loadTableColumn(dbName: string, table: string, columnName?: string) {
|
||||||
|
// 确保该表的列信息都已加载
|
||||||
|
await this.loadColumns(dbName, table);
|
||||||
|
return this.getDb(dbName).getColumn(table, columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取库信息提示
|
||||||
|
*/
|
||||||
|
async loadDbHints(dbName: string) {
|
||||||
|
const db = this.getDb(dbName);
|
||||||
|
if (db.tableHints) {
|
||||||
|
return db.tableHints;
|
||||||
|
}
|
||||||
|
console.log(`load db-hits -> dbName: ${dbName}`);
|
||||||
|
const hits = await dbApi.hintTables.request({ id: this.id, db: db.name, })
|
||||||
|
db.tableHints = hits;
|
||||||
|
return hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行sql
|
||||||
|
*
|
||||||
|
* @param sql sql
|
||||||
|
* @param remark 执行备注
|
||||||
|
*/
|
||||||
|
async runSql(dbName: string, sql: string, remark: string = '') {
|
||||||
|
return await dbApi.sqlExec.request({
|
||||||
|
id: this.id,
|
||||||
|
db: dbName,
|
||||||
|
sql: sql.trim(),
|
||||||
|
remark,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取指定表的默认查询sql
|
||||||
|
getDefaultSelectSql(table: string, condition: string, orderBy: string, pageNum: number, limit: number = DbInst.DefaultLimit) {
|
||||||
|
const baseSql = `SELECT * FROM ${table} ${condition ? 'WHERE ' + condition : ''} ${orderBy ? orderBy : ''}`;
|
||||||
|
if (this.type == 'mysql') {
|
||||||
|
return `${baseSql} LIMIT ${(pageNum - 1) * limit}, ${limit};`;
|
||||||
|
}
|
||||||
|
if (this.type == 'postgres') {
|
||||||
|
return `${baseSql} OFFSET ${(pageNum - 1) * limit} LIMIT ${limit};`;
|
||||||
|
}
|
||||||
|
return baseSql;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成指定数据的insert语句
|
||||||
|
* @param dbName 数据库名
|
||||||
|
* @param table 表名
|
||||||
|
* @param datas 要生成的数据
|
||||||
|
*/
|
||||||
|
genInsertSql(dbName: string, table: string, datas: any[]): string {
|
||||||
|
if (!datas) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const columns = this.getDb(dbName).getColumns(table);
|
||||||
|
const sqls = [];
|
||||||
|
for (let data of datas) {
|
||||||
|
let colNames = [];
|
||||||
|
let values = [];
|
||||||
|
for (let column of columns) {
|
||||||
|
const colName = column.columnName;
|
||||||
|
colNames.push(colName);
|
||||||
|
values.push(DbInst.wrapValueByType(data[colName]));
|
||||||
|
}
|
||||||
|
sqls.push(`INSERT INTO ${table} (${colNames.join(', ')}) VALUES(${values.join(', ')})`);
|
||||||
|
}
|
||||||
|
return sqls.join(';\n') + ';'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成根据主键删除的sql语句
|
||||||
|
* @param table 表名
|
||||||
|
* @param datas 要删除的记录
|
||||||
|
*/
|
||||||
|
genDeleteByPrimaryKeysSql(db: string, table: string, datas: any[]) {
|
||||||
|
const primaryKey = this.getDb(db).getColumn(table);
|
||||||
|
const primaryKeyColumnName = primaryKey.columnName;
|
||||||
|
const ids = datas.map((d: any) => `${DbInst.wrapColumnValue(primaryKey.columnType, d[primaryKeyColumnName])}`).join(',');
|
||||||
|
return `DELETE FROM ${table} WHERE ${primaryKeyColumnName} IN (${ids})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 弹框提示是否执行sql
|
||||||
|
*/
|
||||||
|
promptExeSql = (db: string, sql: string, cancelFunc: any = null, successFunc: any = null) => {
|
||||||
|
SqlExecBox({
|
||||||
|
sql, dbId: this.id, db,
|
||||||
|
runSuccessCallback: successFunc,
|
||||||
|
cancelCallback: cancelFunc,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取或新建dbInst,如果缓存中不存在则新建,否则直接返回
|
||||||
|
* @param inst 数据库实例,后端返回的列表接口中的信息
|
||||||
|
* @returns DbInst
|
||||||
|
*/
|
||||||
|
static getOrNewInst(inst: any) {
|
||||||
|
if (!inst) {
|
||||||
|
throw new Error('inst不能为空')
|
||||||
|
}
|
||||||
|
let dbInst = dbInstCache.get(inst.id);
|
||||||
|
if (dbInst) {
|
||||||
|
return dbInst;
|
||||||
|
}
|
||||||
|
console.info(`new dbInst: ${inst.id}, tagPath: ${inst.tagPath}`);
|
||||||
|
dbInst = new DbInst();
|
||||||
|
dbInst.tagPath = inst.tagPath;
|
||||||
|
dbInst.id = inst.id;
|
||||||
|
dbInst.name = inst.name;
|
||||||
|
dbInst.type = inst.type;
|
||||||
|
|
||||||
|
dbInstCache.set(dbInst.id, dbInst);
|
||||||
|
return dbInst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取数据库实例id,若不存在,则新建一个并缓存
|
||||||
|
* @param dbId 数据库实例id
|
||||||
|
* @param dbType 第一次获取时为必传项,即第一次创建时
|
||||||
|
* @returns 数据库实例
|
||||||
|
*/
|
||||||
|
static getInst(dbId?: number): DbInst {
|
||||||
|
if (!dbId) {
|
||||||
|
throw new Error('dbId不能为空');
|
||||||
|
}
|
||||||
|
let dbInst = dbInstCache.get(dbId);
|
||||||
|
if (dbInst) {
|
||||||
|
return dbInst;
|
||||||
|
}
|
||||||
|
throw new Error('dbInst不存在! 请在合适调用点使用DbInst.newInst()新建该实例');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空所有实例缓存信息
|
||||||
|
*/
|
||||||
|
static clearAll() {
|
||||||
|
dbInstCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取count sql
|
||||||
|
* @param table 表名
|
||||||
|
* @param condition 条件
|
||||||
|
* @returns count sql
|
||||||
|
*/
|
||||||
|
static getDefaultCountSql = (table: string, condition?: string) => {
|
||||||
|
return `SELECT COUNT(*) count FROM ${table} ${condition ? 'WHERE ' + condition : ''}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据返回值包装值,若值为字符串类型则添加''
|
||||||
|
* @param val 值
|
||||||
|
* @returns 包装后的值
|
||||||
|
*/
|
||||||
|
static wrapValueByType = (val: any) => {
|
||||||
|
if (val == null) {
|
||||||
|
return 'NULL';
|
||||||
|
}
|
||||||
|
if (typeof val == 'number') {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
return `'${val}'`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可
|
||||||
|
*/
|
||||||
|
static wrapColumnValue(columnType: string, value: any) {
|
||||||
|
if (this.isNumber(columnType)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return `'${value}'`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断字段类型是否为数字类型
|
||||||
|
* @param columnType 字段类型
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static isNumber(columnType: string) {
|
||||||
|
return columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param str 字符串
|
||||||
|
* @param tableData 表数据
|
||||||
|
* @param flag 标志
|
||||||
|
* @returns 列宽度
|
||||||
|
*/
|
||||||
|
static flexColumnWidth = (str: any, tableData: any, flag = 'equal') => {
|
||||||
|
// str为该列的字段名(传字符串);tableData为该表格的数据源(传变量);
|
||||||
|
// flag为可选值,可不传该参数,传参时可选'max'或'equal',默认为'max'
|
||||||
|
// flag为'max'则设置列宽适配该列中最长的内容,flag为'equal'则设置列宽适配该列中第一行内容的长度。
|
||||||
|
str = str + '';
|
||||||
|
let columnContent = '';
|
||||||
|
if (!tableData || !tableData.length || tableData.length === 0 || tableData === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!str || !str.length || str.length === 0 || str === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (flag === 'equal') {
|
||||||
|
// 获取该列中第一个不为空的数据(内容)
|
||||||
|
for (let i = 0; i < tableData.length; i++) {
|
||||||
|
// 转为字符串后比较
|
||||||
|
if ((tableData[i][str] + '').length > 0) {
|
||||||
|
columnContent = tableData[i][str] + '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 获取该列中最长的数据(内容)
|
||||||
|
let index = 0;
|
||||||
|
for (let i = 0; i < tableData.length; i++) {
|
||||||
|
if (tableData[i][str] === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const now_temp = tableData[i][str] + '';
|
||||||
|
const max_temp = tableData[index][str] + '';
|
||||||
|
if (now_temp.length > max_temp.length) {
|
||||||
|
index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
columnContent = tableData[index][str] + '';
|
||||||
|
}
|
||||||
|
const contentWidth: number = DbInst.getContentWidth(columnContent);
|
||||||
|
// 获取列名称的长度 加上排序图标长度
|
||||||
|
const columnWidth: number = DbInst.getContentWidth(str) + 43;
|
||||||
|
const flexWidth: number = contentWidth > columnWidth ? contentWidth : columnWidth;
|
||||||
|
return flexWidth + 'px';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取内容所需要占用的宽度
|
||||||
|
*/
|
||||||
|
static getContentWidth = (content: any): number => {
|
||||||
|
// 以下分配的单位长度可根据实际需求进行调整
|
||||||
|
let flexWidth = 0;
|
||||||
|
for (const char of content) {
|
||||||
|
if (flexWidth > 500) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((char >= '0' && char <= '9') || (char >= 'a' && char <= 'z')) {
|
||||||
|
// 如果是小写字母、数字字符,分配8个单位宽度
|
||||||
|
flexWidth += 8.5;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (char >= 'A' && char <= 'Z') {
|
||||||
|
flexWidth += 9;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (char >= '\u4e00' && char <= '\u9fa5') {
|
||||||
|
// 如果是中文字符,为字符分配16个单位宽度
|
||||||
|
flexWidth += 16;
|
||||||
|
} else {
|
||||||
|
// 其他种类字符,为字符分配9个单位宽度
|
||||||
|
flexWidth += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flexWidth > 500) {
|
||||||
|
// 设置最大宽度
|
||||||
|
flexWidth = 500;
|
||||||
|
}
|
||||||
|
return flexWidth;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库实例信息
|
||||||
|
*/
|
||||||
|
class Db {
|
||||||
|
name: string // 库名
|
||||||
|
tables: [] // 数据库实例表信息
|
||||||
|
columnsMap: Map<string, any> = new Map // table -> columns
|
||||||
|
tableHints: any = null // 提示词
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定表列信息(前提需要dbInst.loadColumns)
|
||||||
|
* @param table 表名
|
||||||
|
*/
|
||||||
|
getColumns(table: string) {
|
||||||
|
return this.columnsMap.get(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定表中的指定列名信息,若列名为空则默认返回主键
|
||||||
|
* @param table 表名
|
||||||
|
* @param columnName 列名
|
||||||
|
*/
|
||||||
|
getColumn(table: string, columnName: string = '') {
|
||||||
|
const cols = this.getColumns(table);
|
||||||
|
if (!columnName) {
|
||||||
|
const col = cols.find((c: any) => c.columnKey == 'PRI');
|
||||||
|
return col || cols[0];
|
||||||
|
}
|
||||||
|
return cols.find((c: any) => c.columnName == columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TabType {
|
||||||
|
/**
|
||||||
|
* 表数据
|
||||||
|
*/
|
||||||
|
TableData,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询框
|
||||||
|
*/
|
||||||
|
Query,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TabInfo {
|
||||||
|
/**
|
||||||
|
* tab唯一key。与label、name都一致
|
||||||
|
*/
|
||||||
|
key: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单树节点key
|
||||||
|
*/
|
||||||
|
treeNodeKey: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库实例id
|
||||||
|
*/
|
||||||
|
dbId: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 库名
|
||||||
|
*/
|
||||||
|
db: string = ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tab 类型
|
||||||
|
*/
|
||||||
|
type: TabType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tab需要的其他信息
|
||||||
|
*/
|
||||||
|
params: any
|
||||||
|
|
||||||
|
getNowDbInst() {
|
||||||
|
return DbInst.getInst(this.dbId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getNowDb() {
|
||||||
|
return this.getNowDbInst().getDb(this.db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 修改表字段所需数据 */
|
||||||
|
export type UpdateFieldsMeta = {
|
||||||
|
// 主键值
|
||||||
|
primaryKey: string
|
||||||
|
// 主键名
|
||||||
|
primaryKeyName: string
|
||||||
|
// 主键类型
|
||||||
|
primaryKeyType: string
|
||||||
|
// 新值
|
||||||
|
fields: FieldsMeta[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FieldsMeta = {
|
||||||
|
// 字段所在div
|
||||||
|
div: HTMLElement
|
||||||
|
// 字段名
|
||||||
|
fieldName: string
|
||||||
|
// 字段所在的表格行数据
|
||||||
|
row: any
|
||||||
|
// 字段类型
|
||||||
|
fieldType: string
|
||||||
|
// 原值
|
||||||
|
oldValue: string
|
||||||
|
// 新值
|
||||||
|
newValue: string
|
||||||
|
}
|
||||||
@@ -7,5 +7,6 @@ export default {
|
|||||||
// 数据库sql执行类型
|
// 数据库sql执行类型
|
||||||
DbSqlExecTypeEnum: new Enum().add('UPDATE', 'UPDATE', 1)
|
DbSqlExecTypeEnum: new Enum().add('UPDATE', 'UPDATE', 1)
|
||||||
.add('DELETE', 'DELETE', 2)
|
.add('DELETE', 'DELETE', 2)
|
||||||
.add('INSERT', 'INSERT', 3),
|
.add('INSERT', 'INSERT', 3)
|
||||||
|
.add('QUERY', 'QUERY', 4),
|
||||||
}
|
}
|
||||||
@@ -446,7 +446,6 @@ const showCreateFileDialog = (node: any) => {
|
|||||||
|
|
||||||
const createFile = async () => {
|
const createFile = async () => {
|
||||||
const node = state.createFileDialog.node;
|
const node = state.createFileDialog.node;
|
||||||
console.log(node.data);
|
|
||||||
const name = state.createFileDialog.name;
|
const name = state.createFileDialog.name;
|
||||||
const type = state.createFileDialog.type;
|
const type = state.createFileDialog.type;
|
||||||
const path = node.data.path + '/' + name;
|
const path = node.data.path + '/' + name;
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true"
|
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true"
|
||||||
:before-close="cancel" width="38%">
|
:before-close="cancel" width="650px">
|
||||||
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px">
|
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px">
|
||||||
<el-form-item prop="tagId" label="标签:" required>
|
<el-tabs v-model="tabActiveName">
|
||||||
|
<el-tab-pane label="基础信息" name="basic">
|
||||||
|
<el-form-item prop="tagId" label="标签:" required :rules="{
|
||||||
|
required: true,
|
||||||
|
message: '请选择标签',
|
||||||
|
trigger: ['change', 'blur'],
|
||||||
|
}">
|
||||||
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
|
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="name" label="名称:" required>
|
<el-form-item prop="name" label="名称:" required>
|
||||||
@@ -19,58 +25,49 @@
|
|||||||
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
|
<el-input type="number" v-model.number="form.port" placeholder="端口"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="username" label="用户名:" required>
|
|
||||||
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
<el-form-item prop="username" label="用户名:">
|
||||||
|
<el-input v-model.trim="form.username" placeholder="请输授权用户名" autocomplete="new-password">
|
||||||
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="authMethod" label="认证方式:" required>
|
|
||||||
<el-select style="width: 100%" v-model="form.authMethod" placeholder="请选择认证方式">
|
<el-form-item label="认证方式:">
|
||||||
<el-option key="1" label="Password" :value="1"> </el-option>
|
<el-select @change="changeAuthMethod" style="width: 100%" v-model="state.authType"
|
||||||
<el-option key="2" label="PublicKey" :value="2"> </el-option>
|
placeholder="请选认证方式">
|
||||||
|
<el-option key="1" label="密码" :value="1"> </el-option>
|
||||||
|
<el-option key="2" label="授权凭证" :value="2"> </el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="form.authMethod == 1" prop="password" label="密码:">
|
<el-form-item v-if="state.authType == 1" prop="password" label="密码:">
|
||||||
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
|
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码"
|
||||||
autocomplete="new-password">
|
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">
|
|
||||||
<template #reference>
|
|
||||||
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
|
|
||||||
</template>
|
|
||||||
</el-popover>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="form.authMethod == 2" prop="password" label="秘钥:">
|
|
||||||
<el-input type="textarea" :rows="3" v-model="form.password" placeholder="请将私钥文件内容拷贝至此,修改操作可不填">
|
<el-form-item v-if="state.authType == 2" prop="authCertId" label="授权凭证:" required>
|
||||||
</el-input>
|
<auth-cert-select ref="authCertSelectRef" v-model="form.authCertId" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item prop="remark" label="备注:">
|
<el-form-item prop="remark" label="备注:">
|
||||||
<el-input type="textarea" v-model="form.remark"></el-input>
|
<el-input type="textarea" v-model="form.remark"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="其他配置" name="other">
|
||||||
<el-form-item prop="enableRecorder" label="终端回放:">
|
<el-form-item prop="enableRecorder" label="终端回放:">
|
||||||
<el-checkbox v-model="form.enableRecorder" :true-label="1" :false-label="-1"></el-checkbox>
|
<el-checkbox v-model="form.enableRecorder" :true-label="1" :false-label="-1"></el-checkbox>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
|
<el-form-item prop="sshTunnelMachineId" label="SSH隧道:">
|
||||||
<el-col :span="3">
|
<ssh-tunnel-select v-model="form.sshTunnelMachineId" />
|
||||||
<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-form-item>
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div>
|
<div>
|
||||||
|
<el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
|
||||||
<el-button @click="cancel()">取 消</el-button>
|
<el-button @click="cancel()">取 消</el-button>
|
||||||
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,17 +80,14 @@
|
|||||||
import { toRefs, reactive, watch, ref } from 'vue';
|
import { toRefs, reactive, watch, ref } from 'vue';
|
||||||
import { machineApi } from './api';
|
import { machineApi } from './api';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { notBlank } from '@/common/assert';
|
|
||||||
import { RsaEncrypt } from '@/common/rsa';
|
|
||||||
import TagSelect from '../component/TagSelect.vue';
|
import TagSelect from '../component/TagSelect.vue';
|
||||||
|
import SshTunnelSelect from '../component/SshTunnelSelect.vue';
|
||||||
|
import AuthCertSelect from './authcert/AuthCertSelect.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
projects: {
|
|
||||||
type: Array,
|
|
||||||
},
|
|
||||||
machine: {
|
machine: {
|
||||||
type: [Boolean, Object],
|
type: [Boolean, Object],
|
||||||
},
|
},
|
||||||
@@ -106,13 +100,6 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
|
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
tagId: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请选择标签',
|
|
||||||
trigger: ['change', 'blur'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
name: [
|
name: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -127,50 +114,62 @@ const rules = {
|
|||||||
trigger: ['change', 'blur'],
|
trigger: ['change', 'blur'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
username: [
|
authCertId: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请输入用户名',
|
message: '请选择授权凭证',
|
||||||
trigger: ['change', 'blur'],
|
trigger: ['change', 'blur'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
authMethod: [
|
username: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请选择认证方式',
|
message: '请输入授权用户名',
|
||||||
|
trigger: ['change', 'blur'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入授权密码',
|
||||||
trigger: ['change', 'blur'],
|
trigger: ['change', 'blur'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
const machineForm: any = ref(null);
|
const machineForm: any = ref(null);
|
||||||
|
const authCertSelectRef: any = ref(null);
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
|
tabActiveName: 'basic',
|
||||||
sshTunnelMachineList: [] as any,
|
sshTunnelMachineList: [] as any,
|
||||||
|
authCerts: [] as any,
|
||||||
|
authType: 1,
|
||||||
form: {
|
form: {
|
||||||
id: null,
|
id: null,
|
||||||
tagId: null as any,
|
|
||||||
tagPath: '',
|
|
||||||
ip: null,
|
ip: null,
|
||||||
name: null,
|
|
||||||
authMethod: 1,
|
|
||||||
port: 22,
|
port: 22,
|
||||||
|
name: null,
|
||||||
|
authCertId: null as any,
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
tagId: null as any,
|
||||||
|
tagPath: null as any,
|
||||||
remark: '',
|
remark: '',
|
||||||
enableSshTunnel: null,
|
sshTunnelMachineId: null as any,
|
||||||
sshTunnelMachineId: null,
|
|
||||||
enableRecorder: -1,
|
enableRecorder: -1,
|
||||||
},
|
},
|
||||||
pwd: '',
|
pwd: '',
|
||||||
|
testConnBtnLoading: false,
|
||||||
btnLoading: false,
|
btnLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dialogVisible,
|
dialogVisible,
|
||||||
sshTunnelMachineList,
|
tabActiveName,
|
||||||
form,
|
form,
|
||||||
pwd,
|
testConnBtnLoading,
|
||||||
btnLoading,
|
btnLoading,
|
||||||
} = toRefs(state)
|
} = toRefs(state)
|
||||||
|
|
||||||
@@ -179,53 +178,65 @@ watch(props, async (newValue: any) => {
|
|||||||
if (!state.dialogVisible) {
|
if (!state.dialogVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
state.tabActiveName = 'basic';
|
||||||
if (newValue.machine) {
|
if (newValue.machine) {
|
||||||
state.form = { ...newValue.machine };
|
state.form = { ...newValue.machine };
|
||||||
|
// 如果凭证类型为公共的,则表示使用授权凭证认证
|
||||||
|
const authCertId = (state.form as any).authCertId
|
||||||
|
if (authCertId > 0) {
|
||||||
|
state.authType = 2;
|
||||||
} else {
|
} else {
|
||||||
state.form = { port: 22, authMethod: 1 } as any;
|
state.authType = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.form = { port: 22 } as any;
|
||||||
|
state.authType = 1;
|
||||||
}
|
}
|
||||||
getSshTunnelMachines();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSshTunnelMachines = async () => {
|
const changeAuthMethod = (val: any) => {
|
||||||
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
|
if (state.form.id) {
|
||||||
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
|
if (val == 2) {
|
||||||
state.sshTunnelMachineList = res.list;
|
state.form.authCertId = null;
|
||||||
|
} else {
|
||||||
|
state.form.password = '';
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const getSshTunnelMachine = (machineId: any) => {
|
|
||||||
notBlank(machineId, '请选择或先创建一台隧道机器');
|
|
||||||
return state.sshTunnelMachineList.find((x: any) => x.id == machineId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPwd = async () => {
|
|
||||||
state.pwd = await machineApi.getMachinePwd.request({ id: state.form.id });
|
|
||||||
};
|
|
||||||
|
|
||||||
const btnOk = async () => {
|
|
||||||
if (!state.form.id) {
|
|
||||||
notBlank(state.form.password, '新增操作,密码不可为空');
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testConn = async () => {
|
||||||
machineForm.value.validate(async (valid: boolean) => {
|
machineForm.value.validate(async (valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const form: any = state.form;
|
const form = getReqForm();
|
||||||
if (form.enableSshTunnel == 1) {
|
if (!form) {
|
||||||
const tunnelMachine: any = getSshTunnelMachine(form.sshTunnelMachineId);
|
|
||||||
if (tunnelMachine.ip == form.ip && tunnelMachine.port == form.port) {
|
|
||||||
ElMessage.error('隧道机器不能与本机器一致');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
state.testConnBtnLoading = true;
|
||||||
|
try {
|
||||||
|
await machineApi.testConn.request(form);
|
||||||
|
ElMessage.success('连接成功');
|
||||||
|
} finally {
|
||||||
|
state.testConnBtnLoading = false;
|
||||||
}
|
}
|
||||||
const reqForm: any = { ...form };
|
} else {
|
||||||
if (reqForm.authMethod == 1) {
|
ElMessage.error('请正确填写信息');
|
||||||
reqForm.password = await RsaEncrypt(state.form.password);
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnOk = async () => {
|
||||||
|
machineForm.value.validate(async (valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
const form = getReqForm();
|
||||||
|
if (!form) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
state.btnLoading = true;
|
state.btnLoading = true;
|
||||||
try {
|
try {
|
||||||
await machineApi.saveMachine.request(reqForm);
|
await machineApi.saveMachine.request(form);
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
emit('val-change', state.form);
|
emit('val-change', form);
|
||||||
cancel();
|
cancel();
|
||||||
} finally {
|
} finally {
|
||||||
state.btnLoading = false;
|
state.btnLoading = false;
|
||||||
@@ -237,11 +248,22 @@ const btnOk = async () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getReqForm = () => {
|
||||||
|
const reqForm: any = { ...state.form };
|
||||||
|
debugger
|
||||||
|
// 如果为密码认证,则置空授权凭证id
|
||||||
|
if (state.authType == 1) {
|
||||||
|
reqForm.authCertId = -1;
|
||||||
|
}
|
||||||
|
if (!state.form.sshTunnelMachineId || state.form.sshTunnelMachineId <= 0) {
|
||||||
|
reqForm.sshTunnelMachineId = -1
|
||||||
|
}
|
||||||
|
return reqForm
|
||||||
|
}
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
emit('cancel');
|
emit('cancel');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss"></style>
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
<el-input class="ml5" placeholder="请输入名称" style="width: 150px" v-model="params.name" @clear="search"
|
<el-input class="ml5" placeholder="请输入名称" style="width: 150px" v-model="params.name" @clear="search"
|
||||||
plain clearable></el-input>
|
plain clearable></el-input>
|
||||||
<el-input class="ml5" placeholder="请输入ip" style="width: 150px" v-model="params.ip" @clear="search"
|
<el-input class="ml5" placeholder="请输入ip" style="width: 150px" v-model="params.ip" @clear="search" plain
|
||||||
plain clearable></el-input>
|
clearable></el-input>
|
||||||
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
|
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,16 +29,28 @@
|
|||||||
</el-radio>
|
</el-radio>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</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="140" show-overflow-tooltip></el-table-column>
|
<el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip></el-table-column>
|
||||||
|
|
||||||
<el-table-column prop="ip" label="ip:port" min-width="150">
|
<el-table-column prop="ip" label="ip:port" min-width="150">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-link :disabled="scope.row.status == -1" @click="showMachineStats(scope.row)" type="primary"
|
<el-link :disabled="scope.row.status == -1" @click="showMachineStats(scope.row)" type="primary"
|
||||||
:underline="false">{{
|
:underline="false">
|
||||||
`${scope.row.ip}:${scope.row.port}`
|
{{ `${scope.row.ip}:${scope.row.port}` }}
|
||||||
}}</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="username" label="用户名" min-width="100">
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
<el-table-column prop="status" label="状态" min-width="80">
|
<el-table-column prop="status" label="状态" min-width="80">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-switch v-auth:disabled="'machine:update'" :width="52" v-model="scope.row.status"
|
<el-switch v-auth:disabled="'machine:update'" :width="52" v-model="scope.row.status"
|
||||||
@@ -47,7 +59,7 @@
|
|||||||
@change="changeStatus(scope.row)"></el-switch>
|
@change="changeStatus(scope.row)"></el-switch>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="username" label="用户名" min-width="90"></el-table-column>
|
|
||||||
<el-table-column prop="remark" label="备注" min-width="250" show-overflow-tooltip></el-table-column>
|
<el-table-column prop="remark" label="备注" min-width="250" show-overflow-tooltip></el-table-column>
|
||||||
|
|
||||||
<el-table-column label="操作" min-width="235" fixed="right">
|
<el-table-column label="操作" min-width="235" fixed="right">
|
||||||
@@ -59,13 +71,13 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-auth="'machine:file'">
|
<span v-auth="'machine:file'">
|
||||||
<el-link type="success" :disabled="scope.row.status == -1"
|
<el-link type="success" :disabled="scope.row.status == -1" @click="showFileManage(scope.row)"
|
||||||
@click="showFileManage(scope.row)" plain size="small" :underline="false">文件</el-link>
|
plain size="small" :underline="false">文件</el-link>
|
||||||
<el-divider direction="vertical" border-style="dashed" />
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<el-link :disabled="scope.row.status == -1" type="warning" @click="serviceManager(scope.row)"
|
<el-link :disabled="scope.row.status == -1" type="warning" @click="serviceManager(scope.row)" plain
|
||||||
plain size="small" :underline="false">脚本</el-link>
|
size="small" :underline="false">脚本</el-link>
|
||||||
<el-divider direction="vertical" border-style="dashed" />
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
|
||||||
<el-dropdown>
|
<el-dropdown>
|
||||||
@@ -83,8 +95,8 @@
|
|||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
|
||||||
<el-dropdown-item>
|
<el-dropdown-item>
|
||||||
<el-link @click="showProcess(scope.row)" :disabled="scope.row.status == -1"
|
<el-link @click="showProcess(scope.row)" :disabled="scope.row.status == -1" plain
|
||||||
plain :underline="false" size="small">进程</el-link>
|
:underline="false" size="small">进程</el-link>
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
|
||||||
<el-dropdown-item v-if="scope.row.enableRecorder == 1">
|
<el-dropdown-item v-if="scope.row.enableRecorder == 1">
|
||||||
@@ -93,7 +105,8 @@
|
|||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
|
||||||
<el-dropdown-item>
|
<el-dropdown-item>
|
||||||
<el-link :disabled="!scope.row.hasCli || scope.row.status == -1" type="danger"
|
<el-link v-auth="'machine:close-cli'"
|
||||||
|
:disabled="!scope.row.hasCli || scope.row.status == -1" type="danger"
|
||||||
@click="closeCli(scope.row)" plain size="small" :underline="false">关闭连接
|
@click="closeCli(scope.row)" plain size="small" :underline="false">关闭连接
|
||||||
</el-link>
|
</el-link>
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
@@ -121,13 +134,13 @@
|
|||||||
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</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="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="认证方式">{{ infoDialog.data.authMethod == 1 ? 'Password' :
|
<el-descriptions-item :span="1" label="认证方式">
|
||||||
'PublicKey'
|
{{ infoDialog.data.authCertId > 1 ? '授权凭证' : '密码' }}
|
||||||
}}</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
|
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
|
||||||
|
|
||||||
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.enableSshTunnel == 1 ? '是' : '否' }}
|
<el-descriptions-item :span="1.5" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }}
|
<el-descriptions-item :span="1.5" label="终端回放">{{ infoDialog.data.enableRecorder == 1 ? '是' : '否' }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
@@ -149,7 +162,7 @@
|
|||||||
|
|
||||||
<process-list v-model:visible="processDialog.visible" v-model:machineId="processDialog.machineId" />
|
<process-list v-model:visible="processDialog.visible" v-model:machineId="processDialog.machineId" />
|
||||||
|
|
||||||
<service-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible"
|
<script-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible"
|
||||||
v-model:machineId="serviceDialog.machineId" />
|
v-model:machineId="serviceDialog.machineId" />
|
||||||
|
|
||||||
<file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible"
|
<file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible"
|
||||||
@@ -164,18 +177,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toRefs, reactive, onMounted } from 'vue';
|
import { toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { machineApi } from './api';
|
import { machineApi } from './api';
|
||||||
import { tagApi } from '../tag/api.ts';
|
import { tagApi } from '../tag/api';
|
||||||
import ServiceManage from './ServiceManage.vue';
|
|
||||||
import FileManage from './FileManage.vue';
|
|
||||||
import MachineEdit from './MachineEdit.vue';
|
|
||||||
import ProcessList from './ProcessList.vue';
|
|
||||||
import MachineStats from './MachineStats.vue';
|
|
||||||
import MachineRec from './MachineRec.vue';
|
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { dateFormat } from '@/common/utils/date';
|
||||||
|
import TagInfo from '../component/TagInfo.vue';
|
||||||
|
|
||||||
|
// 组件
|
||||||
|
const MachineEdit = defineAsyncComponent(() => import('./MachineEdit.vue'));
|
||||||
|
const ScriptManage = defineAsyncComponent(() => import('./ScriptManage.vue'));
|
||||||
|
const FileManage = defineAsyncComponent(() => import('./FileManage.vue'));
|
||||||
|
const MachineStats = defineAsyncComponent(() => import('./MachineStats.vue'));
|
||||||
|
const MachineRec = defineAsyncComponent(() => import('./MachineRec.vue'));
|
||||||
|
const ProcessList = defineAsyncComponent(() => import('./ProcessList.vue'));
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user