mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-15 05:40:25 +08:00
Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adc65439e4 | ||
|
|
445cf3716b | ||
|
|
f4b59b8503 | ||
|
|
4f08975df2 | ||
|
|
570db453d7 | ||
|
|
b93984bf6f | ||
|
|
e483db1b97 | ||
|
|
17d96acceb | ||
|
|
9900b236ef | ||
|
|
4fa52412c1 | ||
|
|
0076869deb | ||
|
|
af55193591 | ||
|
|
1d858118d5 | ||
|
|
8e64ba67fa | ||
|
|
58fb11b78f | ||
|
|
f6e9076a40 | ||
|
|
2fefa43aea | ||
|
|
f43b0467ba | ||
|
|
14da4d77e0 | ||
|
|
cefad86b85 | ||
|
|
738ff86344 | ||
|
|
110abc4ac7 | ||
|
|
5f1aaa40d8 | ||
|
|
0695ad9a85 | ||
|
|
7c086bbec8 | ||
|
|
c75fe7135a | ||
|
|
edf29976dd | ||
|
|
7b5f963ec4 | ||
|
|
f22badb861 | ||
|
|
8c501c90cd | ||
|
|
11eebdfcf0 | ||
|
|
46e331783f | ||
|
|
7e33e64659 | ||
|
|
7e4152ad6d | ||
|
|
d189ee7c22 | ||
|
|
641c2abb24 | ||
|
|
3ab4ac891b | ||
|
|
70b586e45a | ||
|
|
77aa724003 | ||
|
|
db7b50d96a | ||
|
|
00fee24a85 | ||
|
|
fa0cb73ec9 | ||
|
|
6ee815b71d | ||
|
|
311a048af5 | ||
|
|
3da9ecfaa3 | ||
|
|
4c2c6f613e | ||
|
|
ba63736871 | ||
|
|
ed3dbafc35 | ||
|
|
fdeffbd495 | ||
|
|
9870812e6b | ||
|
|
46f35e14c4 | ||
|
|
e89cf96ff4 | ||
|
|
5cd19bf38d | ||
|
|
a6f69f2b62 | ||
|
|
e34b9adada | ||
|
|
9f43f731b5 | ||
|
|
594ca43505 | ||
|
|
2a91cdb67a | ||
|
|
11148e720b | ||
|
|
c5835d6d8c | ||
|
|
4a26bb3ba5 | ||
|
|
4fec38724d | ||
|
|
85349df8a1 | ||
|
|
ffe250f8a9 | ||
|
|
eeb310a1d2 | ||
|
|
a42c606d20 | ||
|
|
4e64d46cd2 | ||
|
|
9886ee6828 | ||
|
|
0e1bde09c3 | ||
|
|
dda600709b | ||
|
|
15f38491b2 | ||
|
|
4b140732f7 | ||
|
|
4cb9ff3f14 | ||
|
|
e4f3e2c4c1 | ||
|
|
a80adb7dd8 | ||
|
|
195127a9d4 | ||
|
|
f2119b2c52 | ||
|
|
f15c45793b | ||
|
|
24f543e667 | ||
|
|
772995705f | ||
|
|
3475c39fe6 | ||
|
|
e6e393379f | ||
|
|
03cc91c3e5 | ||
|
|
f82f7bec6a | ||
|
|
4afd5bbd5e | ||
|
|
86aac2bf08 | ||
|
|
70c8b25a67 | ||
|
|
231af72444 | ||
|
|
480e930385 | ||
|
|
debc34f0fb | ||
|
|
99ce3bd099 | ||
|
|
99431cf9a2 | ||
|
|
83711c69f9 | ||
|
|
9e67032280 | ||
|
|
fa37937410 | ||
|
|
1be2cad78e | ||
|
|
2b1e687ed4 | ||
|
|
881009321b | ||
|
|
aed99b63b8 | ||
|
|
dfa34ba371 | ||
|
|
20beb30dd8 | ||
|
|
4475972af3 | ||
|
|
a843c65783 | ||
|
|
c2de1d3fa2 | ||
|
|
c8d091da06 | ||
|
|
553208ba57 | ||
|
|
072028699a | ||
|
|
9cdcf145a5 | ||
|
|
4df1c19e81 | ||
|
|
ac26a214bc | ||
|
|
ad616496d1 | ||
|
|
9870582779 | ||
|
|
20cc696b33 | ||
|
|
d7263f2b3c | ||
|
|
74e5ee41fb | ||
|
|
f936331dff | ||
|
|
ba311c3504 | ||
|
|
03291594b1 | ||
|
|
a6d9a4b5ae | ||
|
|
875de022c1 | ||
|
|
2c863a2774 | ||
|
|
f2f086a82c | ||
|
|
936ca61f94 | ||
|
|
87ae2f81fa | ||
|
|
ecf67db2b1 | ||
|
|
2e5589e112 | ||
|
|
2598a60898 | ||
|
|
0de226bbf3 | ||
|
|
422f0d8491 | ||
|
|
b028708b94 | ||
|
|
812c0d0f6a | ||
|
|
46df5293dd | ||
|
|
ab42b3e90b | ||
|
|
1378259cc7 | ||
|
|
c8f0b0a83f | ||
|
|
acec760ec1 | ||
|
|
2fe70d49f6 | ||
|
|
9013fff804 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -16,3 +16,5 @@
|
|||||||
|
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
**/vendor/
|
**/vendor/
|
||||||
|
.idea
|
||||||
|
out
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
<img src="https://img.shields.io/github/stars/may-fly/mayfly-go.svg?style=social" alt="github star"/>
|
<img src="https://img.shields.io/github/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="ccr.ccs.tencentyun.com/mayfly/mayfly-go:${imageVersion}"
|
||||||
|
docker buildx build --push --platform linux/amd64,linux/arm64 -t "${imageName}" .
|
||||||
|
echo_green "docker多版本镜像构建完成->[${imageName}]"
|
||||||
|
echo_yellow "-------------------docker buildx构建镜像结束-------------------"
|
||||||
|
}
|
||||||
|
|
||||||
function runBuild() {
|
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',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="zh_CN">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
|||||||
5164
mayfly_go_web/package-lock.json
generated
Normal file
5164
mayfly_go_web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,31 +4,34 @@
|
|||||||
"scripts": {
|
"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.9",
|
"@element-plus/icons-vue": "^2.1.0",
|
||||||
"asciinema-player": "^3.0.1",
|
"asciinema-player": "^3.3.0",
|
||||||
"axios": "^1.1.2",
|
"axios": "^1.4.0",
|
||||||
"codemirror": "^5.65.5",
|
|
||||||
"countup.js": "^2.0.7",
|
"countup.js": "^2.0.7",
|
||||||
"cropperjs": "^1.5.11",
|
"cropperjs": "^1.5.11",
|
||||||
"echarts": "^5.3.3",
|
"echarts": "^5.4.0",
|
||||||
"element-plus": "^2.2.18",
|
"element-plus": "^2.3.5",
|
||||||
"jsencrypt": "^3.2.1",
|
"jsencrypt": "^3.3.1",
|
||||||
"jsoneditor": "^9.9.2",
|
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
|
"monaco-editor": "^0.38.0",
|
||||||
|
"monaco-sql-languages": "^0.11.0",
|
||||||
|
"monaco-themes": "^0.4.4",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
|
"pinia": "^2.1.3",
|
||||||
"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.41",
|
"vue": "^3.3.4",
|
||||||
"vue-clipboard3": "^1.0.1",
|
"vue-clipboard3": "^1.0.1",
|
||||||
"vue-router": "^4.1.5",
|
"vue-router": "^4.2.2",
|
||||||
"vuex": "^4.0.2",
|
"xterm": "^5.2.1",
|
||||||
"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",
|
||||||
@@ -37,17 +40,17 @@
|
|||||||
"@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.62.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.3.9",
|
||||||
"vue-eslint-parser": "^8.0.1"
|
"vue-eslint-parser": "^9.1.1"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
1
mayfly_go_web/plugins.d.ts
vendored
1
mayfly_go_web/plugins.d.ts
vendored
@@ -1 +0,0 @@
|
|||||||
declare module 'vue-grid-layout';
|
|
||||||
@@ -3,3 +3,22 @@ window.globalConfig = {
|
|||||||
"BaseApiUrl": "",
|
"BaseApiUrl": "",
|
||||||
"BaseWsUrl": ""
|
"BaseWsUrl": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// index.html添加百秒级时间戳,防止被浏览器缓存
|
||||||
|
!function () {
|
||||||
|
let t = "t=" + new Date().getTime().toString().substring(0, 8)
|
||||||
|
let search = location.search;
|
||||||
|
let m = search && search.match(/t=\d*/g)
|
||||||
|
|
||||||
|
if (m[0]) {
|
||||||
|
if (m[0] !== t) {
|
||||||
|
location.search = search.replace(m[0], t)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (search.indexOf('?') > -1) {
|
||||||
|
location.search = search + '&' + t
|
||||||
|
} else {
|
||||||
|
location.search = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|||||||
10
mayfly_go_web/shim.d.ts
vendored
10
mayfly_go_web/shim.d.ts
vendored
@@ -1,10 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
declare module '*.vue' {
|
|
||||||
import type { DefineComponent } from 'vue';
|
|
||||||
const component: DefineComponent<{}, {}, any>;
|
|
||||||
export default component;
|
|
||||||
}
|
|
||||||
declare module 'codemirror';
|
|
||||||
declare module 'sql-formatter';
|
|
||||||
declare module 'jsoneditor';
|
|
||||||
declare module 'asciinema-player';
|
|
||||||
@@ -1,34 +1,32 @@
|
|||||||
<template>
|
<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,6 +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.4.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
@@ -1,4 +1,42 @@
|
|||||||
import * as echarts from 'echarts'
|
// import * as echarts from 'echarts'
|
||||||
|
|
||||||
|
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
|
||||||
|
import * as echarts from "echarts/core";
|
||||||
|
|
||||||
|
/** 图表后缀都为 Chart */
|
||||||
|
import { PieChart } from "echarts/charts";
|
||||||
|
|
||||||
|
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
|
||||||
|
import {
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
DatasetComponent,
|
||||||
|
TransformComponent,
|
||||||
|
LegendComponent,
|
||||||
|
} from "echarts/components";
|
||||||
|
|
||||||
|
// 标签自动布局,全局过渡动画等特性
|
||||||
|
import { LabelLayout, UniversalTransition } from "echarts/features";
|
||||||
|
|
||||||
|
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
|
||||||
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
|
|
||||||
|
// 注册必须的组件
|
||||||
|
echarts.use([
|
||||||
|
TitleComponent,
|
||||||
|
TooltipComponent,
|
||||||
|
GridComponent,
|
||||||
|
DatasetComponent,
|
||||||
|
TransformComponent,
|
||||||
|
LegendComponent,
|
||||||
|
// BarChart,
|
||||||
|
LabelLayout,
|
||||||
|
UniversalTransition,
|
||||||
|
CanvasRenderer,
|
||||||
|
// LineChart,
|
||||||
|
PieChart,
|
||||||
|
]);
|
||||||
|
|
||||||
export default function(dom: any, theme: any = null, option: any) {
|
export default function(dom: any, theme: any = null, option: any) {
|
||||||
let chart = echarts.init(dom, theme);
|
let chart = echarts.init(dom, theme);
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,360 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="in-coder-panel">
|
|
||||||
<textarea ref="textarea"></textarea>
|
|
||||||
<el-select v-if="canChangeMode" class="code-mode-select" v-model="mode" @change="changeMode">
|
|
||||||
<el-option v-for="mode in modes" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref, nextTick, toRefs, reactive, watch, onMounted, defineComponent } from 'vue';
|
|
||||||
// 引入全局实例
|
|
||||||
import _CodeMirror from 'codemirror';
|
|
||||||
|
|
||||||
// 核心样式
|
|
||||||
import 'codemirror/lib/codemirror.css';
|
|
||||||
// 引入主题后还需要在 options 中指定主题才会生效
|
|
||||||
import 'codemirror/theme/cobalt.css';
|
|
||||||
import 'codemirror/addon/selection/active-line.js';
|
|
||||||
// 匹配括号
|
|
||||||
import 'codemirror/addon/edit/matchbrackets.js';
|
|
||||||
import 'codemirror/addon/selection/active-line';
|
|
||||||
import 'codemirror/addon/comment/comment';
|
|
||||||
|
|
||||||
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
|
|
||||||
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
|
|
||||||
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
|
|
||||||
import 'codemirror/mode/yaml/yaml.js';
|
|
||||||
import 'codemirror/mode/dockerfile/dockerfile.js';
|
|
||||||
import 'codemirror/mode/nginx/nginx.js';
|
|
||||||
import 'codemirror/mode/javascript/javascript.js';
|
|
||||||
import 'codemirror/mode/css/css.js';
|
|
||||||
import 'codemirror/mode/xml/xml.js';
|
|
||||||
import 'codemirror/mode/markdown/markdown.js';
|
|
||||||
import 'codemirror/mode/python/python.js';
|
|
||||||
import 'codemirror/mode/shell/shell.js';
|
|
||||||
import 'codemirror/mode/sql/sql.js';
|
|
||||||
import 'codemirror/mode/vue/vue.js';
|
|
||||||
import 'codemirror/mode/textile/textile.js';
|
|
||||||
import 'codemirror/addon/hint/show-hint.css';
|
|
||||||
import 'codemirror/addon/hint/show-hint.js';
|
|
||||||
|
|
||||||
import { ElOption, ElSelect } from 'element-plus';
|
|
||||||
|
|
||||||
// 尝试获取全局实例
|
|
||||||
const CodeMirror = (window as any).CodeMirror || _CodeMirror;
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'CodeMirror',
|
|
||||||
components: {
|
|
||||||
ElOption,
|
|
||||||
ElSelect,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
language: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: '500px',
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: 'auto',
|
|
||||||
},
|
|
||||||
canChangeMode: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Object,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props: any, { emit }) {
|
|
||||||
let { modelValue, language } = toRefs(props);
|
|
||||||
const textarea: any = ref(null);
|
|
||||||
// 编辑器实例
|
|
||||||
let coder = null as any;
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
coder: null as any,
|
|
||||||
content: '',
|
|
||||||
// 默认的语法类型
|
|
||||||
mode: 'x-sh',
|
|
||||||
// 默认配置
|
|
||||||
options: {
|
|
||||||
// 缩进格式
|
|
||||||
tabSize: 2,
|
|
||||||
// 主题,对应主题库 JS 需要提前引入
|
|
||||||
theme: 'cobalt',
|
|
||||||
// 显示行号
|
|
||||||
lineNumbers: true,
|
|
||||||
line: true,
|
|
||||||
indentWithTabs: true,
|
|
||||||
smartIndent: true,
|
|
||||||
matchBrackets: true,
|
|
||||||
autofocus: true,
|
|
||||||
styleSelectedText: true,
|
|
||||||
styleActiveLine: true, // 高亮选中行
|
|
||||||
foldGutter: true, // 块槽
|
|
||||||
// extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
|
|
||||||
hintOptions: {
|
|
||||||
// 当匹配只有一项的时候是否自动补全
|
|
||||||
completeSingle: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// 支持切换的语法高亮类型,对应 JS 已经提前引入
|
|
||||||
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
|
|
||||||
modes: [
|
|
||||||
{
|
|
||||||
value: 'x-sh',
|
|
||||||
label: 'Shell',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-yaml',
|
|
||||||
label: 'Yaml',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-dockerfile',
|
|
||||||
label: 'Dockerfile',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-nginx-conf',
|
|
||||||
label: 'Nginx',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'html',
|
|
||||||
label: 'XML/HTML',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-python',
|
|
||||||
label: 'Python',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-sql',
|
|
||||||
label: 'SQL',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'css',
|
|
||||||
label: 'CSS',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'javascript',
|
|
||||||
label: 'Javascript',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-java',
|
|
||||||
label: 'Java',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-vue',
|
|
||||||
label: 'Vue',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'markdown',
|
|
||||||
label: 'Markdown',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'text/x-textile',
|
|
||||||
label: 'text',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(newValue) => {
|
|
||||||
handerCodeChange(newValue);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// watch(
|
|
||||||
// () => props.options,
|
|
||||||
// (newValue, oldValue) => {
|
|
||||||
// for (const key in newValue) {
|
|
||||||
// coder.setOption(key, newValue[key]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
const init = () => {
|
|
||||||
if (props.options) {
|
|
||||||
state.options = props.options;
|
|
||||||
}
|
|
||||||
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
|
|
||||||
coder = CodeMirror.fromTextArea(textarea.value, state.options);
|
|
||||||
coder.setValue(modelValue.value || state.content);
|
|
||||||
|
|
||||||
// 支持双向绑定
|
|
||||||
coder.on('change', (coder: any) => {
|
|
||||||
state.content = coder.getDoc().getValue();
|
|
||||||
emit('update:modelValue', state.content);
|
|
||||||
});
|
|
||||||
|
|
||||||
coder.on('inputRead', (instance: any, changeObj: any) => {
|
|
||||||
if (/^[a-zA-Z]/.test(changeObj.text[0])) {
|
|
||||||
instance.showHint();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
coder.setSize(props.width, props.height);
|
|
||||||
// editor.setSize('width','height');
|
|
||||||
|
|
||||||
// 修改编辑器的语法配置
|
|
||||||
setMode(language.value);
|
|
||||||
|
|
||||||
[
|
|
||||||
'scroll',
|
|
||||||
'changes',
|
|
||||||
'beforeChange',
|
|
||||||
'cursorActivity',
|
|
||||||
'keyHandled',
|
|
||||||
'inputRead',
|
|
||||||
'electricInput',
|
|
||||||
'beforeSelectionChange',
|
|
||||||
'viewportChange',
|
|
||||||
'swapDoc',
|
|
||||||
'gutterClick',
|
|
||||||
'gutterContextMenu',
|
|
||||||
'focus',
|
|
||||||
'blur',
|
|
||||||
'refresh',
|
|
||||||
'optionChange',
|
|
||||||
'scrollCursorIntoView',
|
|
||||||
'update',
|
|
||||||
].forEach((event) => {
|
|
||||||
// 循环事件,并兼容 run-time 事件命名
|
|
||||||
coder.on(event, (...args: any) => {
|
|
||||||
// console.log('当有事件触发了', event, args);
|
|
||||||
emit(event, ...args);
|
|
||||||
const lowerCaseEvent = event.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
||||||
if (lowerCaseEvent !== event) {
|
|
||||||
emit(lowerCaseEvent, ...args);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
state.coder = coder;
|
|
||||||
// 不加无法显示内容,需点击后才可显示
|
|
||||||
refresh();
|
|
||||||
};
|
|
||||||
|
|
||||||
const refresh = () => {
|
|
||||||
nextTick(() => {
|
|
||||||
coder.refresh();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 设置模式
|
|
||||||
const setMode = (val: string) => {
|
|
||||||
if (val) {
|
|
||||||
// 获取具体的语法类型对象
|
|
||||||
let modeObj = getLanguage(val);
|
|
||||||
// 判断父容器传入的语法是否被支持
|
|
||||||
if (modeObj) {
|
|
||||||
state.mode = modeObj.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 修改编辑器的语法配置
|
|
||||||
coder.setOption('mode', `text/${state.mode}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取当前语法类型
|
|
||||||
const getLanguage = (language: string) => {
|
|
||||||
// 在支持的语法类型列表中寻找传入的语法类型
|
|
||||||
return state.modes.find((mode: any) => {
|
|
||||||
// 所有的值都忽略大小写,方便比较
|
|
||||||
let currentLanguage = language.toLowerCase();
|
|
||||||
let currentLabel = mode.label.toLowerCase();
|
|
||||||
let currentValue = mode.value.toLowerCase();
|
|
||||||
|
|
||||||
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
|
|
||||||
return currentLabel === currentLanguage || currentValue === currentLanguage;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 更改模式
|
|
||||||
const changeMode = (val: string) => {
|
|
||||||
setMode(val);
|
|
||||||
// 获取修改后的语法
|
|
||||||
let label = (getLanguage(val) as any).label.toLowerCase();
|
|
||||||
|
|
||||||
// 允许父容器通过以下函数监听当前的语法值
|
|
||||||
emit('language-change', label);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handerCodeChange = (newVal: string) => {
|
|
||||||
const cm_value = coder.getValue();
|
|
||||||
if (newVal !== cm_value) {
|
|
||||||
const scrollInfo = coder.getScrollInfo();
|
|
||||||
coder.setValue(newVal);
|
|
||||||
state.content = newVal;
|
|
||||||
coder.scrollTo(scrollInfo.left, scrollInfo.top);
|
|
||||||
refresh();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
textarea,
|
|
||||||
changeMode,
|
|
||||||
refresh,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.in-coder-panel {
|
|
||||||
flex-grow: 1;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
.CodeMirror {
|
|
||||||
flex-grow: 1;
|
|
||||||
z-index: 1;
|
|
||||||
.CodeMirror-code {
|
|
||||||
line-height: 19px;
|
|
||||||
}
|
|
||||||
font-family: 'JetBrainsMono';
|
|
||||||
}
|
|
||||||
.code-mode-select {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
right: 10px;
|
|
||||||
top: 10px;
|
|
||||||
max-width: 130px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.CodeMirror-hints {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 10000;
|
|
||||||
overflow: hidden;
|
|
||||||
list-style: none;
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
padding: 2px;
|
|
||||||
|
|
||||||
-webkit-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
||||||
-moz-box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
||||||
box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid silver;
|
|
||||||
|
|
||||||
background: white;
|
|
||||||
font-size: 90%;
|
|
||||||
font-family: 'JetBrainsMono';
|
|
||||||
|
|
||||||
max-height: 20em;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import _CodeMirror from 'codemirror'
|
|
||||||
import codemirror from './codemirror.vue'
|
|
||||||
|
|
||||||
const CodeMirror = window.CodeMirror || _CodeMirror
|
|
||||||
const install = (Vue, config) => {
|
|
||||||
if (config) {
|
|
||||||
if (config.options) {
|
|
||||||
codemirror.props.globalOptions.default = () => config.options
|
|
||||||
}
|
|
||||||
if (config.events) {
|
|
||||||
codemirror.props.globalEvents.default = () => config.events
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vue.component(codemirror.name, codemirror)
|
|
||||||
}
|
|
||||||
|
|
||||||
const VueCodemirror = { CodeMirror, codemirror, install }
|
|
||||||
|
|
||||||
export default VueCodemirror
|
|
||||||
export { CodeMirror, codemirror, install }
|
|
||||||
132
mayfly_go_web/src/components/contextmenu/index.vue
Normal file
132
mayfly_go_web/src/components/contextmenu/index.vue
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<template>
|
||||||
|
<transition name="el-zoom-in-center">
|
||||||
|
<div aria-hidden="true" class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu" role="tooltip"
|
||||||
|
data-popper-placement="bottom" :style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`" :key="Math.random()"
|
||||||
|
v-show="state.isShow">
|
||||||
|
<ul class="el-dropdown-menu">
|
||||||
|
<template v-for="(v, k) in state.dropdownList">
|
||||||
|
<li class="el-dropdown-menu__item" aria-disabled="false" tabindex="-1" :key="k" v-if="!v.affix"
|
||||||
|
@click="onCurrentContextmenuClick(v.contextMenuClickId)">
|
||||||
|
<SvgIcon :name="v.icon" />
|
||||||
|
<span>{{ v.txt }}</span>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
<div class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
||||||
|
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||||
|
|
||||||
|
// 定义父组件传过来的值
|
||||||
|
const props = defineProps({
|
||||||
|
dropdown: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义子组件向父组件传值/事件
|
||||||
|
const emit = defineEmits(['currentContextmenuClick']);
|
||||||
|
|
||||||
|
// 定义变量内容
|
||||||
|
const state = reactive({
|
||||||
|
isShow: false,
|
||||||
|
dropdownList: [
|
||||||
|
{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' },
|
||||||
|
],
|
||||||
|
item: {} as any,
|
||||||
|
arrowLeft: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 父级传过来的坐标 x,y 值
|
||||||
|
const dropdowns = computed(() => {
|
||||||
|
// 117 为 `Dropdown 下拉菜单` 的宽度
|
||||||
|
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
|
||||||
|
return {
|
||||||
|
x: document.documentElement.clientWidth - 117 - 5,
|
||||||
|
y: props.dropdown.y,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return props.dropdown;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 当前项菜单点击
|
||||||
|
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
|
||||||
|
emit('currentContextmenuClick', { id: contextMenuClickId, item: state.item });
|
||||||
|
};
|
||||||
|
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||||
|
const openContextmenu = (item: any) => {
|
||||||
|
state.item = item;
|
||||||
|
closeContextmenu();
|
||||||
|
setTimeout(() => {
|
||||||
|
state.isShow = true;
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
// 关闭右键菜单
|
||||||
|
const closeContextmenu = () => {
|
||||||
|
state.isShow = false;
|
||||||
|
};
|
||||||
|
// 监听页面监听进行右键菜单的关闭
|
||||||
|
onMounted(() => {
|
||||||
|
document.body.addEventListener('click', closeContextmenu);
|
||||||
|
});
|
||||||
|
// 页面卸载时,移除右键菜单监听事件
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.body.removeEventListener('click', closeContextmenu);
|
||||||
|
});
|
||||||
|
// 监听下拉菜单位置
|
||||||
|
watch(
|
||||||
|
() => props.dropdown,
|
||||||
|
({ x }) => {
|
||||||
|
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
|
||||||
|
else state.arrowLeft = 10;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => props.items,
|
||||||
|
(x: any) => {
|
||||||
|
state.dropdownList = x
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 暴露变量
|
||||||
|
defineExpose({
|
||||||
|
openContextmenu,
|
||||||
|
closeContextmenu,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.custom-contextmenu {
|
||||||
|
transform-origin: center top;
|
||||||
|
z-index: 2190;
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
.el-dropdown-menu__item {
|
||||||
|
font-size: 12px !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
<template>
|
<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,133 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div ref="jsoneditorVue" :style="{ height: height, width: width }"></div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref, toRefs, reactive, nextTick, watch, onMounted, onUnmounted, defineComponent } from 'vue';
|
|
||||||
import JSONEditor from 'jsoneditor';
|
|
||||||
import 'jsoneditor/dist/jsoneditor.min.css';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'JsonEdit',
|
|
||||||
components: {},
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: [String, Object],
|
|
||||||
},
|
|
||||||
height: {
|
|
||||||
type: String,
|
|
||||||
default: '500px',
|
|
||||||
},
|
|
||||||
width: {
|
|
||||||
type: String,
|
|
||||||
default: 'auto',
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Object,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
currentMode: {
|
|
||||||
type: String,
|
|
||||||
default: 'tree',
|
|
||||||
},
|
|
||||||
modeList: {
|
|
||||||
type: Array,
|
|
||||||
default() {
|
|
||||||
return ['tree', 'code', 'form', 'text', 'view'];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props: any, { emit }) {
|
|
||||||
let { modelValue, options, modeList, currentMode } = toRefs(props);
|
|
||||||
|
|
||||||
const jsoneditorVue = ref(null)
|
|
||||||
// 编辑器实例
|
|
||||||
let editor = null as any;
|
|
||||||
// 值类型
|
|
||||||
let valueType = 'string';
|
|
||||||
// 是否内部改变(即onChange事件双向绑定),内部改变则不需要重新赋值给editor
|
|
||||||
let internalChange = false;
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
height: '500px',
|
|
||||||
width: 'auto',
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
state.width = props.width;
|
|
||||||
state.height = props.height;
|
|
||||||
|
|
||||||
init();
|
|
||||||
setJson(modelValue.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
editor?.destroy();
|
|
||||||
editor = null;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(newValue) => {
|
|
||||||
if (!editor) {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
setJson(newValue);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const setJson = (value: any) => {
|
|
||||||
if (internalChange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (typeof value == 'string') {
|
|
||||||
valueType = 'string';
|
|
||||||
editor.set(JSON.parse(value));
|
|
||||||
} else {
|
|
||||||
valueType = 'object';
|
|
||||||
editor.set(value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onChange = () => {
|
|
||||||
try {
|
|
||||||
const json = editor.get();
|
|
||||||
if (valueType == 'string') {
|
|
||||||
emit('update:modelValue', JSON.stringify(json));
|
|
||||||
} else {
|
|
||||||
emit('update:modelValue', json);
|
|
||||||
}
|
|
||||||
emit('onChange', json);
|
|
||||||
internalChange = true;
|
|
||||||
nextTick(() => {
|
|
||||||
internalChange = false;
|
|
||||||
});
|
|
||||||
} catch (error) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const init = () => {
|
|
||||||
console.log('init json editor');
|
|
||||||
const finalOptions = {
|
|
||||||
...options.value,
|
|
||||||
mode: currentMode.value,
|
|
||||||
modes: modeList.value,
|
|
||||||
onChange,
|
|
||||||
};
|
|
||||||
editor = new JSONEditor(jsoneditorVue.value, finalOptions);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
jsoneditorVue,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
div.jsoneditor-menu a.jsoneditor-poweredBy {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
280
mayfly_go_web/src/components/monaco/MonacoEditor.vue
Normal file
280
mayfly_go_web/src/components/monaco/MonacoEditor.vue
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
<template>
|
||||||
|
<div class="monaco-editor" style="border: 1px solid #ccc;">
|
||||||
|
<div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
|
||||||
|
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
|
||||||
|
<el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, toRefs, reactive, onMounted, onBeforeUnmount } from 'vue';
|
||||||
|
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
|
||||||
|
import * as monaco from 'monaco-editor';
|
||||||
|
import { editor, languages } from 'monaco-editor';
|
||||||
|
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
|
||||||
|
// 主题仓库 https://github.com/brijeshb42/monaco-themes
|
||||||
|
// 主题例子 https://editor.bitwiser.in/
|
||||||
|
// import Monokai from 'monaco-themes/themes/Monokai.json'
|
||||||
|
// import Active4D from 'monaco-themes/themes/Active4D.json'
|
||||||
|
// import ahe from 'monaco-themes/themes/All Hallows Eve.json'
|
||||||
|
// import bop from 'monaco-themes/themes/Birds of Paradise.json'
|
||||||
|
// import krTheme from 'monaco-themes/themes/krTheme.json'
|
||||||
|
// import Dracula from 'monaco-themes/themes/Dracula.json'
|
||||||
|
import SolarizedLight from 'monaco-themes/themes/Solarized-light.json'
|
||||||
|
import { language as shellLan } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
|
||||||
|
import { ElOption, ElSelect } from 'element-plus';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
language: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: String,
|
||||||
|
default: '500px',
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto',
|
||||||
|
},
|
||||||
|
canChangeMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
//定义事件
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const languageArr = [
|
||||||
|
{
|
||||||
|
value: 'shell',
|
||||||
|
label: 'Shell',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'json',
|
||||||
|
label: 'JSON',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'yaml',
|
||||||
|
label: 'Yaml',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'dockerfile',
|
||||||
|
label: 'Dockerfile',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'html',
|
||||||
|
label: 'XML/HTML',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'python',
|
||||||
|
label: 'Python',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'sql',
|
||||||
|
label: 'SQL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'css',
|
||||||
|
label: 'CSS',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'javascript',
|
||||||
|
label: 'Javascript',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'java',
|
||||||
|
label: 'Java',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'markdown',
|
||||||
|
label: 'Markdown',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'text',
|
||||||
|
label: 'text',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
language: 'shell',
|
||||||
|
theme: 'SolarizedLight',
|
||||||
|
automaticLayout: true, //自适应宽高布局
|
||||||
|
foldingStrategy: 'indentation',//代码可分小段折叠
|
||||||
|
roundedSelection: false, // 禁用选择文本背景的圆角
|
||||||
|
matchBrackets: 'near',
|
||||||
|
linkedEditing: true,
|
||||||
|
cursorBlinking: 'smooth',// 光标闪烁样式
|
||||||
|
mouseWheelZoom: true, // 在按住Ctrl键的同时使用鼠标滚轮时,在编辑器中缩放字体
|
||||||
|
overviewRulerBorder: false, // 不要滚动条的边框
|
||||||
|
tabSize: 4, // tab 缩进长度
|
||||||
|
// fontFamily: 'JetBrainsMono', // 字体 暂时不要设置,否则光标容易错位
|
||||||
|
fontWeight: 'bold',
|
||||||
|
// fontSize: 12,
|
||||||
|
// letterSpacing: 1, 字符间距
|
||||||
|
// quickSuggestions:false, // 禁用代码提示
|
||||||
|
minimap: {
|
||||||
|
enabled: false, // 不要小地图
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
languageMode: 'shell',
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
languageMode,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
state.languageMode = props.language;
|
||||||
|
initMonacoEditorIns();
|
||||||
|
setEditorValue(props.modelValue);
|
||||||
|
registerCompletionItemProvider();
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (monacoEditorIns) {
|
||||||
|
monacoEditorIns.dispose();
|
||||||
|
}
|
||||||
|
if (completionItemProvider) {
|
||||||
|
completionItemProvider.dispose();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (newValue: any) => {
|
||||||
|
if (!monacoEditorIns.hasTextFocus()) {
|
||||||
|
state.languageMode = props.language;
|
||||||
|
monacoEditorIns?.setValue(newValue);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.language, (newValue: any) => {
|
||||||
|
changeLanguage(newValue);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const monacoTextarea: any = ref(null);
|
||||||
|
|
||||||
|
let monacoEditorIns: editor.IStandaloneCodeEditor = null as any;
|
||||||
|
let completionItemProvider: any = null;
|
||||||
|
|
||||||
|
self.MonacoEnvironment = {
|
||||||
|
getWorker(_: any, label: string) {
|
||||||
|
if (label === 'json') {
|
||||||
|
return new JsonWorker()
|
||||||
|
}
|
||||||
|
return new EditorWorker();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initMonacoEditorIns = () => {
|
||||||
|
console.log('初始化monaco编辑器')
|
||||||
|
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
|
||||||
|
// 初始化一些主题
|
||||||
|
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
|
||||||
|
options.language = state.languageMode;
|
||||||
|
// 从localStorage中获取,通过store可能存在父子组件都使用store报错
|
||||||
|
options.theme = JSON.parse(localStorage.getItem('themeConfig') as string).editorTheme || 'vs';
|
||||||
|
monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(options, props.options as any));
|
||||||
|
|
||||||
|
// 监听内容改变,双向绑定
|
||||||
|
monacoEditorIns.onDidChangeModelContent(() => {
|
||||||
|
emit('update:modelValue', monacoEditorIns.getModel()?.getValue());
|
||||||
|
})
|
||||||
|
|
||||||
|
// 动态设置主题
|
||||||
|
// monaco.editor.setTheme('hc-black');
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeLanguage = (value: any) => {
|
||||||
|
console.log('change lan');
|
||||||
|
// 获取当前的文档模型
|
||||||
|
let oldModel = monacoEditorIns.getModel()
|
||||||
|
if (!oldModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 创建一个新的文档模型
|
||||||
|
let newModel = monaco.editor.createModel(oldModel.getValue(), value)
|
||||||
|
// 设置成新的
|
||||||
|
monacoEditorIns.setModel(newModel)
|
||||||
|
// 销毁旧的模型
|
||||||
|
if (oldModel) {
|
||||||
|
oldModel.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
registerCompletionItemProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
const setEditorValue = (value: any) => {
|
||||||
|
monacoEditorIns.getModel()?.setValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册联想补全提示
|
||||||
|
*/
|
||||||
|
const registerCompletionItemProvider = () => {
|
||||||
|
if (completionItemProvider) {
|
||||||
|
completionItemProvider.dispose();
|
||||||
|
}
|
||||||
|
if (state.languageMode == 'shell') {
|
||||||
|
registeShell()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const registeShell = () => {
|
||||||
|
completionItemProvider = monaco.languages.registerCompletionItemProvider('shell', {
|
||||||
|
provideCompletionItems: async () => {
|
||||||
|
let suggestions: languages.CompletionItem[] = []
|
||||||
|
shellLan.keywords.forEach((item: any) => {
|
||||||
|
suggestions.push({
|
||||||
|
label: item,
|
||||||
|
kind: monaco.languages.CompletionItemKind.Keyword,
|
||||||
|
insertText: item,
|
||||||
|
} as any);
|
||||||
|
})
|
||||||
|
shellLan.builtins.forEach((item: any) => {
|
||||||
|
suggestions.push({
|
||||||
|
label: item,
|
||||||
|
kind: monaco.languages.CompletionItemKind.Property,
|
||||||
|
insertText: item,
|
||||||
|
} as any);
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
suggestions: suggestions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const format = () => {
|
||||||
|
/*
|
||||||
|
触发自动格式化;
|
||||||
|
*/
|
||||||
|
monacoEditorIns.trigger('', 'editor.action.formatDocument', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ format })
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.monaco-editor {
|
||||||
|
.code-mode-select {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
|
max-width: 130px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,24 @@
|
|||||||
<script lang="ts">
|
<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/index.vue'),
|
|
||||||
'Personal': () => import('@/views/personal/index.vue'),
|
|
||||||
// machine
|
|
||||||
"MachineList": () => import('@/views/ops/machine'),
|
|
||||||
// sys
|
|
||||||
"ResourceList": () => import('@/views/system/resource'),
|
|
||||||
"RoleList": () => import('@/views/system/role'),
|
|
||||||
"AccountList": () => import('@/views/system/account'),
|
|
||||||
"SyslogList": () => import('@/views/system/syslog/SyslogList.vue'),
|
|
||||||
"ConfigList": () => import('@/views/system/config/ConfigList.vue'),
|
|
||||||
|
|
||||||
// tag
|
|
||||||
"TagTreeList": () => import('@/views/ops/tag/TagTreeList.vue'),
|
|
||||||
"TeamList": () => import('@/views/ops/tag/TeamList.vue'),
|
|
||||||
// db
|
|
||||||
"DbList": () => import('@/views/ops/db/DbList.vue'),
|
|
||||||
"SqlExec": () => import('@/views/ops/db'),
|
|
||||||
// redis
|
|
||||||
"RedisList": () => import('@/views/ops/redis'),
|
|
||||||
"DataOperation": () => import('@/views/ops/redis/DataOperation.vue'),
|
|
||||||
// mongo
|
|
||||||
"MongoDataOp": () => import('@/views/ops/mongo/MongoDataOp.vue'),
|
|
||||||
// redis
|
|
||||||
"MongoList": () => import('@/views/ops/mongo/MongoList.vue'),
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,25 @@
|
|||||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
import { 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,22 +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';
|
|
||||||
|
|
||||||
export const key: InjectionKey<Store<RootStateTypes>> = Symbol();
|
// 创建
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
export const store = createStore<RootStateTypes>({
|
// 导出
|
||||||
modules: {
|
export default pinia;
|
||||||
themeConfig,
|
|
||||||
routesList,
|
|
||||||
keepAliveNames,
|
|
||||||
userInfos,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
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,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,16 @@ 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',
|
||||||
|
|
||||||
|
|
||||||
/* 后端控制路由
|
/* 后端控制路由
|
||||||
@@ -132,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,8 +328,20 @@ body,
|
|||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fr {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.search-form {
|
.search-form {
|
||||||
.el-form-item {
|
.el-form-item {
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-table-z-index-inherit .el-table .el-table__cell {
|
||||||
|
z-index: inherit !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f12 {
|
||||||
|
font-size: 12px
|
||||||
|
}
|
||||||
@@ -3,83 +3,27 @@
|
|||||||
.icon-selector-popper {
|
.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,35 +54,24 @@ export interface ThemeConfigState {
|
|||||||
terminalBackground: string;
|
terminalBackground: string;
|
||||||
terminalCursor: string;
|
terminalCursor: string;
|
||||||
terminalFontSize: number;
|
terminalFontSize: number;
|
||||||
terminalFontWeight: string;
|
terminalFontWeight: string | any;
|
||||||
|
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 RequestOldRoutesState {
|
|
||||||
// requestOldRoutes: Array<object>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 主接口(顶级类型声明)
|
|
||||||
export interface RootStateTypes {
|
|
||||||
themeConfig: ThemeConfigState;
|
|
||||||
routesList: RoutesListState;
|
|
||||||
keepAliveNames: KeepAliveNamesState;
|
|
||||||
userInfos: UserInfosState;
|
|
||||||
// requestOldRoutes: RequestOldRoutesState;
|
|
||||||
}
|
}
|
||||||
14
mayfly_go_web/src/types/shim.d.ts
vendored
Normal file
14
mayfly_go_web/src/types/shim.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
import {IDisposable} from 'monaco-editor';
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
completionItemProvider?: IDisposable | undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 申明外部 npm 插件模块
|
||||||
|
declare module 'sql-formatter';
|
||||||
|
declare module 'jsoneditor';
|
||||||
|
declare module 'asciinema-player';
|
||||||
|
declare module 'monaco-editor';
|
||||||
|
declare module 'vue-grid-layout';
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// 声明一个模块,防止引入文件时报错
|
||||||
declare module '*.json';
|
declare module '*.json';
|
||||||
declare module '*.png';
|
declare module '*.png';
|
||||||
declare module '*.jpg';
|
declare module '*.jpg';
|
||||||
@@ -4,16 +4,17 @@
|
|||||||
<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>
|
<div class="home-card-first-right-title">{{ `${currentTime}, ${userInfo.username}`
|
||||||
|
}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList" :key="k">
|
<el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList as any" :key="k">
|
||||||
<div @click="toPage(v)" class="home-card-item home-card-item-box" :style="{ background: v.color }">
|
<div @click="toPage(v)" class="home-card-item home-card-item-box" :style="{ background: v.color }">
|
||||||
<div class="home-card-item-flex">
|
<div class="home-card-item-flex">
|
||||||
<div class="home-card-item-title pb3">{{ v.title }}</div>
|
<div class="home-card-item-title pb3">{{ v.title }}</div>
|
||||||
@@ -26,20 +27,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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';
|
||||||
export default {
|
import { storeToRefs } from 'pinia';
|
||||||
name: 'HomePage',
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
setup() {
|
|
||||||
// const { proxy } = getCurrentInstance() as any;
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const store = useStore();
|
const { userInfo } = storeToRefs(useUserInfo());
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
topCardItemList: [
|
topCardItemList: [
|
||||||
{
|
{
|
||||||
@@ -65,6 +65,10 @@ export default {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
topCardItemList,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
// 当前时间提示语
|
// 当前时间提示语
|
||||||
const currentTime = computed(() => {
|
const currentTime = computed(() => {
|
||||||
return formatAxis(new Date());
|
return formatAxis(new Date());
|
||||||
@@ -112,25 +116,12 @@ export default {
|
|||||||
// initHomeLaboratory();
|
// initHomeLaboratory();
|
||||||
// initHomeOvertime();
|
// initHomeOvertime();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取用户信息 vuex
|
|
||||||
const getUserInfos = computed(() => {
|
|
||||||
return store.state.userInfos.userInfos;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
getUserInfos,
|
|
||||||
currentTime,
|
|
||||||
toPage,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.home-container {
|
.home-container {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
.home-card-item {
|
.home-card-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 103px;
|
height: 103px;
|
||||||
@@ -138,16 +129,19 @@ export default {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-card-item-box {
|
.home-card-item-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
i {
|
i {
|
||||||
right: 0px !important;
|
right: 0px !important;
|
||||||
@@ -155,6 +149,7 @@ export default {
|
|||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
i {
|
i {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -10px;
|
right: -10px;
|
||||||
@@ -163,48 +158,59 @@ export default {
|
|||||||
transform: rotate(-30deg);
|
transform: rotate(-30deg);
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-card-item-flex {
|
.home-card-item-flex {
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
||||||
.home-card-item-title,
|
.home-card-item-title,
|
||||||
.home-card-item-tip {
|
.home-card-item-tip {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-card-item-title-num {
|
.home-card-item-title-num {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-card-item-tip-num {
|
.home-card-item-tip-num {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-card-first {
|
.home-card-first {
|
||||||
background: white;
|
background: white;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid #ebeef5;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
border: 2px solid var(--color-primary-light-5);
|
border: 2px solid var(--color-primary-light-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-card-first-right {
|
.home-card-first-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.home-card-first-right-msg {
|
.home-card-first-right-msg {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-monitor {
|
.home-monitor {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
|
||||||
.flex-warp-item {
|
.flex-warp-item {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
.flex-warp-item-box {
|
.flex-warp-item-box {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
@@ -212,19 +218,24 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-warning-card {
|
.home-warning-card {
|
||||||
height: 292px;
|
height: 292px;
|
||||||
|
|
||||||
::v-deep(.el-card) {
|
::v-deep(.el-card) {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-dynamic {
|
.home-dynamic {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
|
||||||
.home-dynamic-item {
|
.home-dynamic-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
.home-dynamic-item-line {
|
.home-dynamic-item-line {
|
||||||
i {
|
i {
|
||||||
@@ -232,20 +243,24 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-dynamic-item-left {
|
.home-dynamic-item-left {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
.home-dynamic-item-left-time1 {
|
|
||||||
}
|
.home-dynamic-item-left-time1 {}
|
||||||
|
|
||||||
.home-dynamic-item-left-time2 {
|
.home-dynamic-item-left-time2 {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-dynamic-item-line {
|
.home-dynamic-item-line {
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border-right: 2px dashed #dfdfdf;
|
border-right: 2px dashed #dfdfdf;
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -256,8 +271,10 @@ export default {
|
|||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-dynamic-item-right {
|
.home-dynamic-item-right {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
.home-dynamic-item-right-title {
|
.home-dynamic-item-right-title {
|
||||||
i {
|
i {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
@@ -270,6 +287,7 @@ export default {
|
|||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-dynamic-item-right-label {
|
.home-dynamic-item-right-label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: gray;
|
color: gray;
|
||||||
@@ -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,19 +2,12 @@
|
|||||||
<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 }"
|
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
|
||||||
:title="v.meta.title"
|
|
||||||
>
|
|
||||||
<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">
|
||||||
{{ v.meta.title && v.meta.title.length >= 4 ? v.meta.title.substr(0, 4) : v.meta.title }}
|
{{ v.meta.title && v.meta.title.length >= 4 ? v.meta.title.substr(0, 4) : v.meta.title }}
|
||||||
@@ -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.linkType != 1"
|
||||||
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.linkType == 2" />
|
||||||
: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.linkType == 1 && 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;
|
||||||
@@ -83,15 +69,7 @@ export default defineComponent({
|
|||||||
() => route.path,
|
() => route.path,
|
||||||
() => {
|
() => {
|
||||||
initCurrentRouteMeta(route.meta);
|
initCurrentRouteMeta(route.meta);
|
||||||
proxy.$refs.layoutScrollbarRef.wrap$.scrollTop = 0;
|
proxy.$refs.layoutScrollbarRef.wrapRef.scrollTop = 0;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return {
|
|
||||||
getThemeConfig,
|
|
||||||
initCurrentRouteMeta,
|
|
||||||
onGetCurrentRouteMeta,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-footer mt15" v-show="isDelayFooter">
|
<div class="layout-footer mt15" v-show="isDelayFooter">
|
||||||
<div class="layout-footer-warp">
|
<div class="layout-footer-warp">
|
||||||
<div>vue-next-admin,Made by lyt with ❤️</div>
|
<div>Made by mayfly with ❤️</div>
|
||||||
<div class="mt5">mayfly</div>
|
<div class="mt5">mayfly-go</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -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';
|
||||||
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>
|
||||||
@@ -1,42 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<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>{{ getThemeConfig.globalTitle }}</span>
|
<span>
|
||||||
|
{{ `${themeConfig.globalTitle}` }}
|
||||||
|
<sub><span style="font-size: 10px;color:goldenrod">{{ ` ${config.version}` }}</span></sub>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
|
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
|
||||||
<img src="@/assets/image/logo.svg" class="layout-logo-size-img" />
|
<img src="@/assets/image/logo.svg" class="layout-logo-size-img" />
|
||||||
</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';
|
||||||
export default {
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
name: 'layoutLogo',
|
import config from '@/common/config';
|
||||||
setup() {
|
import mittBus from '@/common/utils/mitt';
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const store = useStore();
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
// 获取布局配置信息
|
|
||||||
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 {
|
|
||||||
setShowLogo,
|
|
||||||
getThemeConfig,
|
|
||||||
onThemeConfigChange,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -52,26 +45,31 @@ export default {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
animation: logoAnimation 0.3s ease-in-out;
|
animation: logoAnimation 0.3s ease-in-out;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
span {
|
span {
|
||||||
color: var(--color-primary-light-2);
|
color: var(--color-primary-light-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-medium-img {
|
&-medium-img {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-logo-size {
|
.layout-logo-size {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
animation: logoAnimation 0.3s ease-in-out;
|
animation: logoAnimation 0.3s ease-in-out;
|
||||||
|
|
||||||
&-img {
|
&-img {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
img {
|
img {
|
||||||
animation: logoAnimation 0.3s ease-in-out;
|
animation: logoAnimation 0.3s ease-in-out;
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
@@ -95,7 +87,7 @@ export default defineComponent({
|
|||||||
// 当前菜单选中时
|
// 当前菜单选中时
|
||||||
const onHandleSelect = (item: any) => {
|
const onHandleSelect = (item: any) => {
|
||||||
let { path, redirect } = item;
|
let { path, redirect } = item;
|
||||||
if (item.meta.link && !item.meta.isIframe) window.open(item.meta.link);
|
if (item.meta.link && item.meta.linkType == 2) window.open(item.meta.link);
|
||||||
else if (redirect) router.push(redirect);
|
else if (redirect) router.push(redirect);
|
||||||
else router.push(path);
|
else router.push(path);
|
||||||
closeSearch();
|
closeSearch();
|
||||||
@@ -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,85 +1,101 @@
|
|||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<el-divider content-position="left">editor 设置</el-divider>
|
||||||
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
|
<div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
|
||||||
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
|
<el-select @change="setLocalThemeConfig" v-model="themeConfig.editorTheme" size="small"
|
||||||
|
style="width: 130px">
|
||||||
|
<el-option label="vs" value="vs"> </el-option>
|
||||||
|
<el-option label="vs-dark" value="vs-dark"> </el-option>
|
||||||
|
<el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 全局主题 -->
|
<!-- 全局主题 -->
|
||||||
<el-divider content-position="left">全局主题</el-divider>
|
<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">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>
|
||||||
|
|
||||||
@@ -88,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>
|
||||||
|
|
||||||
@@ -159,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>
|
||||||
@@ -212,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>
|
||||||
|
|
||||||
@@ -279,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>
|
||||||
@@ -312,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>
|
||||||
@@ -330,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>
|
||||||
@@ -345,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>
|
||||||
@@ -357,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>
|
||||||
@@ -365,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">
|
||||||
@@ -376,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>
|
||||||
@@ -384,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">
|
||||||
@@ -392,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>
|
||||||
@@ -402,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>
|
||||||
@@ -417,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、全局主题设置函数
|
||||||
@@ -448,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();
|
||||||
@@ -456,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、菜单 / 顶栏 --> 背景渐变函数
|
||||||
@@ -493,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'));
|
||||||
@@ -511,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();
|
||||||
@@ -556,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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -617,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);
|
||||||
@@ -638,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 = () => {
|
||||||
@@ -652,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();
|
||||||
});
|
});
|
||||||
@@ -664,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'));
|
||||||
@@ -712,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;
|
||||||
@@ -769,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;
|
||||||
@@ -805,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;
|
||||||
@@ -813,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;
|
||||||
@@ -832,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;
|
||||||
@@ -866,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="true" @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.username === '' ? 'test' : 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>
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default {
|
|||||||
};
|
};
|
||||||
// 前往通知中心点击
|
// 前往通知中心点击
|
||||||
const toMsgCenter = () => {
|
const toMsgCenter = () => {
|
||||||
// window.open('https://gitee.com/lyt-top/vue-next-admin');
|
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
onAllReadClick,
|
onAllReadClick,
|
||||||
|
|||||||
@@ -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';
|
||||||
|
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)的下标
|
||||||
@@ -117,40 +99,57 @@ export default {
|
|||||||
if (!to) {
|
if (!to) {
|
||||||
to = route;
|
to = route;
|
||||||
}
|
}
|
||||||
|
|
||||||
path = decodeURI(path);
|
path = decodeURI(path);
|
||||||
for (let tv of state.tagsViewList) {
|
for (let tv of state.tagsViewList) {
|
||||||
if (tv.fullPath === path) {
|
if (tv.fullPath === path) {
|
||||||
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 +173,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) => {
|
||||||
@@ -234,7 +233,7 @@ export default {
|
|||||||
};
|
};
|
||||||
// 鼠标滚轮滚动
|
// 鼠标滚轮滚动
|
||||||
const onHandleScroll = (e: any) => {
|
const onHandleScroll = (e: any) => {
|
||||||
proxy.$refs.scrollbarRef.$refs.wrap.scrollLeft += e.wheelDelta / 4;
|
proxy.$refs.scrollbarRef.$refs.wrapRef.scrollLeft += e.wheelDelta / 4;
|
||||||
};
|
};
|
||||||
// tagsView 横向滚动
|
// tagsView 横向滚动
|
||||||
const tagsViewmoveToCurrentTag = () => {
|
const tagsViewmoveToCurrentTag = () => {
|
||||||
@@ -251,7 +250,7 @@ export default {
|
|||||||
// 最后 li
|
// 最后 li
|
||||||
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
|
let liLast: any = tagsRefs.value[tagsRefs.value.length - 1];
|
||||||
// 当前滚动条的值
|
// 当前滚动条的值
|
||||||
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrap$;
|
let scrollRefs = proxy.$refs.scrollbarRef.$refs.wrapRef;
|
||||||
// 当前滚动条滚动宽度
|
// 当前滚动条滚动宽度
|
||||||
let scrollS = scrollRefs.scrollWidth;
|
let scrollS = scrollRefs.scrollWidth;
|
||||||
// 当前滚动条偏移宽度
|
// 当前滚动条偏移宽度
|
||||||
@@ -298,8 +297,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 +309,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 +362,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 +382,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 +400,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 +421,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-sub-menu :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-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 #title v-if="!val.meta.link || (val.meta.link && val.meta.isIframe)">
|
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
|
||||||
<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,
|
||||||
});
|
});
|
||||||
@@ -56,21 +58,21 @@ export default defineComponent({
|
|||||||
// 设置横向滚动条可以鼠标滚轮滚动
|
// 设置横向滚动条可以鼠标滚轮滚动
|
||||||
const onElMenuHorizontalScroll = (e: any) => {
|
const onElMenuHorizontalScroll = (e: any) => {
|
||||||
const eventDelta = e.wheelDelta || -e.deltaY * 40;
|
const eventDelta = e.wheelDelta || -e.deltaY * 40;
|
||||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft =
|
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft =
|
||||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft + eventDelta / 4;
|
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft + eventDelta / 4;
|
||||||
};
|
};
|
||||||
// 初始化数据,页面刷新时,滚动条滚动到对应位置
|
// 初始化数据,页面刷新时,滚动条滚动到对应位置
|
||||||
const initElMenuOffsetLeft = () => {
|
const initElMenuOffsetLeft = () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
|
let els: any = document.querySelector('.el-menu.el-menu--horizontal li.is-active');
|
||||||
if (!els) return false;
|
if (!els) return false;
|
||||||
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrap.scrollLeft = els.offsetLeft;
|
proxy.$refs.elMenuHorizontalScrollRef.$refs.wrapRef.scrollLeft = els.offsetLeft;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 设置页面当前路由高亮
|
// 设置页面当前路由高亮
|
||||||
const setCurrentRouterHighlight = (path: string) => {
|
const 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,8 +7,8 @@
|
|||||||
</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.linkType == 1)">
|
||||||
<SvgIcon :name="val.meta.icon"/>
|
<SvgIcon :name="val.meta.icon"/>
|
||||||
<span>{{ val.meta.title }}</span>
|
<span>{{ val.meta.title }}</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -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.linkType == 1)">
|
||||||
<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>
|
||||||
|
|||||||
@@ -2,37 +2,25 @@
|
|||||||
<div>
|
<div>
|
||||||
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
|
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable autocomplete="off">
|
<el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable
|
||||||
|
autocomplete="off">
|
||||||
</el-input>
|
</el-input>
|
||||||
</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" autocomplete="off" show-password>
|
<el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password"
|
||||||
|
autocomplete="off" @keyup.enter="login" show-password>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="useLoginCaptcha" prop="captcha">
|
<el-form-item v-if="isUseLoginCaptcha" prop="captcha">
|
||||||
<el-row :gutter="15">
|
<el-row :gutter="15">
|
||||||
<el-col :span="16">
|
<el-col :span="16">
|
||||||
<el-input
|
<el-input type="text" maxlength="6" placeholder="请输入验证码" prefix-icon="position"
|
||||||
type="text"
|
v-model="loginForm.captcha" clearable autocomplete="off" @keyup.enter="login"></el-input>
|
||||||
maxlength="6"
|
|
||||||
placeholder="请输入验证码"
|
|
||||||
prefix-icon="position"
|
|
||||||
v-model="loginForm.captcha"
|
|
||||||
clearable
|
|
||||||
autocomplete="off"
|
|
||||||
@keyup.enter="login"
|
|
||||||
></el-input>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<div class="login-content-code">
|
<div class="login-content-code">
|
||||||
<img
|
<img class="login-content-code-img" @click="getCaptcha" width="130px" height="40px"
|
||||||
class="login-content-code-img"
|
:src="captchaImage" style="cursor: pointer" />
|
||||||
@click="getCaptcha"
|
|
||||||
width="130px"
|
|
||||||
height="40px"
|
|
||||||
:src="captchaImage"
|
|
||||||
style="cursor: pointer"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -44,21 +32,20 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
|
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px"
|
||||||
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef" label-width="65px">
|
:destroy-on-close="true">
|
||||||
|
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef"
|
||||||
|
label-width="65px">
|
||||||
<el-form-item prop="username" label="用户名" required>
|
<el-form-item prop="username" label="用户名" required>
|
||||||
<el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
|
<el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="oldPassword" label="旧密码" required>
|
<el-form-item prop="oldPassword" label="旧密码" required>
|
||||||
<el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password" type="password"></el-input>
|
<el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password"
|
||||||
|
type="password"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="newPassword" label="新密码" required>
|
<el-form-item prop="newPassword" label="新密码" required>
|
||||||
<el-input
|
<el-input v-model.trim="changePwdDialog.form.newPassword" placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
|
||||||
v-model.trim="changePwdDialog.form.newPassword"
|
type="password" autocomplete="new-password"></el-input>
|
||||||
placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
|
|
||||||
type="password"
|
|
||||||
autocomplete="new-password"
|
|
||||||
></el-input>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
@@ -72,30 +59,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { nextTick, onMounted, ref, toRefs, reactive, defineComponent, 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 = {
|
||||||
|
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||||
|
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||||
|
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
||||||
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'AccountLogin',
|
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const loginFormRef: any = ref(null);
|
const loginFormRef: any = ref(null);
|
||||||
const changePwdFormRef: any = ref(null);
|
const changePwdFormRef: any = ref(null);
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
useLoginCaptcha: false,
|
isUseLoginCaptcha: false,
|
||||||
captchaImage: '',
|
captchaImage: '',
|
||||||
loginForm: {
|
loginForm: {
|
||||||
username: '',
|
username: '',
|
||||||
@@ -121,20 +110,23 @@ export default defineComponent({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
|
||||||
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
|
||||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
|
||||||
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
|
|
||||||
},
|
|
||||||
loading: {
|
loading: {
|
||||||
signIn: false,
|
signIn: false,
|
||||||
changePwd: false,
|
changePwd: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
isUseLoginCaptcha,
|
||||||
|
captchaImage,
|
||||||
|
loginForm,
|
||||||
|
changePwdDialog,
|
||||||
|
loading,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
state.useLoginCaptcha = await useLoginCaptcha();
|
state.isUseLoginCaptcha = await useLoginCaptcha();
|
||||||
getCaptcha();
|
getCaptcha();
|
||||||
});
|
});
|
||||||
// 移除公钥, 方便后续重新获取
|
// 移除公钥, 方便后续重新获取
|
||||||
@@ -142,10 +134,10 @@ export default defineComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const getCaptcha = async () => {
|
const getCaptcha = async () => {
|
||||||
if (!state.useLoginCaptcha) {
|
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;
|
||||||
};
|
};
|
||||||
@@ -174,10 +166,9 @@ export default defineComponent({
|
|||||||
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 = '';
|
||||||
@@ -194,12 +185,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
// 用户信息
|
// 用户信息
|
||||||
const userInfos = {
|
const userInfos = {
|
||||||
|
name: loginRes.name,
|
||||||
username: state.loginForm.username,
|
username: state.loginForm.username,
|
||||||
// 头像
|
// 头像
|
||||||
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,
|
||||||
@@ -208,19 +198,9 @@ export default defineComponent({
|
|||||||
// 存储用户信息到浏览器缓存
|
// 存储用户信息到浏览器缓存
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 登录成功后的跳转
|
// 登录成功后的跳转
|
||||||
@@ -253,7 +233,7 @@ export default defineComponent({
|
|||||||
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;
|
||||||
@@ -271,28 +251,17 @@ export default defineComponent({
|
|||||||
state.changePwdDialog.form.username = '';
|
state.changePwdDialog.form.username = '';
|
||||||
getCaptcha();
|
getCaptcha();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
|
||||||
getCaptcha,
|
|
||||||
currentTime,
|
|
||||||
loginFormRef,
|
|
||||||
changePwdFormRef,
|
|
||||||
login,
|
|
||||||
changePwd,
|
|
||||||
cancelChangePwd,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.login-content-form {
|
.login-content-form {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
||||||
.login-content-code {
|
.login-content-code {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
|
|
||||||
.login-content-code-img {
|
.login-content-code-img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@@ -309,12 +278,14 @@ export default defineComponent({
|
|||||||
transition: all ease 0.2s;
|
transition: all ease 0.2s;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: #c0c4cc;
|
border-color: #c0c4cc;
|
||||||
transition: all ease 0.2s;
|
transition: all ease 0.2s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-content-submit {
|
.login-content-submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
|
|||||||
@@ -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,41 +24,35 @@
|
|||||||
</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">
|
<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';
|
||||||
export default {
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
name: 'LoginPage',
|
|
||||||
components: { Account },
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
setup() {
|
|
||||||
const store = useStore();
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
tabsActiveName: 'account',
|
tabsActiveName: 'account',
|
||||||
isTabPaneShow: true,
|
isTabPaneShow: true,
|
||||||
});
|
});
|
||||||
// 获取布局配置信息
|
|
||||||
const getThemeConfig = computed(() => {
|
const {
|
||||||
return store.state.themeConfig.themeConfig;
|
isTabPaneShow,
|
||||||
});
|
tabsActiveName,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
|
|
||||||
// 切换密码、手机登录
|
// 切换密码、手机登录
|
||||||
const onTabsClick = () => {
|
const onTabsClick = () => {
|
||||||
state.isTabPaneShow = !state.isTabPaneShow;
|
state.isTabPaneShow = !state.isTabPaneShow;
|
||||||
};
|
};
|
||||||
return {
|
|
||||||
onTabsClick,
|
|
||||||
getThemeConfig,
|
|
||||||
...toRefs(state),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -67,6 +61,7 @@ export default {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
background: url('@/assets/image/bg-login.png') no-repeat;
|
background: url('@/assets/image/bg-login.png') no-repeat;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
|
|
||||||
.login-logo {
|
.login-logo {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 30px;
|
top: 30px;
|
||||||
@@ -80,6 +75,7 @@ export default {
|
|||||||
width: 90%;
|
width: 90%;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-content {
|
.login-content {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@@ -94,9 +90,11 @@ export default {
|
|||||||
height: 480px;
|
height: 480px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
||||||
.login-content-main {
|
.login-content-main {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
|
||||||
.login-content-title {
|
.login-content-title {
|
||||||
color: #333;
|
color: #333;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -108,9 +106,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-content-mobile {
|
.login-content-mobile {
|
||||||
height: 418px;
|
height: 418px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-copyright {
|
.login-copyright {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -120,9 +120,11 @@ export default {
|
|||||||
color: white;
|
color: white;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|
||||||
.login-copyright-company {
|
.login-copyright-company {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-copyright-msg {
|
.login-copyright-msg {
|
||||||
@extend .login-copyright-company;
|
@extend .login-copyright-company;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<el-form class="search-form" label-position="right" :inline="true">
|
|
||||||
<el-form-item prop="project" label="项目" label-width="40px">
|
|
||||||
<el-select v-model="projectId" placeholder="请选择项目" @change="changeProject" filterable>
|
|
||||||
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item prop="env" label="env" label-width="33px">
|
|
||||||
<el-select style="width: 85px" v-model="envId" placeholder="环境" @change="changeEnv" filterable>
|
|
||||||
<el-option v-for="item in envs" :key="item.id" :label="item.name" :value="item.id">
|
|
||||||
<span style="float: left">{{ item.name }}</span>
|
|
||||||
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.remark }}</span>
|
|
||||||
</el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<slot></slot>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
|
|
||||||
import { projectApi } from '../project/api';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ProjectEnvSelect',
|
|
||||||
props: {
|
|
||||||
visible: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
machineId: {
|
|
||||||
type: Number,
|
|
||||||
},
|
|
||||||
isCommon: {
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props: any, { emit }) {
|
|
||||||
const state = reactive({
|
|
||||||
projects: [] as any,
|
|
||||||
envs: [] as any,
|
|
||||||
projectId: null,
|
|
||||||
envId: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
state.projects = await projectApi.accountProjects.request(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
const changeProject = async (projectId: any) => {
|
|
||||||
emit('update:projectId', projectId);
|
|
||||||
emit('changeProjectEnv', state.projectId, null);
|
|
||||||
state.envId = null;
|
|
||||||
state.envs = await projectApi.projectEnvs.request({ projectId });
|
|
||||||
};
|
|
||||||
|
|
||||||
const changeEnv = (envId: any) => {
|
|
||||||
emit('update:envId', envId);
|
|
||||||
emit('changeProjectEnv', state.projectId, envId);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
changeProject,
|
|
||||||
changeEnv,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
</style>
|
|
||||||
61
mayfly_go_web/src/views/ops/component/SshTunnelSelect.vue
Normal file
61
mayfly_go_web/src/views/ops/component/SshTunnelSelect.vue
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width: 100%">
|
||||||
|
<el-select @focus="getSshTunnelMachines" @change="change" style="width: 100%" v-model="sshTunnelMachineId"
|
||||||
|
@clear="clear" placeholder="请选择SSH隧道机器" clearable>
|
||||||
|
<el-option v-for="item in sshTunnelMachineList" :key="item.id" :label="`${item.ip}:${item.port} [${item.name}]`"
|
||||||
|
:value="item.id">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { toRefs, reactive, onMounted } from 'vue';
|
||||||
|
import { machineApi } from '../machine/api';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
//定义事件
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
// 单选则为id,多选为id数组
|
||||||
|
sshTunnelMachineId: null as any,
|
||||||
|
sshTunnelMachineList: [] as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
sshTunnelMachineId,
|
||||||
|
sshTunnelMachineList,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!props.modelValue || props.modelValue <= 0) {
|
||||||
|
state.sshTunnelMachineId = null;
|
||||||
|
} else {
|
||||||
|
state.sshTunnelMachineId = props.modelValue;
|
||||||
|
}
|
||||||
|
await getSshTunnelMachines();
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSshTunnelMachines = async () => {
|
||||||
|
if (state.sshTunnelMachineList.length == 0) {
|
||||||
|
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
|
||||||
|
state.sshTunnelMachineList = res.list;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
state.sshTunnelMachineId = null;
|
||||||
|
change();
|
||||||
|
}
|
||||||
|
|
||||||
|
const change = () => {
|
||||||
|
emit('update:modelValue', state.sshTunnelMachineId);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
68
mayfly_go_web/src/views/ops/component/TagInfo.vue
Normal file
68
mayfly_go_web/src/views/ops/component/TagInfo.vue
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
style="display: inline-flex; justify-content: center; align-items: center; cursor: pointer;vertical-align: middle;">
|
||||||
|
<el-popover @show="showTagInfo" placement="top-start" title="标签信息" :width="300" trigger="hover">
|
||||||
|
<template #reference>
|
||||||
|
<el-icon>
|
||||||
|
<InfoFilled />
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
<span v-for="(v, i) in tags" :key="i">
|
||||||
|
<el-tooltip effect="customized" :content="v.remark" placement="top">
|
||||||
|
<span class="color-success">{{ v.name }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-if="i != state.tags.length - 1" class="color-primary"> / </span>
|
||||||
|
</span>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { reactive, toRefs, onMounted } from 'vue';
|
||||||
|
import { tagApi } from '../tag/api';
|
||||||
|
const props = defineProps({
|
||||||
|
tagPath: {
|
||||||
|
type: [String],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
tagPath: '',
|
||||||
|
tags: [] as any,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
tags,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
state.tagPath = props.tagPath;
|
||||||
|
})
|
||||||
|
|
||||||
|
const showTagInfo = async () => {
|
||||||
|
if (state.tags && state.tags.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tagStrs = state.tagPath.split('/');
|
||||||
|
const tagPaths = [];
|
||||||
|
let nowTag = '';
|
||||||
|
for (let tagStr of tagStrs) {
|
||||||
|
if (!tagStr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (nowTag) {
|
||||||
|
nowTag = `${nowTag}${tagStr}/`;
|
||||||
|
} else {
|
||||||
|
nowTag = tagStr + '/';
|
||||||
|
}
|
||||||
|
tagPaths.push(nowTag);
|
||||||
|
}
|
||||||
|
state.tags = await tagApi.listByQuery.request({ tagPaths: tagPaths.join(',') });
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,21 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-tree-select
|
<el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags" placeholder="请选择关联标签"
|
||||||
@check="changeTag"
|
:render-after-expand="true" :default-expanded-keys="[selectTags]" show-checkbox check-strictly node-key="id"
|
||||||
style="width: 100%"
|
|
||||||
v-model="selectTags"
|
|
||||||
:data="tags"
|
|
||||||
:render-after-expand="true"
|
|
||||||
:default-expanded-keys="[selectTags]"
|
|
||||||
show-checkbox
|
|
||||||
check-strictly
|
|
||||||
node-key="id"
|
|
||||||
:props="{
|
:props="{
|
||||||
value: 'id',
|
value: 'id',
|
||||||
label: 'codePath',
|
label: 'codePath',
|
||||||
children: 'children',
|
children: 'children',
|
||||||
}"
|
}">
|
||||||
>
|
|
||||||
<template #default="{ data }">
|
<template #default="{ data }">
|
||||||
<span class="custom-tree-node">
|
<span class="custom-tree-node">
|
||||||
<span style="font-size: 13px">
|
<span style="font-size: 13px">
|
||||||
@@ -31,27 +22,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
|
import { toRefs, reactive, onMounted } from 'vue';
|
||||||
import { tagApi } from '../tag/api';
|
import { tagApi } from '../tag/api';
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'TagSelect',
|
|
||||||
props: {
|
|
||||||
tagId: {
|
tagId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
},
|
},
|
||||||
tagPath: {
|
tagPath: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props: any, { emit }) {
|
|
||||||
|
//定义事件
|
||||||
|
const emit = defineEmits(['changeTag', 'update:tagId', 'update:tagPath'])
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
tags: [],
|
tags: [],
|
||||||
// 单选则为id,多选为id数组
|
// 单选则为id,多选为id数组
|
||||||
selectTags: null as any,
|
selectTags: null as any,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
tags,
|
||||||
|
selectTags,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (props.tagId) {
|
if (props.tagId) {
|
||||||
state.selectTags = props.tagId;
|
state.selectTags = props.tagId;
|
||||||
@@ -69,13 +66,7 @@ export default defineComponent({
|
|||||||
emit('update:tagPath', null);
|
emit('update:tagPath', null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
changeTag,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
166
mayfly_go_web/src/views/ops/component/TagTree.vue
Normal file
166
mayfly_go_web/src/views/ops/component/TagTree.vue
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<div class="instances-box">
|
||||||
|
<el-row type="flex" justify="space-between">
|
||||||
|
<el-col :span="24" class="el-scrollbar flex-auto" style="overflow: auto">
|
||||||
|
<el-input v-model="filterText" placeholder="输入关键字->搜索已展开节点信息" clearable size="small" class="mb5" />
|
||||||
|
|
||||||
|
<el-tree ref="treeRef" :style="{ maxHeight: state.height, height: state.height, overflow: 'auto' }"
|
||||||
|
:highlight-current="true" :indent="7" :load="loadNode" :props="treeProps" lazy node-key="key"
|
||||||
|
:expand-on-click-node="true" :filter-node-method="filterNode" @node-click="treeNodeClick"
|
||||||
|
@node-expand="treeNodeClick" @node-contextmenu="nodeContextmenu">
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<span>
|
||||||
|
<span v-if="data.type == TagTreeNode.TagPath">
|
||||||
|
<tag-info :tag-path="data.label" />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<slot v-else :node="node" :data="data" name="prefix"></slot>
|
||||||
|
|
||||||
|
<span class="ml3">
|
||||||
|
<slot name="label" :data="data"> {{ data.label }}</slot>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<contextmenu :dropdown="state.dropdown" :items="state.contextmenuItems" ref="contextmenuRef"
|
||||||
|
@currentContextmenuClick="onCurrentContextmenuClick" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, reactive, ref, watch, toRefs } from 'vue';
|
||||||
|
import { TagTreeNode } from './tag';
|
||||||
|
import TagInfo from './TagInfo.vue';
|
||||||
|
import Contextmenu from '@/components/contextmenu/index.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
height: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
load: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
loadContextmenuItems: {
|
||||||
|
type: Function,
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const treeProps = {
|
||||||
|
label: 'name',
|
||||||
|
children: 'zones',
|
||||||
|
isLeaf: 'isLeaf',
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(['nodeClick', 'currentContextmenuClick'])
|
||||||
|
const treeRef: any = ref(null)
|
||||||
|
const contextmenuRef = ref();
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
height: 600 as any,
|
||||||
|
filterText: '',
|
||||||
|
dropdown: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
|
contextmenuItems: [],
|
||||||
|
opend: {},
|
||||||
|
})
|
||||||
|
const { filterText } = toRefs(state)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!props.height) {
|
||||||
|
state.height = window.innerHeight - 147 + 'px';
|
||||||
|
} else {
|
||||||
|
state.height = props.height;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(filterText, (val) => {
|
||||||
|
treeRef.value?.filter(val)
|
||||||
|
})
|
||||||
|
|
||||||
|
const filterNode = (value: string, data: any) => {
|
||||||
|
if (!value) return true
|
||||||
|
return data.label.includes(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载树节点
|
||||||
|
* @param { Object } node
|
||||||
|
* @param { Object } resolve
|
||||||
|
*/
|
||||||
|
const loadNode = async (node: any, resolve: any) => {
|
||||||
|
if (typeof resolve !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let nodes = []
|
||||||
|
try {
|
||||||
|
nodes = await props.load(node)
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
return resolve(nodes)
|
||||||
|
};
|
||||||
|
|
||||||
|
const treeNodeClick = (data: any) => {
|
||||||
|
emit('nodeClick', data);
|
||||||
|
// 关闭可能存在的右击菜单
|
||||||
|
contextmenuRef.value.closeContextmenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 树节点右击事件
|
||||||
|
const nodeContextmenu = (event: any, data: any) => {
|
||||||
|
if (!props.loadContextmenuItems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 加载当前节点是否需要显示右击菜单
|
||||||
|
const items = props.loadContextmenuItems(data)
|
||||||
|
if (!items || items.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.contextmenuItems = items;
|
||||||
|
const { clientX, clientY } = event;
|
||||||
|
state.dropdown.x = clientX;
|
||||||
|
state.dropdown.y = clientY;
|
||||||
|
contextmenuRef.value.openContextmenu(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCurrentContextmenuClick = (clickData: any) => {
|
||||||
|
emit('currentContextmenuClick', clickData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadNode = (nodeKey: any) => {
|
||||||
|
let node = getNode(nodeKey);
|
||||||
|
node.loaded = false;
|
||||||
|
node.expand();
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNode = (nodeKey: any) => {
|
||||||
|
let node = treeRef.value.getNode(nodeKey);
|
||||||
|
if (!node) {
|
||||||
|
throw new Error('未找到节点: ' + nodeKey);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
reloadNode,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.instances-box {
|
||||||
|
overflow: 'auto';
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.el-tree {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
38
mayfly_go_web/src/views/ops/component/tag.ts
Normal file
38
mayfly_go_web/src/views/ops/component/tag.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
export class TagTreeNode {
|
||||||
|
/**
|
||||||
|
* 节点id
|
||||||
|
*/
|
||||||
|
key: any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点名称
|
||||||
|
*/
|
||||||
|
label: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 树节点类型
|
||||||
|
*/
|
||||||
|
type: any
|
||||||
|
|
||||||
|
isLeaf: boolean = false;
|
||||||
|
|
||||||
|
params: any;
|
||||||
|
|
||||||
|
static TagPath = -1;
|
||||||
|
|
||||||
|
constructor(key: any, label: string, type?: any) {
|
||||||
|
this.key = key;
|
||||||
|
this.label = label;
|
||||||
|
this.type = type || TagTreeNode.TagPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
withIsLeaf(isLeaf: boolean) {
|
||||||
|
this.isLeaf = isLeaf;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withParams(params: any) {
|
||||||
|
this.params = params;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-dialog title="创建表" v-model="dialogVisible" :before-close="cancel" width="90%">
|
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" width="90%">
|
||||||
<el-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
|
<el-form label-position="left" ref="formRef" :model="tableData" label-width="80px">
|
||||||
<el-row>
|
<el-row>
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
@@ -13,10 +13,21 @@
|
|||||||
<el-input style="width: 80%" v-model="tableData.tableComment" size="small"></el-input>
|
<el-input style="width: 80%" v-model="tableData.tableComment" size="small"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col style="margin-top: 20px" :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item prop="characterSet" label="字符集">
|
<el-form-item prop="characterSet" label="charset">
|
||||||
<el-select filterable style="width: 80%" v-model="tableData.characterSet" size="small">
|
<el-select filterable style="width: 80%" v-model="tableData.characterSet" size="small">
|
||||||
<el-option v-for="item in characterSetNameList" :key="item" :label="item" :value="item"> </el-option>
|
<el-option v-for="item in characterSetNameList" :key="item" :label="item" :value="item">
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item prop="characterSet" label="collation">
|
||||||
|
<el-select filterable style="width: 80%" v-model="tableData.collation" size="small">
|
||||||
|
<el-option v-for="item in collationNameList" :key="item"
|
||||||
|
:label="tableData.characterSet + '_' + item"
|
||||||
|
:value="tableData.characterSet + '_' + item">
|
||||||
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -24,35 +35,88 @@
|
|||||||
|
|
||||||
<el-tabs v-model="activeName">
|
<el-tabs v-model="activeName">
|
||||||
<el-tab-pane label="字段" name="1">
|
<el-tab-pane label="字段" name="1">
|
||||||
<el-table :data="tableData.fields.res">
|
<el-table :data="tableData.fields.res" :max-height="tableData.height">
|
||||||
<el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.fields.colNames" :key="item.prop">
|
<el-table-column :prop="item.prop" :label="item.label"
|
||||||
|
v-for="item in tableData.fields.colNames" :key="item.prop">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name"></el-input>
|
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name">
|
||||||
|
</el-input>
|
||||||
|
|
||||||
<el-select v-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
|
<el-select v-if="item.prop === 'type'" filterable size="small"
|
||||||
<el-option v-for="typeValue in typeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option>
|
v-model="scope.row.type">
|
||||||
|
<el-option v-for="typeValue in columnTypeList" :key="typeValue"
|
||||||
|
:value="typeValue">{{ typeValue }}</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
|
||||||
<el-input v-if="item.prop === 'value'" size="small" v-model="scope.row.value"> </el-input>
|
<el-input v-if="item.prop === 'value'" size="small" v-model="scope.row.value">
|
||||||
|
</el-input>
|
||||||
|
|
||||||
<el-input v-if="item.prop === 'length'" size="small" v-model="scope.row.length"> </el-input>
|
<el-input v-if="item.prop === 'length'" size="small" v-model="scope.row.length">
|
||||||
|
</el-input>
|
||||||
|
|
||||||
<el-checkbox v-if="item.prop === 'notNull'" size="small" v-model="scope.row.notNull"> </el-checkbox>
|
<el-checkbox v-if="item.prop === 'notNull'" size="small"
|
||||||
|
v-model="scope.row.notNull"> </el-checkbox>
|
||||||
|
|
||||||
<el-checkbox v-if="item.prop === 'pri'" size="small" v-model="scope.row.pri"> </el-checkbox>
|
<el-checkbox v-if="item.prop === 'pri'" size="small" v-model="scope.row.pri">
|
||||||
|
</el-checkbox>
|
||||||
|
|
||||||
<el-checkbox v-if="item.prop === 'auto_increment'" size="small" v-model="scope.row.auto_increment"> </el-checkbox>
|
<el-checkbox v-if="item.prop === 'auto_increment'" size="small"
|
||||||
|
v-model="scope.row.auto_increment"> </el-checkbox>
|
||||||
|
|
||||||
<el-input v-if="item.prop === 'remark'" size="small" v-model="scope.row.remark"> </el-input>
|
<el-input v-if="item.prop === 'remark'" size="small" v-model="scope.row.remark">
|
||||||
|
</el-input>
|
||||||
|
|
||||||
<el-button v-if="item.prop === 'action'" type="text" size="small" @click.prevent="deleteRow(scope.$index)">删除</el-button>
|
<el-link v-if="item.prop === 'action'" type="danger" plain size="small"
|
||||||
|
:underline="false" @click.prevent="deleteRow(scope.$index)">删除</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-row style="margin-top: 20px">
|
<el-row style="margin-top: 20px">
|
||||||
<el-button @click="addRow()" type="text" icon="plus"></el-button>
|
<el-button @click="addDefaultRows()" link type="warning" icon="plus">添加默认列</el-button>
|
||||||
|
<el-button @click="addRow()" link type="primary" icon="plus">添加列</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<el-tab-pane label="索引" name="2">
|
||||||
|
<el-table :data="tableData.indexs.res" :max-height="tableData.height">
|
||||||
|
<el-table-column :prop="item.prop" :label="item.label"
|
||||||
|
v-for="item in tableData.indexs.colNames" :key="item.prop">
|
||||||
|
<template #default="scope">
|
||||||
|
|
||||||
|
<el-input v-if="item.prop === 'indexName'" size="small"
|
||||||
|
v-model="scope.row.indexName"></el-input>
|
||||||
|
|
||||||
|
<el-select v-if="item.prop === 'columnNames'" v-model="scope.row.columnNames"
|
||||||
|
multiple collapse-tags collapse-tags-tooltip filterable placeholder="请选择字段"
|
||||||
|
style="width: 100%">
|
||||||
|
<el-option v-for="cl in tableData.indexs.columns" :key="cl.name"
|
||||||
|
:label="cl.name" :value="cl.name">
|
||||||
|
{{ cl.name + ' - ' + (cl.remark || '') }}
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique">
|
||||||
|
</el-checkbox>
|
||||||
|
|
||||||
|
<el-select v-if="item.prop === 'indexType'" filterable size="small"
|
||||||
|
v-model="scope.row.indexType">
|
||||||
|
<el-option v-for="typeValue in indexTypeList" :key="typeValue"
|
||||||
|
:value="typeValue">{{ typeValue }}</el-option>
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-input v-if="item.prop === 'indexComment'" size="small"
|
||||||
|
v-model="scope.row.indexComment"> </el-input>
|
||||||
|
|
||||||
|
<el-link v-if="item.prop === 'action'" type="danger" plain size="small"
|
||||||
|
:underline="false" @click.prevent="deleteIndex(scope.$index)">删除</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<el-row style="margin-top: 20px">
|
||||||
|
<el-button @click="addIndex()" link type="primary" icon="plus">添加索引</el-button>
|
||||||
|
</el-row>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -63,14 +127,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { watch, toRefs, reactive, defineComponent, ref, getCurrentInstance } from 'vue';
|
import { watch, toRefs, reactive, ref } from 'vue';
|
||||||
import { TYPE_LIST, CHARACTER_SET_NAME_LIST } from './service.ts';
|
import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service.ts';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import SqlExecBox from './component/SqlExecBox.ts';
|
import SqlExecBox from './component/SqlExecBox.ts';
|
||||||
export default defineComponent({
|
|
||||||
name: 'createTable',
|
const props = defineProps({
|
||||||
props: {
|
|
||||||
visible: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
@@ -86,16 +149,20 @@ export default defineComponent({
|
|||||||
db: {
|
db: {
|
||||||
type: String,
|
type: String,
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
setup(props: any, { emit }) {
|
|
||||||
|
//定义事件
|
||||||
|
const emit = defineEmits(['update:visible', 'cancel', 'val-change', 'submit-sql'])
|
||||||
|
|
||||||
const formRef: any = ref();
|
const formRef: any = ref();
|
||||||
const { proxy } = getCurrentInstance() as any;
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
btnloading: false,
|
btnloading: false,
|
||||||
activeName: '1',
|
activeName: '1',
|
||||||
typeList: TYPE_LIST,
|
columnTypeList: TYPE_LIST,
|
||||||
|
indexTypeList: ['BTREE'], // mysql索引类型详解 http://c.biancheng.net/view/7897.html
|
||||||
characterSetNameList: CHARACTER_SET_NAME_LIST,
|
characterSetNameList: CHARACTER_SET_NAME_LIST,
|
||||||
|
collationNameList: COLLATION_SUFFIX_LIST,
|
||||||
tableData: {
|
tableData: {
|
||||||
fields: {
|
fields: {
|
||||||
colNames: [
|
colNames: [
|
||||||
@@ -137,7 +204,6 @@ export default defineComponent({
|
|||||||
label: '操作',
|
label: '操作',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
res: [
|
res: [
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
@@ -151,19 +217,72 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
indexs: {
|
||||||
|
colNames: [
|
||||||
|
{
|
||||||
|
prop: 'indexName',
|
||||||
|
label: '索引名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'columnNames',
|
||||||
|
label: '列名',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'unique',
|
||||||
|
label: '唯一',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'indexType',
|
||||||
|
label: '类型',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'indexComment',
|
||||||
|
label: '备注',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'action',
|
||||||
|
label: '操作',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columns: [{ name: '', remark: '' }],
|
||||||
|
res: [
|
||||||
|
{
|
||||||
|
indexName: '',
|
||||||
|
columnNames: [],
|
||||||
|
unique: false,
|
||||||
|
indexType: 'BTREE',
|
||||||
|
indexComment: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
characterSet: 'utf8mb4',
|
characterSet: 'utf8mb4',
|
||||||
|
collation: 'utf8mb4_general_ci',
|
||||||
tableName: '',
|
tableName: '',
|
||||||
tableComment: '',
|
tableComment: '',
|
||||||
|
height: 550
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
dialogVisible,
|
||||||
|
btnloading,
|
||||||
|
activeName,
|
||||||
|
columnTypeList,
|
||||||
|
indexTypeList,
|
||||||
|
characterSetNameList,
|
||||||
|
collationNameList,
|
||||||
|
tableData,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
watch(props, async (newValue) => {
|
watch(props, async (newValue) => {
|
||||||
state.dialogVisible = newValue.visible;
|
state.dialogVisible = newValue.visible;
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
emit('update:visible', false);
|
emit('update:visible', false);
|
||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
const addRow = () => {
|
const addRow = () => {
|
||||||
state.tableData.fields.res.push({
|
state.tableData.fields.res.push({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -176,45 +295,236 @@ export default defineComponent({
|
|||||||
remark: '',
|
remark: '',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addIndex = () => {
|
||||||
|
state.tableData.indexs.res.push({
|
||||||
|
indexName: '',
|
||||||
|
columnNames: [],
|
||||||
|
unique: false,
|
||||||
|
indexType: 'BTREE',
|
||||||
|
indexComment: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addDefaultRows = () => {
|
||||||
|
state.tableData.fields.res.push(
|
||||||
|
{ name: 'id', type: 'bigint', length: '20', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
|
||||||
|
{ name: 'creator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
|
||||||
|
{ name: 'creator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人姓名' },
|
||||||
|
{ name: 'create_time', type: 'datetime', length: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '创建时间' },
|
||||||
|
{ name: 'updator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
|
||||||
|
{ name: 'updator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名' },
|
||||||
|
{ name: 'update_time', type: 'datetime', length: '', value: 'CURRENT_TIMESTAMP', notNull: true, pri: false, auto_increment: false, remark: '修改时间' },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const deleteRow = (index: any) => {
|
const deleteRow = (index: any) => {
|
||||||
state.tableData.fields.res.splice(index, 1);
|
state.tableData.fields.res.splice(index, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteIndex = (index: any) => {
|
||||||
|
state.tableData.indexs.res.splice(index, 1);
|
||||||
|
};
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
let data = state.tableData;
|
let sql = genSql();
|
||||||
let primary_key = '';
|
if (!sql) {
|
||||||
let fields: string[] = [];
|
ElMessage.warning('没有更改');
|
||||||
data.fields.res.forEach((item) => {
|
return;
|
||||||
fields.push(
|
|
||||||
`${item.name} ${item.type}${+item.length > 0 ? `(${item.length})` : ''} ${item.notNull ? 'NOT NULL' : ''} ${
|
|
||||||
item.auto_increment ? 'AUTO_INCREMENT' : ''
|
|
||||||
} ${item.value ? 'DEFAULT ' + item.value : item.notNull ? '' : 'DEFAULT NULL'} ${
|
|
||||||
item.remark ? `COMMENT '${item.remark}'` : ''
|
|
||||||
} \n`
|
|
||||||
);
|
|
||||||
if (item.pri) {
|
|
||||||
primary_key += `${item.name},`;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
let sql = `
|
|
||||||
CREATE TABLE ${data.tableName} (
|
|
||||||
${fields.join(',')}
|
|
||||||
${primary_key ? `, PRIMARY KEY (${primary_key.slice(0, -1)})` : ''}
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=${data.characterSet} COLLATE=utf8mb4_bin COMMENT='${data.tableComment}';`;
|
|
||||||
|
|
||||||
SqlExecBox({
|
SqlExecBox({
|
||||||
sql: sql,
|
sql: sql,
|
||||||
dbId: props.dbId as any,
|
dbId: props.dbId as any,
|
||||||
db: props.db,
|
db: props.db,
|
||||||
runSuccessCallback: () => {
|
runSuccessCallback: () => {
|
||||||
ElMessage.success('创建成功');
|
emit('submit-sql', {tableName: state.tableData.tableName });
|
||||||
proxy.$parent.tableInfo({ id: props.dbId });
|
// cancel();
|
||||||
cancel();
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对比两个数组,取出被修改过的对象数组
|
||||||
|
* @param oldArr 原对象数组
|
||||||
|
* @param nowArr 修改后的对象数组
|
||||||
|
* @param key 标志对象唯一属性
|
||||||
|
*/
|
||||||
|
const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { del: any[], add: any[], upd: any[] } => {
|
||||||
|
let data = {
|
||||||
|
del: [] as object[], // 删除的数据
|
||||||
|
add: [] as object[], // 新增的数据
|
||||||
|
upd: [] as object[] // 修改的数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// 旧数据为空
|
||||||
|
if (oldArr && Array.isArray(oldArr) && oldArr.length === 0
|
||||||
|
&& nowArr && Array.isArray(nowArr) && nowArr.length > 0) {
|
||||||
|
data.add = nowArr;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新数据为空
|
||||||
|
if (nowArr && Array.isArray(nowArr) && nowArr.length === 0
|
||||||
|
&& oldArr && Array.isArray(oldArr) && oldArr.length > 0) {
|
||||||
|
data.del = oldArr;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldMap = {}, newMap = {};
|
||||||
|
oldArr.forEach(a => oldMap[a[key]] = a)
|
||||||
|
|
||||||
|
nowArr.forEach(a => {
|
||||||
|
let k = a[key]
|
||||||
|
newMap[k] = a;
|
||||||
|
if (!oldMap.hasOwnProperty(k)) {// 新增
|
||||||
|
data.add.push(a)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
oldArr.forEach(a => {
|
||||||
|
let k = a[key];
|
||||||
|
let newData = newMap[k];
|
||||||
|
if (!newData) { // 删除
|
||||||
|
data.del.push(a)
|
||||||
|
} else { // 判断每个字段是否相等,否则为修改
|
||||||
|
for (let f in a) {
|
||||||
|
let oldV = a[f]
|
||||||
|
let newV = newData[f]
|
||||||
|
if (oldV.toString() !== newV.toString()) {
|
||||||
|
data.upd.push(newData)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const genSql = () => {
|
||||||
|
|
||||||
|
const genColumnBasicSql = (cl: any) => {
|
||||||
|
let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : '\'' + cl.value + '\'') : '';
|
||||||
|
let defVal = `${val ? ('DEFAULT ' + val) : ''}`;
|
||||||
|
let length = cl.length ? `(${cl.length})` : '';
|
||||||
|
let onUpdate = 'update_time' === cl.name ? ' ON UPDATE CURRENT_TIMESTAMP ' : ''
|
||||||
|
return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${cl.auto_increment ? 'AUTO_INCREMENT' : ''} ${defVal} ${onUpdate} comment '${cl.remark || ''}' `
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = state.tableData;
|
||||||
|
// 创建表
|
||||||
|
if (!props.data?.edit) {
|
||||||
|
if (state.activeName === '1') {// 创建表结构
|
||||||
|
let primary_key = '';
|
||||||
|
let fields: string[] = [];
|
||||||
|
data.fields.res.forEach((item) => {
|
||||||
|
item.name && fields.push(genColumnBasicSql(item));
|
||||||
|
if (item.pri) {
|
||||||
|
primary_key += `${item.name},`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return `CREATE TABLE ${data.tableName}
|
||||||
|
( ${fields.join(',')}
|
||||||
|
${primary_key ? `, PRIMARY KEY (${primary_key.slice(0, -1)})` : ''}
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=${data.characterSet} COLLATE =${data.collation} COMMENT='${data.tableComment}';`;
|
||||||
|
|
||||||
|
} else if (state.activeName === '2' && data.indexs.res.length > 0) { // 创建索引
|
||||||
|
let sql = `ALTER TABLE ${data.tableName}`;
|
||||||
|
state.tableData.indexs.res.forEach(a => {
|
||||||
|
sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
|
||||||
|
})
|
||||||
|
return sql.substring(0, sql.length - 1) + ';'
|
||||||
|
}
|
||||||
|
} else { // 修改
|
||||||
|
let addSql = '', updSql = '', delSql = '';
|
||||||
|
if (state.activeName === '1') {// 修改列
|
||||||
|
let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name')
|
||||||
|
if (changeData.add.length > 0) {
|
||||||
|
addSql = `ALTER TABLE ${data.tableName}`
|
||||||
|
changeData.add.forEach(a => {
|
||||||
|
addSql += ` ADD ${genColumnBasicSql(a)},`
|
||||||
|
})
|
||||||
|
addSql = addSql.substring(0, addSql.length - 1)
|
||||||
|
addSql += ';'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changeData.upd.length > 0) {
|
||||||
|
updSql = `ALTER TABLE ${data.tableName}`;
|
||||||
|
changeData.upd.forEach(a => {
|
||||||
|
updSql += ` MODIFY ${genColumnBasicSql(a)},`
|
||||||
|
})
|
||||||
|
updSql = updSql.substring(0, updSql.length - 1)
|
||||||
|
updSql += ';'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changeData.del.length > 0) {
|
||||||
|
changeData.del.forEach(a => {
|
||||||
|
delSql += ` ALTER TABLE ${data.tableName} DROP COLUMN ${a.name}; `
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return addSql + updSql + delSql;
|
||||||
|
|
||||||
|
} else if (state.activeName === '2') { // 修改索引
|
||||||
|
let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName')
|
||||||
|
// 搜集修改和删除的索引,添加到drop index xx
|
||||||
|
// 收集新增和修改的索引,添加到ADD xx
|
||||||
|
// ALTER TABLE `test1`
|
||||||
|
// DROP INDEX `test1_name_uindex`,
|
||||||
|
// DROP INDEX `test1_column_name4_index`,
|
||||||
|
// ADD UNIQUE INDEX `test1_name_uindex`(`id`) USING BTREE COMMENT 'ASDASD',
|
||||||
|
// ADD INDEX `111`(`column_name4`) USING BTREE COMMENT 'zasf';
|
||||||
|
|
||||||
|
let dropIndexNames: string[] = [];
|
||||||
|
let addIndexs: any[] = [];
|
||||||
|
|
||||||
|
if (changeData.upd.length > 0) {
|
||||||
|
changeData.upd.forEach(a => {
|
||||||
|
dropIndexNames.push(a.indexName)
|
||||||
|
addIndexs.push(a)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changeData.del.length > 0) {
|
||||||
|
changeData.del.forEach(a => {
|
||||||
|
dropIndexNames.push(a.indexName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changeData.add.length > 0) {
|
||||||
|
changeData.add.forEach(a => {
|
||||||
|
addIndexs.push(a)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropIndexNames.length > 0 || addIndexs.length > 0) {
|
||||||
|
let sql = `ALTER TABLE ${data.tableName} `;
|
||||||
|
if (dropIndexNames.length > 0) {
|
||||||
|
dropIndexNames.forEach(a => {
|
||||||
|
sql += `DROP INDEX ${a},`
|
||||||
|
})
|
||||||
|
sql = sql.substring(0, sql.length - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addIndexs.length > 0) {
|
||||||
|
if (dropIndexNames.length > 0){
|
||||||
|
sql += ','
|
||||||
|
}
|
||||||
|
addIndexs.forEach(a => {
|
||||||
|
sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
|
||||||
|
})
|
||||||
|
sql = sql.substring(0, sql.length - 1)
|
||||||
|
}
|
||||||
|
return sql;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
formRef.value.resetFields();
|
state.activeName = '1'
|
||||||
|
formRef.value.resetFields()
|
||||||
|
state.tableData.tableName = ''
|
||||||
|
state.tableData.tableComment = ''
|
||||||
state.tableData.fields.res = [
|
state.tableData.fields.res = [
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
@@ -227,17 +537,64 @@ export default defineComponent({
|
|||||||
remark: '',
|
remark: '',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
state.tableData.indexs.res = [{
|
||||||
|
indexName: '',
|
||||||
|
columnNames: [],
|
||||||
|
unique: false,
|
||||||
|
indexType: 'BTREE',
|
||||||
|
indexComment: '',
|
||||||
|
},]
|
||||||
};
|
};
|
||||||
return {
|
|
||||||
...toRefs(state),
|
const oldData = { indexs: [] as any[], fields: [] as any[] }
|
||||||
formRef,
|
watch(() => props.data, (newValue: any) => {
|
||||||
cancel,
|
const { row, indexs, columns } = newValue;
|
||||||
reset,
|
// 回显表名表注释
|
||||||
addRow,
|
state.tableData.tableName = row.tableName
|
||||||
deleteRow,
|
state.tableData.tableComment = row.tableComment
|
||||||
submit,
|
// 回显列
|
||||||
|
if (columns && Array.isArray(columns) && columns.length > 0) {
|
||||||
|
oldData.fields = [];
|
||||||
|
state.tableData.fields.res = [];
|
||||||
|
// 索引列下拉选
|
||||||
|
state.tableData.indexs.columns = [];
|
||||||
|
columns.forEach(a => {
|
||||||
|
let typeObj = a.columnType.replace(')', '').split('(')
|
||||||
|
let type = typeObj[0];
|
||||||
|
let length = typeObj.length > 1 && typeObj[1] || '';
|
||||||
|
let data = {
|
||||||
|
name: a.columnName,
|
||||||
|
type,
|
||||||
|
value: a.columnDefault || '',
|
||||||
|
length,
|
||||||
|
notNull: a.nullable !== 'YES',
|
||||||
|
pri: a.columnKey === 'PRI',
|
||||||
|
auto_increment: a.extra?.indexOf('auto_increment') > -1,
|
||||||
|
remark: a.columnComment,
|
||||||
};
|
};
|
||||||
},
|
state.tableData.fields.res.push(data)
|
||||||
});
|
oldData.fields.push(JSON.parse(JSON.stringify(data)))
|
||||||
|
// 索引字段下拉选项
|
||||||
|
state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 回显索引
|
||||||
|
if (indexs && Array.isArray(indexs) && indexs.length > 0) {
|
||||||
|
oldData.indexs = [];
|
||||||
|
state.tableData.indexs.res = [];
|
||||||
|
// 索引过滤掉主键
|
||||||
|
indexs.filter(a => a.indexName !== "PRIMARY").forEach(a => {
|
||||||
|
let data = {
|
||||||
|
indexName: a.indexName,
|
||||||
|
columnNames: a.columnName?.split(','),
|
||||||
|
unique: a.nonUnique === 0 || false,
|
||||||
|
indexType: a.indexType,
|
||||||
|
indexComment: a.indexComment,
|
||||||
|
}
|
||||||
|
state.tableData.indexs.res.push(data)
|
||||||
|
oldData.indexs.push(JSON.parse(JSON.stringify(data)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
|
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
|
||||||
|
:destroy-on-close="true" width="38%">
|
||||||
<el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
|
<el-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>
|
||||||
@@ -17,7 +20,8 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="host" label="host:" required>
|
<el-form-item prop="host" label="host:" required>
|
||||||
<el-col :span="18">
|
<el-col :span="18">
|
||||||
<el-input :disabled="form.id" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
|
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip"
|
||||||
|
auto-complete="off"></el-input>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col style="text-align: center" :span="1">:</el-col>
|
<el-col style="text-align: center" :span="1">:</el-col>
|
||||||
<el-col :span="5">
|
<el-col :span="5">
|
||||||
@@ -28,42 +32,30 @@
|
|||||||
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="password" label="密码:">
|
<el-form-item prop="password" label="密码:">
|
||||||
<el-input
|
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
|
||||||
type="password"
|
autocomplete="new-password">
|
||||||
show-password
|
|
||||||
v-model.trim="form.password"
|
|
||||||
placeholder="请输入密码,修改操作可不填"
|
|
||||||
autocomplete="new-password"
|
|
||||||
>
|
|
||||||
<template v-if="form.id && form.id != 0" #suffix>
|
<template v-if="form.id && form.id != 0" #suffix>
|
||||||
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
|
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
|
||||||
|
:content="pwd">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
|
<el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码
|
||||||
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item prop="params" label="连接参数:">
|
|
||||||
<el-input v-model="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2"></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item prop="database" label="数据库名:" required>
|
<el-form-item prop="database" label="数据库名:" required>
|
||||||
<el-col :span="19">
|
<el-col :span="19">
|
||||||
<el-select
|
<el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
|
||||||
@change="changeDatabase"
|
collapse-tags-tooltip filterable allow-create placeholder="请确保数据库实例信息填写完整后获取库名"
|
||||||
v-model="databaseList"
|
style="width: 100%">
|
||||||
multiple
|
|
||||||
collapse-tags
|
|
||||||
collapse-tags-tooltip
|
|
||||||
filterable
|
|
||||||
allow-create
|
|
||||||
placeholder="请确保数据库实例信息填写完整后获取库名"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
|
<el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col style="text-align: center" :span="1"><el-divider direction="vertical" border-style="dashed" /></el-col>
|
<el-col style="text-align: center" :span="1">
|
||||||
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
</el-col>
|
||||||
<el-col :span="4">
|
<el-col :span="4">
|
||||||
<el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link>
|
<el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -72,24 +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" :false-label="-1"></el-checkbox>
|
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
|
||||||
</el-col>
|
<template #suffix>
|
||||||
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
|
<el-link target="_blank" href="https://github.com/go-sql-driver/mysql#parameters"
|
||||||
<el-col :span="19" v-if="form.enableSshTunnel == 1">
|
:underline="false" type="primary" class="mr5">参数参考</el-link>
|
||||||
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
|
</template>
|
||||||
<el-option
|
</el-input>
|
||||||
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>
|
||||||
@@ -102,79 +94,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { toRefs, reactive, watch, defineComponent, 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';
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'DbEdit',
|
|
||||||
components: {
|
|
||||||
TagSelect,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
visible: {
|
visible: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
projects: {
|
|
||||||
type: Array,
|
|
||||||
},
|
|
||||||
db: {
|
db: {
|
||||||
type: [Boolean, Object],
|
type: [Boolean, Object],
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
setup(props: any, { emit }) {
|
|
||||||
const dbForm: any = ref(null);
|
|
||||||
|
|
||||||
const state = reactive({
|
//定义事件
|
||||||
dialogVisible: false,
|
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
|
||||||
projects: [],
|
|
||||||
envs: [],
|
const rules = {
|
||||||
allDatabases: [] as any,
|
tagId: [
|
||||||
databaseList: [] as any,
|
|
||||||
sshTunnelMachineList: [] as any,
|
|
||||||
form: {
|
|
||||||
id: null,
|
|
||||||
tagId: null as any,
|
|
||||||
tagPath: null as any,
|
|
||||||
type: null,
|
|
||||||
name: null,
|
|
||||||
host: '',
|
|
||||||
port: 3306,
|
|
||||||
username: null,
|
|
||||||
password: null,
|
|
||||||
params: null,
|
|
||||||
database: '',
|
|
||||||
project: null,
|
|
||||||
projectId: null,
|
|
||||||
envId: null,
|
|
||||||
env: null,
|
|
||||||
remark: '',
|
|
||||||
enableSshTunnel: null,
|
|
||||||
sshTunnelMachineId: null,
|
|
||||||
},
|
|
||||||
// 原密码
|
|
||||||
pwd: '',
|
|
||||||
btnLoading: false,
|
|
||||||
rules: {
|
|
||||||
projectId: [
|
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请选择项目',
|
message: '请选择标签',
|
||||||
trigger: ['change', 'blur'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
envId: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请选择环境',
|
|
||||||
trigger: ['change', 'blur'],
|
trigger: ['change', 'blur'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -213,25 +161,59 @@ export default defineComponent({
|
|||||||
trigger: ['change', 'blur'],
|
trigger: ['change', 'blur'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbForm: any = ref(null);
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
dialogVisible: false,
|
||||||
|
tabActiveName: 'basic',
|
||||||
|
allDatabases: [] as any,
|
||||||
|
databaseList: [] as any,
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
tagId: null as any,
|
||||||
|
tagPath: null as any,
|
||||||
|
type: null,
|
||||||
|
name: null,
|
||||||
|
host: '',
|
||||||
|
port: 3306,
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
|
params: null,
|
||||||
|
database: '',
|
||||||
|
remark: '',
|
||||||
|
sshTunnelMachineId: null as any,
|
||||||
},
|
},
|
||||||
|
// 原密码
|
||||||
|
pwd: '',
|
||||||
|
btnLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(props, (newValue) => {
|
const {
|
||||||
|
dialogVisible,
|
||||||
|
tabActiveName,
|
||||||
|
allDatabases,
|
||||||
|
databaseList,
|
||||||
|
form,
|
||||||
|
pwd,
|
||||||
|
btnLoading,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
|
watch(props, (newValue: any) => {
|
||||||
state.dialogVisible = newValue.visible;
|
state.dialogVisible = newValue.visible;
|
||||||
if (!state.dialogVisible) {
|
if (!state.dialogVisible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state.projects = newValue.projects;
|
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.envs = [];
|
state.form = { port: 3306 } as any;
|
||||||
state.form = { port: 3306, enableSshTunnel: -1 } as any;
|
|
||||||
state.databaseList = [];
|
state.databaseList = [];
|
||||||
}
|
}
|
||||||
getSshTunnelMachines();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -241,21 +223,6 @@ export default defineComponent({
|
|||||||
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 changeEnv = (envId: number) => {
|
|
||||||
for (let p of state.envs as any) {
|
|
||||||
if (p.id == envId) {
|
|
||||||
state.form.env = p.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
||||||
@@ -275,6 +242,9 @@ export default defineComponent({
|
|||||||
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);
|
||||||
@@ -304,20 +274,5 @@ export default defineComponent({
|
|||||||
resetInputDb();
|
resetInputDb();
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
dbForm,
|
|
||||||
getAllDatabase,
|
|
||||||
getDbPwd,
|
|
||||||
changeDatabase,
|
|
||||||
getSshTunnelMachines,
|
|
||||||
changeEnv,
|
|
||||||
btnOk,
|
|
||||||
cancel,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss"></style>
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
<div class="db-list">
|
<div class="db-list">
|
||||||
<el-card>
|
<el-card>
|
||||||
<el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button>
|
<el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button>
|
||||||
<el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary" icon="edit">编辑</el-button>
|
<el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary"
|
||||||
<el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger" icon="delete"
|
icon="edit">编辑</el-button>
|
||||||
>删除</el-button
|
<el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger"
|
||||||
>
|
icon="delete">删除</el-button>
|
||||||
<div style="float: right">
|
<div style="float: right">
|
||||||
<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">
|
||||||
@@ -30,56 +37,55 @@
|
|||||||
<el-table-column prop="type" label="类型" min-width="90"></el-table-column>
|
<el-table-column prop="type" label="类型" min-width="90"></el-table-column>
|
||||||
<el-table-column prop="database" label="数据库" min-width="80">
|
<el-table-column prop="database" label="数据库" min-width="80">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-popover :width="250" placement="right" trigger="click">
|
<el-popover placement="right" trigger="click" :width="300">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-link type="primary" :underline="false" plain>查看</el-link>
|
<el-link type="primary" :underline="false" plain @click="selectDb(scope.row.dbs)">查看
|
||||||
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
<el-tag
|
<el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索"
|
||||||
@click="showTableInfo(scope.row, db)"
|
size="small">
|
||||||
effect="plain"
|
<template #prefix>
|
||||||
type="success"
|
<el-icon class="el-input__icon">
|
||||||
size="small"
|
<search-icon />
|
||||||
v-for="db in scope.row.dbs"
|
</el-icon>
|
||||||
:key="db"
|
</template>
|
||||||
style="cursor: pointer; margin-left: 3px; margin-bottom: 3px"
|
</el-input>
|
||||||
>{{ db }}</el-tag
|
<div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
|
||||||
>
|
style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative">
|
||||||
|
<el-link type="success" plain size="small" :underline="false">{{ db }}</el-link>
|
||||||
|
<el-link type="primary" plain size="small" :underline="false"
|
||||||
|
@click="showTableInfo(scope.row, db)" style="position: absolute; right: 4px">操作
|
||||||
|
</el-link>
|
||||||
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
|
<el-table-column prop="username" label="用户名" min-width="100"></el-table-column>
|
||||||
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
|
<el-table-column prop="remark" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
|
||||||
|
|
||||||
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
|
<el-table-column label="操作" min-width="160" fixed="right">
|
||||||
<el-table-column min-width="160" prop="createTime" label="创建时间" show-overflow-tooltip>
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ $filters.dateFormat(scope.row.createTime) }}
|
<el-link plain size="small" :underline="false" @click="showInfo(scope.row)">
|
||||||
</template>
|
详情</el-link>
|
||||||
</el-table-column>
|
<el-divider direction="vertical" border-style="dashed" />
|
||||||
|
<el-link class="ml5" type="primary" plain size="small" :underline="false"
|
||||||
<el-table-column label="操作" min-width="120" fixed="right">
|
@click="onShowSqlExec(scope.row)">
|
||||||
<template #default="scope">
|
SQL执行记录</el-link>
|
||||||
<el-link type="primary" plain size="small" :underline="false" @click="onShowSqlExec(scope.row)">SQL执行记录</el-link>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-row style="margin-top: 20px" type="flex" justify="end">
|
<el-row style="margin-top: 20px" type="flex" justify="end">
|
||||||
<el-pagination
|
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
|
||||||
style="text-align: right"
|
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
|
||||||
@current-change="handlePageChange"
|
:page-size="query.pageSize"></el-pagination>
|
||||||
:total="total"
|
|
||||||
layout="prev, pager, next, total, jumper"
|
|
||||||
v-model:current-page="query.pageNum"
|
|
||||||
:page-size="query.pageSize"
|
|
||||||
></el-pagination>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-dialog width="75%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
|
<el-dialog width="80%" :title="`${db} 表信息`" :before-close="closeTableInfo" v-model="tableInfoDialog.visible">
|
||||||
<el-row class="mb10">
|
<el-row class="mb10">
|
||||||
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right">
|
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-button class="ml5" type="success" size="small" @click="showDumpInfo = !showDumpInfo">导出</el-button>
|
<el-button class="ml5" type="success" size="small">导出</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-form-item label="导出内容: ">
|
<el-form-item label="导出内容: ">
|
||||||
<el-radio-group v-model="dumpInfo.type">
|
<el-radio-group v-model="dumpInfo.type">
|
||||||
@@ -90,10 +96,13 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="导出表: ">
|
<el-form-item label="导出表: ">
|
||||||
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tableInfoDialog.infos">
|
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small"
|
||||||
|
:data="tableInfoDialog.infos">
|
||||||
<el-table-column type="selection" width="45" />
|
<el-table-column type="selection" width="45" />
|
||||||
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
|
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
||||||
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -103,52 +112,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
|
||||||
<el-button type="primary" size="small" @click="tableCreateDialog.visible = true">创建表</el-button>
|
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small">
|
<el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small"
|
||||||
|
max-height="680">
|
||||||
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
|
||||||
<template #header>
|
<template #header>
|
||||||
<el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
|
<el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤"
|
||||||
|
clearable />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
|
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
|
||||||
<template #header>
|
<template #header>
|
||||||
<el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤" clearable />
|
<el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤"
|
||||||
|
clearable />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column prop="tableRows" label="Rows" min-width="70" sortable
|
||||||
prop="tableRows"
|
:sort-method="(a: any, b: any) => parseInt(a.tableRows) - parseInt(b.tableRows)"></el-table-column>
|
||||||
label="Rows"
|
<el-table-column property="dataLength" label="数据大小" sortable
|
||||||
min-width="70"
|
:sort-method="(a: any, b: any) => parseInt(a.dataLength) - parseInt(b.dataLength)">
|
||||||
sortable
|
|
||||||
:sort-method="(a, b) => parseInt(a.tableRows) - parseInt(b.tableRows)"
|
|
||||||
></el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
property="dataLength"
|
|
||||||
label="数据大小"
|
|
||||||
sortable
|
|
||||||
:sort-method="(a, b) => parseInt(a.dataLength) - parseInt(b.dataLength)"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatByteSize(scope.row.dataLength) }}
|
{{ formatByteSize(scope.row.dataLength) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column property="indexLength" label="索引大小" sortable
|
||||||
property="indexLength"
|
:sort-method="(a: any, b: any) => parseInt(a.indexLength) - parseInt(b.indexLength)">
|
||||||
label="索引大小"
|
|
||||||
sortable
|
|
||||||
:sort-method="(a, b) => parseInt(a.indexLength) - parseInt(b.indexLength)"
|
|
||||||
>
|
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
{{ formatByteSize(scope.row.indexLength) }}
|
{{ formatByteSize(scope.row.indexLength) }}
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column property="createTime" label="创建时间" min-width="150"> </el-table-column>
|
<el-table-column property="createTime" label="创建时间" min-width="150"> </el-table-column>
|
||||||
<el-table-column label="更多信息" min-width="100">
|
<el-table-column label="更多信息" min-width="140">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
|
<el-link @click.prevent="showColumns(scope.row)" type="primary">字段</el-link>
|
||||||
<el-link class="ml5" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
|
<el-link class="ml5" @click.prevent="showTableIndex(scope.row)" type="success">索引</el-link>
|
||||||
<el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">SQL</el-link>
|
<el-link class="ml5"
|
||||||
|
v-if="tableCreateDialog.enableEditTypes.indexOf(tableCreateDialog.type) > -1"
|
||||||
|
@click.prevent="openEditTable(scope.row)" type="warning">编辑表</el-link>
|
||||||
|
<el-link class="ml5" @click.prevent="showCreateDdl(scope.row)" type="info">DDL</el-link>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" min-width="80">
|
<el-table-column label="操作" min-width="80">
|
||||||
@@ -159,19 +161,18 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog width="90%" :title="`${sqlExecLogDialog.title} - SQL执行记录`" :before-close="onBeforeCloseSqlExecDialog"
|
||||||
width="90%"
|
v-model="sqlExecLogDialog.visible">
|
||||||
:title="`${sqlExecLogDialog.title} - SQL执行记录`"
|
|
||||||
:before-close="onBeforeCloseSqlExecDialog"
|
|
||||||
v-model="sqlExecLogDialog.visible"
|
|
||||||
>
|
|
||||||
<div class="toolbar">
|
<div class="toolbar">
|
||||||
<el-select v-model="sqlExecLogDialog.query.db" placeholder="请选择数据库" filterable clearable>
|
<el-select v-model="sqlExecLogDialog.query.db" placeholder="请选择数据库" filterable clearable>
|
||||||
<el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item"> </el-option>
|
<el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item">
|
||||||
|
</el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5" style="width: 180px" />
|
<el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5"
|
||||||
|
style="width: 180px" />
|
||||||
<el-select v-model="sqlExecLogDialog.query.type" placeholder="请选择操作类型" clearable class="ml5">
|
<el-select v-model="sqlExecLogDialog.query.type" placeholder="请选择操作类型" clearable class="ml5">
|
||||||
<el-option v-for="item in enums.DbSqlExecTypeEnum" :key="item.value" :label="item.label" :value="item.value"> </el-option>
|
<el-option v-for="item in enums.DbSqlExecTypeEnum as any" :key="item.value" :label="item.label"
|
||||||
|
:value="item.value"> </el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
|
<el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -180,9 +181,14 @@
|
|||||||
<el-table-column prop="table" label="表" min-width="60" show-overflow-tooltip> </el-table-column>
|
<el-table-column prop="table" label="表" min-width="60" show-overflow-tooltip> </el-table-column>
|
||||||
<el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
|
<el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.UPDATE.value" color="#E4F5EB" size="small">UPDATE</el-tag>
|
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value" color="#E4F5EB"
|
||||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.DELETE.value" color="#F9E2AE" size="small">DELETE</el-tag>
|
size="small">UPDATE</el-tag>
|
||||||
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.INSERT.value" color="#A8DEE0" size="small">INSERT</el-tag>
|
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value" color="#F9E2AE"
|
||||||
|
size="small">DELETE</el-tag>
|
||||||
|
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['INSERT'].value" color="#A8DEE0"
|
||||||
|
size="small">INSERT</el-tag>
|
||||||
|
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['QUERY'].value" color="#A8DEE0"
|
||||||
|
size="small">QUERY</el-tag>
|
||||||
</template>
|
</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>
|
||||||
@@ -197,31 +203,23 @@
|
|||||||
<el-table-column label="操作" min-width="50" fixed="right">
|
<el-table-column label="操作" min-width="50" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-link
|
<el-link
|
||||||
v-if="scope.row.type == enums.DbSqlExecTypeEnum.UPDATE.value || scope.row.type == enums.DbSqlExecTypeEnum.DELETE.value"
|
v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value || scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value"
|
||||||
type="primary"
|
type="primary" plain size="small" :underline="false" @click="onShowRollbackSql(scope.row)">
|
||||||
plain
|
还原SQL</el-link>
|
||||||
size="small"
|
|
||||||
:underline="false"
|
|
||||||
@click="onShowRollbackSql(scope.row)"
|
|
||||||
>还原SQL</el-link
|
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<el-row style="margin-top: 20px" type="flex" justify="end">
|
<el-row style="margin-top: 20px" type="flex" justify="end">
|
||||||
<el-pagination
|
<el-pagination style="text-align: right" @current-change="handleSqlExecPageChange"
|
||||||
style="text-align: right"
|
:total="sqlExecLogDialog.total" layout="prev, pager, next, total, jumper"
|
||||||
@current-change="handleSqlExecPageChange"
|
v-model:current-page="sqlExecLogDialog.query.pageNum" :page-size="sqlExecLogDialog.query.pageSize">
|
||||||
:total="sqlExecLogDialog.total"
|
</el-pagination>
|
||||||
layout="prev, pager, next, total, jumper"
|
|
||||||
v-model:current-page="sqlExecLogDialog.query.pageNum"
|
|
||||||
:page-size="sqlExecLogDialog.query.pageSize"
|
|
||||||
></el-pagination>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
|
<el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
|
||||||
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql" size="small"> </el-input>
|
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql"
|
||||||
|
size="small"> </el-input>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
|
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
|
||||||
@@ -235,59 +233,87 @@
|
|||||||
|
|
||||||
<el-dialog width="40%" :title="`${chooseTableName} 索引信息`" v-model="indexDialog.visible">
|
<el-dialog width="40%" :title="`${chooseTableName} 索引信息`" v-model="indexDialog.visible">
|
||||||
<el-table border stripe :data="indexDialog.indexs" size="small">
|
<el-table border stripe :data="indexDialog.indexs" size="small">
|
||||||
<el-table-column prop="indexName" label="索引名" show-overflow-tooltip> </el-table-column>
|
<el-table-column prop="indexName" label="索引名" min-width="120" show-overflow-tooltip> </el-table-column>
|
||||||
<el-table-column prop="columnName" label="列名" show-overflow-tooltip> </el-table-column>
|
<el-table-column prop="columnName" label="列名" min-width="120" show-overflow-tooltip> </el-table-column>
|
||||||
<el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
|
<el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
|
||||||
<el-table-column prop="indexType" label="类型"> </el-table-column>
|
<el-table-column prop="indexType" label="类型"> </el-table-column>
|
||||||
<el-table-column prop="indexComment" label="备注" min-width="230" show-overflow-tooltip> </el-table-column>
|
<el-table-column prop="indexComment" label="备注" min-width="130" show-overflow-tooltip>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog width="55%" :title="`${chooseTableName} Create-DDL`" v-model="ddlDialog.visible">
|
<el-dialog width="55%" :title="`${chooseTableName} Create-DDL`" v-model="ddlDialog.visible">
|
||||||
<el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl" size="small"> </el-input>
|
<el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl"
|
||||||
|
size="small"> </el-input>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<db-edit
|
<el-dialog v-model="infoDialog.visible">
|
||||||
@val-change="valChange"
|
<el-descriptions title="详情" :column="3" border>
|
||||||
:title="dbEditDialog.title"
|
<el-descriptions-item :span="1.5" label="id">{{ infoDialog.data.id }}</el-descriptions-item>
|
||||||
v-model:visible="dbEditDialog.visible"
|
<el-descriptions-item :span="1.5" label="名称">{{ infoDialog.data.name }}</el-descriptions-item>
|
||||||
v-model:db="dbEditDialog.data"
|
|
||||||
></db-edit>
|
<el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data.tagPath }}</el-descriptions-item>
|
||||||
<create-table :dbId="dbId" :db="db" v-model:visible="tableCreateDialog.visible"></create-table>
|
|
||||||
|
<el-descriptions-item :span="2" label="主机">{{ infoDialog.data.host }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="1" label="端口">{{ infoDialog.data.port }}</el-descriptions-item>
|
||||||
|
|
||||||
|
<el-descriptions-item :span="2" label="用户名">{{ infoDialog.data.username }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="1" label="类型">{{ infoDialog.data.type }}</el-descriptions-item>
|
||||||
|
|
||||||
|
<el-descriptions-item :span="3" label="连接参数">{{ infoDialog.data.params }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="3" label="备注">{{ infoDialog.data.remark }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data.database }}</el-descriptions-item>
|
||||||
|
|
||||||
|
<el-descriptions-item :span="3" label="SSH隧道">{{ infoDialog.data.sshTunnelMachineId > 0 ? '是' : '否' }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
|
||||||
|
<el-descriptions-item :span="2" label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="1" label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
|
||||||
|
|
||||||
|
<el-descriptions-item :span="2" label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :span="1" label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<db-edit @val-change="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible"
|
||||||
|
v-model:db="dbEditDialog.data"></db-edit>
|
||||||
|
<create-table :title="tableCreateDialog.title" :active-name="tableCreateDialog.activeName" :dbId="dbId" :db="db"
|
||||||
|
:data="tableCreateDialog.data" v-model:visible="tableCreateDialog.visible" @submit-sql="onSubmitSql">
|
||||||
|
</create-table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang='ts'>
|
<script lang='ts' setup>
|
||||||
import { toRefs, reactive, computed, onMounted, defineComponent } 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 { tagApi } from '../tag/api.ts';
|
import { Search as SearchIcon } from '@element-plus/icons-vue'
|
||||||
|
import { tagApi } from '../tag/api';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { dateFormat } from '@/common/utils/date';
|
||||||
|
import TagInfo from '../component/TagInfo.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
||||||
name: 'DbList',
|
const CreateTable = defineAsyncComponent(() => import('./CreateTable.vue'));
|
||||||
components: {
|
|
||||||
DbEdit,
|
const permissions = {
|
||||||
CreateTable,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const state = reactive({
|
|
||||||
dbId: 0,
|
|
||||||
db: '',
|
|
||||||
permissions: {
|
|
||||||
saveDb: 'db:save',
|
saveDb: 'db:save',
|
||||||
delDb: 'db:del',
|
delDb: 'db:del',
|
||||||
},
|
}
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
row: {},
|
||||||
|
dbId: 0,
|
||||||
|
db: '',
|
||||||
tags: [],
|
tags: [],
|
||||||
chooseId: null,
|
chooseId: null as any,
|
||||||
/**
|
/**
|
||||||
* 选中的数据
|
* 选中的数据
|
||||||
*/
|
*/
|
||||||
@@ -302,6 +328,10 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
datas: [],
|
datas: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
|
infoDialog: {
|
||||||
|
visible: false,
|
||||||
|
data: null as any,
|
||||||
|
},
|
||||||
showDumpInfo: false,
|
showDumpInfo: false,
|
||||||
dumpInfo: {
|
dumpInfo: {
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -351,14 +381,53 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
dbEditDialog: {
|
dbEditDialog: {
|
||||||
visible: false,
|
visible: false,
|
||||||
data: null,
|
data: null as any,
|
||||||
title: '新增数据库',
|
title: '新增数据库',
|
||||||
},
|
},
|
||||||
tableCreateDialog: {
|
tableCreateDialog: {
|
||||||
|
title: '创建表',
|
||||||
visible: false,
|
visible: false,
|
||||||
|
activeName: '1',
|
||||||
|
type: '',
|
||||||
|
enableEditTypes: ['mysql'], // 支持"编辑表"的数据库类型
|
||||||
|
data: { // 修改表时,传递修改数据
|
||||||
|
edit: false,
|
||||||
|
row: {},
|
||||||
|
indexs: [],
|
||||||
|
columns: [],
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
filterDb: {
|
||||||
|
param: '',
|
||||||
|
cache: [],
|
||||||
|
list: [],
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
dbId,
|
||||||
|
db,
|
||||||
|
tags,
|
||||||
|
chooseId,
|
||||||
|
query,
|
||||||
|
datas,
|
||||||
|
total,
|
||||||
|
infoDialog,
|
||||||
|
showDumpInfo,
|
||||||
|
dumpInfo,
|
||||||
|
sqlExecLogDialog,
|
||||||
|
rollbackSqlDialog,
|
||||||
|
chooseTableName,
|
||||||
|
tableInfoDialog,
|
||||||
|
columnDialog,
|
||||||
|
indexDialog,
|
||||||
|
ddlDialog,
|
||||||
|
dbEditDialog,
|
||||||
|
tableCreateDialog,
|
||||||
|
filterDb,
|
||||||
|
} = toRefs(state)
|
||||||
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
search();
|
search();
|
||||||
});
|
});
|
||||||
@@ -407,6 +476,11 @@ export default defineComponent({
|
|||||||
search();
|
search();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showInfo = (info: any) => {
|
||||||
|
state.infoDialog.data = info;
|
||||||
|
state.infoDialog.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
const getTags = async () => {
|
const getTags = async () => {
|
||||||
state.tags = await tagApi.getAccountTags.request(null);
|
state.tags = await tagApi.getAccountTags.request(null);
|
||||||
};
|
};
|
||||||
@@ -499,7 +573,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const onShowRollbackSql = async (sqlExecLog: any) => {
|
const onShowRollbackSql = async (sqlExecLog: any) => {
|
||||||
const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
|
const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
|
||||||
const primaryKey = columns[0].columnName;
|
const primaryKey = getPrimaryKey(columns);
|
||||||
const oldValue = JSON.parse(sqlExecLog.oldValue);
|
const oldValue = JSON.parse(sqlExecLog.oldValue);
|
||||||
|
|
||||||
const rollbackSqls = [];
|
const rollbackSqls = [];
|
||||||
@@ -529,6 +603,14 @@ export default defineComponent({
|
|||||||
state.rollbackSqlDialog.visible = true;
|
state.rollbackSqlDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPrimaryKey = (columns: any) => {
|
||||||
|
const col = columns.find((c: any) => c.columnKey == 'PRI');
|
||||||
|
if (col) {
|
||||||
|
return col.columnName;
|
||||||
|
}
|
||||||
|
return columns[0].columnName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 包装值,如果值类型为number则直接返回,其他则需要使用''包装
|
* 包装值,如果值类型为number则直接返回,其他则需要使用''包装
|
||||||
*/
|
*/
|
||||||
@@ -544,7 +626,9 @@ export default defineComponent({
|
|||||||
state.tableInfoDialog.visible = true;
|
state.tableInfoDialog.visible = true;
|
||||||
try {
|
try {
|
||||||
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id, db });
|
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: row.id, db });
|
||||||
|
state.tableCreateDialog.type = row.type
|
||||||
state.dbId = row.id;
|
state.dbId = row.id;
|
||||||
|
state.row = row;
|
||||||
state.db = db;
|
state.db = db;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.tableInfoDialog.visible = false;
|
state.tableInfoDialog.visible = false;
|
||||||
@@ -553,6 +637,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onSubmitSql = async (row: { tableName: string }) => {
|
||||||
|
await openEditTable(row)
|
||||||
|
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.dbId, db: state.db });
|
||||||
|
}
|
||||||
|
|
||||||
const closeTableInfo = () => {
|
const closeTableInfo = () => {
|
||||||
state.showDumpInfo = false;
|
state.showDumpInfo = false;
|
||||||
state.tableInfoDialog.visible = false;
|
state.tableInfoDialog.visible = false;
|
||||||
@@ -588,7 +677,7 @@ export default defineComponent({
|
|||||||
db: state.db,
|
db: state.db,
|
||||||
tableName: row.tableName,
|
tableName: row.tableName,
|
||||||
});
|
});
|
||||||
state.ddlDialog.ddl = res[0]['Create Table'];
|
state.ddlDialog.ddl = res;
|
||||||
state.ddlDialog.visible = true;
|
state.ddlDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -614,35 +703,49 @@ export default defineComponent({
|
|||||||
} catch (err) { }
|
} catch (err) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
// 点击查看时初始化数据
|
||||||
...toRefs(state),
|
const selectDb = (row: any) => {
|
||||||
dateFormat,
|
state.filterDb.param = ''
|
||||||
getTags,
|
state.filterDb.cache = row;
|
||||||
filterTableInfos,
|
state.filterDb.list = row;
|
||||||
enums,
|
}
|
||||||
search,
|
|
||||||
choose,
|
// 输入字符过滤schema
|
||||||
handlePageChange,
|
const filterSchema = () => {
|
||||||
editDb,
|
if (state.filterDb.param) {
|
||||||
valChange,
|
state.filterDb.list = state.filterDb.cache.filter((a) => { return String(a).toLowerCase().indexOf(state.filterDb.param) > -1 })
|
||||||
deleteDb,
|
} else {
|
||||||
onShowSqlExec,
|
state.filterDb.list = state.filterDb.cache;
|
||||||
handleDumpTableSelectionChange,
|
}
|
||||||
dump,
|
}
|
||||||
onBeforeCloseSqlExecDialog,
|
|
||||||
handleSqlExecPageChange,
|
// 打开编辑表
|
||||||
searchSqlExecLog,
|
const openEditTable = async (row: any) => {
|
||||||
onShowRollbackSql,
|
|
||||||
showTableInfo,
|
state.tableCreateDialog.visible = true
|
||||||
closeTableInfo,
|
state.tableCreateDialog.activeName = '1'
|
||||||
showColumns,
|
|
||||||
showTableIndex,
|
if (row === false) {
|
||||||
showCreateDdl,
|
state.tableCreateDialog.data = { edit: false, row: {}, indexs: [], columns: [] }
|
||||||
dropTable,
|
state.tableCreateDialog.title = '创建表'
|
||||||
formatByteSize,
|
}
|
||||||
};
|
|
||||||
},
|
if (row.tableName) {
|
||||||
|
state.tableCreateDialog.title = '修改表'
|
||||||
|
let indexs = await dbApi.tableIndex.request({
|
||||||
|
id: state.chooseId,
|
||||||
|
db: state.db,
|
||||||
|
tableName: row.tableName,
|
||||||
});
|
});
|
||||||
|
let columns = await dbApi.columnMetadata.request({
|
||||||
|
id: state.chooseId,
|
||||||
|
db: state.db,
|
||||||
|
tableName: row.tableName,
|
||||||
|
});
|
||||||
|
state.tableCreateDialog.data = { edit: true, row, indexs, columns }
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user