Compare commits
121 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8faf1831d9 | ||
|
|
d86ef0a9ab | ||
|
|
c2bb0c589e | ||
|
|
9994f20a2c | ||
|
|
b5014c307f | ||
|
|
75bd4ca3df | ||
|
|
d00bd2ed72 | ||
|
|
e444500835 | ||
|
|
6709135a0b | ||
|
|
59a7ff9ac7 | ||
|
|
172c729535 | ||
|
|
ac5198db1c | ||
|
|
5c5c2c2037 | ||
|
|
1db990b554 | ||
|
|
70c887a16a | ||
|
|
2430c4f6aa | ||
|
|
84fd14c129 | ||
|
|
a376a82240 | ||
|
|
e1e03dc09a | ||
|
|
790d644c34 | ||
|
|
9de8dae954 | ||
|
|
57361d8241 | ||
|
|
b347bd7ef5 | ||
|
|
070c8ac0da | ||
|
|
e221c2f42e | ||
|
|
c7bab3a71b | ||
|
|
82c17a51a2 | ||
|
|
e4447e6bc2 | ||
|
|
b9570d9a5f | ||
|
|
01e8a2c14d | ||
|
|
64bd51c3b0 | ||
|
|
54ab34df3f | ||
|
|
206490ba3e | ||
|
|
16612d2c9c | ||
|
|
6b65605360 | ||
|
|
bb37ed3b95 | ||
|
|
d102cc8c08 | ||
|
|
a6df74d63d | ||
|
|
f79760943e | ||
|
|
a40ec21a05 | ||
|
|
43230267b6 | ||
|
|
0ae99cdaf9 | ||
|
|
f234c72514 | ||
|
|
76527d95bd | ||
|
|
27c53385f2 | ||
|
|
a1b25e9766 | ||
|
|
abad0ed481 | ||
|
|
eddda41291 | ||
|
|
d9adf0fd25 | ||
|
|
0ce82b41ba | ||
|
|
37026f3269 | ||
|
|
3155380f16 | ||
|
|
f2b0f294d8 | ||
|
|
12f63ef3dd | ||
|
|
a1303b52eb | ||
|
|
10f6b03fb5 | ||
|
|
45d2449221 | ||
|
|
9e5f146e05 | ||
|
|
2b91bbe185 | ||
|
|
747ea6404d | ||
|
|
ccfc6bd1df | ||
|
|
361eafedae | ||
|
|
a64b894b08 | ||
|
|
0ad805c170 | ||
|
|
ba82b5b516 | ||
|
|
f04b82c933 | ||
|
|
23b137ab9b | ||
|
|
a4d3a4627a | ||
|
|
77ae6e3bab | ||
|
|
e0f1f40ba0 | ||
|
|
d300f604f1 | ||
|
|
2c2c0ff40b | ||
|
|
b4ddbbd38f | ||
|
|
7544288451 | ||
|
|
41443dccc0 | ||
|
|
22e218fc5f | ||
|
|
4da0d1abaa | ||
|
|
6563b53436 | ||
|
|
fac71a4794 | ||
|
|
92dff6fdc3 | ||
|
|
a1eca3d691 | ||
|
|
6681dc1057 | ||
|
|
829a68feaa | ||
|
|
72677e270d | ||
|
|
dd4ac390de | ||
|
|
0bd7d38c23 | ||
|
|
ead3b0d0d8 | ||
|
|
4b973b22a4 | ||
|
|
e4e68d02bc | ||
|
|
ef8822d671 | ||
|
|
8e75e1f6ef | ||
|
|
08c381fa60 | ||
|
|
d7a10d4032 | ||
|
|
c324a030f9 | ||
|
|
b618b8f93b | ||
|
|
4d2e110e1e | ||
|
|
ecd79a2e15 | ||
|
|
f4f297d3f7 | ||
|
|
b5549c0fae | ||
|
|
929bfb3200 | ||
|
|
7d3593a944 | ||
|
|
9e0db2bc99 | ||
|
|
25b0d276b3 | ||
|
|
0cb7a7cf83 | ||
|
|
52f72400ba | ||
|
|
0eaff33168 | ||
|
|
086dbf278b | ||
|
|
57a5e237ae | ||
|
|
eee6cf7b14 | ||
|
|
b9c6ac8d6d | ||
|
|
618d782af3 | ||
|
|
d0ac7de4cb | ||
|
|
baf8053613 | ||
|
|
b973d63331 | ||
|
|
85b64d7e8d | ||
|
|
86ad183c41 | ||
|
|
f7b685cfad | ||
|
|
649116a0b8 | ||
|
|
899a3a8243 | ||
|
|
d51cd4b289 | ||
|
|
537b179e78 |
@@ -18,7 +18,7 @@ WORKDIR /mayfly
|
|||||||
# Copy the go source for building server
|
# Copy the go source for building server
|
||||||
COPY server .
|
COPY server .
|
||||||
|
|
||||||
RUN go mod download
|
RUN go mod tidy && go mod download
|
||||||
|
|
||||||
COPY --from=fe-builder /mayfly/dist /mayfly/static/static
|
COPY --from=fe-builder /mayfly/dist /mayfly/static/static
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ FROM alpine:3.16
|
|||||||
RUN apk add --no-cache ca-certificates bash expat
|
RUN apk add --no-cache ca-certificates bash expat
|
||||||
|
|
||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
RUN ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone
|
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
WORKDIR /mayfly
|
WORKDIR /mayfly
|
||||||
|
|
||||||
|
|||||||
18
README.md
@@ -1,19 +1,19 @@
|
|||||||
# 🌈mayfly-go
|
# 🌈mayfly-go
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://gitee.com/objs/mayfly-go" target="_blank">
|
<a href="https://gitee.com/dromara/mayfly-go" target="_blank">
|
||||||
<img src="https://gitee.com/objs/mayfly-go/badge/star.svg?theme=white" alt="star"/>
|
<img src="https://gitee.com/dromara/mayfly-go/badge/star.svg?theme=white" alt="star"/>
|
||||||
<img src="https://gitee.com/objs/mayfly-go/badge/fork.svg" alt="fork"/>
|
<img src="https://gitee.com/dromara/mayfly-go/badge/fork.svg" alt="fork"/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/may-fly/mayfly-go" target="_blank">
|
<a href="https://github.com/dromara/mayfly-go" target="_blank">
|
||||||
<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/dromara/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/dromara/mayfly-go.svg?style=social" alt="github fork"/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://hub.docker.com/r/mayflygo/mayfly-go/tags" target="_blank">
|
<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"/>
|
<img src="https://img.shields.io/docker/pulls/mayflygo/mayfly-go.svg?label=docker%20pulls&color=fac858" alt="docker pulls"/>
|
||||||
</a>
|
</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.20%2B-yellow.svg" alt="golang"/>
|
<img src="https://img.shields.io/badge/Golang-1.21%2B-yellow.svg" alt="golang"/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://cn.vuejs.org" target="_blank">
|
<a href="https://cn.vuejs.org" target="_blank">
|
||||||
<img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
|
<img src="https://img.shields.io/badge/Vue-3.x-green.svg" alt="vue">
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
### 介绍
|
### 介绍
|
||||||
|
|
||||||
web 版 **linux(终端[终端回放] 文件 脚本 进程)、数据库(mysql postgres)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
|
web 版 **linux(终端[终端回放] 文件 脚本 进程 计划任务)、数据库(mysql postgres)、redis(单机 哨兵 集群)、mongo 统一管理操作平台**
|
||||||
|
|
||||||
### 开发语言与主要框架
|
### 开发语言与主要框架
|
||||||
|
|
||||||
@@ -100,4 +100,4 @@ http://go.mayfly.run
|
|||||||
|
|
||||||
#### 💌 支持作者
|
#### 💌 支持作者
|
||||||
|
|
||||||
如果觉得项目不错,或者已经在使用了,希望你可以去 <a target="_blank" href="https://github.com/may-fly/mayfly-go">Github</a> 或者 <a target="_blank" href="https://gitee.com/objs/mayfly-go">Gitee</a> 帮我点个 ⭐ Star,这将是对我极大的鼓励与支持。
|
如果觉得项目不错,或者已经在使用了,希望你可以去 <a target="_blank" href="https://github.com/dromara/mayfly-go">Github</a> 或者 <a target="_blank" href="https://gitee.com/dromara/mayfly-go">Gitee</a> 帮我点个 ⭐ Star,这将是对我极大的鼓励与支持。
|
||||||
|
|||||||
@@ -74,12 +74,13 @@ function build() {
|
|||||||
# fi
|
# fi
|
||||||
|
|
||||||
if [ "${copyDocScript}" == "1" ] ; then
|
if [ "${copyDocScript}" == "1" ] ; then
|
||||||
echo_green "拷贝脚本等资源文件[config.yml.example、mayfly-go.sql、readme.txt、startup.sh、shutdown.sh]"
|
echo_green "拷贝脚本等资源文件[config.yml.example、mayfly-go.sql、mayfly-go.sqlite、readme.txt、startup.sh、shutdown.sh]"
|
||||||
cp ${server_folder}/config.yml.example ${toFolder}
|
cp ${server_folder}/config.yml.example ${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}/resources/script/startup.sh ${toFolder}
|
||||||
cp ${server_folder}/shutdown.sh ${toFolder}
|
cp ${server_folder}/resources/script/shutdown.sh ${toFolder}
|
||||||
|
cp ${server_folder}/resources/script/sql/mayfly-go.sql ${toFolder}
|
||||||
|
cp ${server_folder}/resources/data/mayfly-go.sqlite ${toFolder}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
|
echo_yellow ">>>>>>>>>>>>>>>>>>>${os}-${arch}打包构建完成<<<<<<<<<<<<<<<<<<<<\n"
|
||||||
|
|||||||
@@ -3,3 +3,7 @@ ENV = 'development'
|
|||||||
|
|
||||||
# 本地环境接口地址
|
# 本地环境接口地址
|
||||||
VITE_API_URL = '/api'
|
VITE_API_URL = '/api'
|
||||||
|
|
||||||
|
# 路由模式
|
||||||
|
# Optional: hash | history
|
||||||
|
VITE_ROUTER_MODE = hash
|
||||||
@@ -3,3 +3,7 @@ ENV = 'production'
|
|||||||
|
|
||||||
# 线上环境接口地址
|
# 线上环境接口地址
|
||||||
VITE_API_URL = '/api'
|
VITE_API_URL = '/api'
|
||||||
|
|
||||||
|
# 路由模式
|
||||||
|
# Optional: hash | history
|
||||||
|
VITE_ROUTER_MODE = hash
|
||||||
@@ -70,7 +70,7 @@ module.exports = {
|
|||||||
'no-unused-vars': 'error',
|
'no-unused-vars': 'error',
|
||||||
'no-v-model-argument': 'off',
|
'no-v-model-argument': 'off',
|
||||||
'no-case-declarations': 'off',
|
'no-case-declarations': 'off',
|
||||||
'no-console': 'error',
|
// 'no-console': 'error',
|
||||||
'no-redeclare': 'off',
|
'no-redeclare': 'off',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -10,48 +10,53 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.1.0",
|
"@element-plus/icons-vue": "^2.1.0",
|
||||||
"asciinema-player": "^3.5.0",
|
"@vueuse/core": "^10.7.0",
|
||||||
"axios": "^1.4.0",
|
"asciinema-player": "^3.6.2",
|
||||||
|
"axios": "^1.6.2",
|
||||||
|
"clipboard": "^2.0.11",
|
||||||
"countup.js": "^2.7.0",
|
"countup.js": "^2.7.0",
|
||||||
"cropperjs": "^1.5.11",
|
"cropperjs": "^1.5.11",
|
||||||
"echarts": "^5.4.0",
|
"echarts": "^5.4.3",
|
||||||
"element-plus": "^2.3.8",
|
"element-plus": "^2.4.3",
|
||||||
"jsencrypt": "^3.3.1",
|
"js-base64": "^3.7.5",
|
||||||
|
"jsencrypt": "^3.3.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"monaco-editor": "^0.41.0",
|
"monaco-editor": "^0.45.0",
|
||||||
"monaco-sql-languages": "^0.11.0",
|
"monaco-sql-languages": "^0.11.0",
|
||||||
"monaco-themes": "^0.4.4",
|
"monaco-themes": "^0.4.4",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.7",
|
||||||
"qrcode.vue": "^3.4.0",
|
"qrcode.vue": "^3.4.1",
|
||||||
"screenfull": "^6.0.2",
|
"screenfull": "^6.0.2",
|
||||||
"sortablejs": "^1.13.0",
|
"sortablejs": "^1.15.0",
|
||||||
"sql-formatter": "^12.1.2",
|
"splitpanes": "^3.1.5",
|
||||||
"vue": "^3.3.4",
|
"sql-formatter": "^14.0.0",
|
||||||
"vue-clipboard3": "^1.0.1",
|
"uuid": "^9.0.1",
|
||||||
"vue-router": "^4.2.4",
|
"vue": "^3.3.11",
|
||||||
"xterm": "^5.2.1",
|
"vue-router": "^4.2.5",
|
||||||
"xterm-addon-fit": "^0.7.0"
|
"xterm": "^5.3.0",
|
||||||
|
"xterm-addon-fit": "^0.8.0",
|
||||||
|
"xterm-addon-search": "^0.13.0",
|
||||||
|
"xterm-addon-web-links": "^0.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash": "^4.14.178",
|
"@types/lodash": "^4.14.178",
|
||||||
"@types/node": "^15.6.0",
|
"@types/node": "^15.6.0",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/sortablejs": "^1.10.6",
|
"@types/sortablejs": "^1.15.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||||
"@typescript-eslint/parser": "^4.23.0",
|
"@typescript-eslint/parser": "^6.7.4",
|
||||||
"@vitejs/plugin-vue": "^4.0.0",
|
"@vitejs/plugin-vue": "^4.5.1",
|
||||||
"@vue/compiler-sfc": "^3.0.11",
|
"@vue/compiler-sfc": "^3.3.10",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^16.3.1",
|
||||||
"eslint": "^8.35.0",
|
"eslint": "^8.35.0",
|
||||||
"eslint-plugin-vue": "^8.2.0",
|
"eslint-plugin-vue": "^9.19.2",
|
||||||
"prettier": "^2.3.0",
|
"prettier": "^3.1.0",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.69.0",
|
||||||
"sass-loader": "^13.2.0",
|
"typescript": "^5.3.2",
|
||||||
"typescript": "^5.0.2",
|
"vite": "^5.0.7",
|
||||||
"vite": "^4.4.9",
|
"vue-eslint-parser": "^9.3.2"
|
||||||
"vue-eslint-parser": "^9.1.1"
|
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
|
|||||||
@@ -1,20 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="h100">
|
||||||
|
<el-watermark
|
||||||
|
:zIndex="10000000"
|
||||||
|
:width="210"
|
||||||
|
v-if="themeConfig.isWatermark"
|
||||||
|
:font="{ color: 'rgba(180, 180, 180, 0.5)' }"
|
||||||
|
:content="themeConfig.watermarkText"
|
||||||
|
class="h100"
|
||||||
|
>
|
||||||
<router-view v-show="themeConfig.lockScreenTime !== 0" />
|
<router-view v-show="themeConfig.lockScreenTime !== 0" />
|
||||||
|
</el-watermark>
|
||||||
|
<router-view v-if="!themeConfig.isWatermark" v-show="themeConfig.lockScreenTime !== 0" />
|
||||||
|
|
||||||
<LockScreen v-if="themeConfig.isLockScreen" />
|
<LockScreen v-if="themeConfig.isLockScreen" />
|
||||||
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
|
<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime !== 0" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="app">
|
<script setup lang="ts" name="app">
|
||||||
import { ref, onBeforeMount, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
import { ref, onMounted, onUnmounted, nextTick, watch } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
// import { useTagsViewRoutes } from '@/store/tagsViewRoutes';
|
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { getLocal } from '@/common/utils/storage';
|
import { getLocal } from '@/common/utils/storage';
|
||||||
import LockScreen from '@/views/layout/lockScreen/index.vue';
|
import LockScreen from '@/layout/lockScreen/index.vue';
|
||||||
import Setings from '@/views/layout/navBars/breadcrumb/setings.vue';
|
import Setings from '@/layout/navBars/breadcrumb/setings.vue';
|
||||||
import Watermark from '@/common/utils/wartermark';
|
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
|
import { getThemeConfig } from './common/utils/storage';
|
||||||
|
import { useWatermark } from '@/common/sysconfig';
|
||||||
|
import { useIntervalFn } from '@vueuse/core';
|
||||||
|
|
||||||
const setingsRef = ref();
|
const setingsRef = ref();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -27,14 +41,6 @@ const openSetingsDrawer = () => {
|
|||||||
setingsRef.value.openDrawer();
|
setingsRef.value.openDrawer();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设置初始化,防止刷新时恢复默认
|
|
||||||
onBeforeMount(() => {
|
|
||||||
// 设置批量第三方 icon 图标
|
|
||||||
// setIntroduction.cssCdn();
|
|
||||||
// // 设置批量第三方 js
|
|
||||||
// setIntroduction.jsCdn();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -42,14 +48,56 @@ onMounted(() => {
|
|||||||
mittBus.on('openSetingsDrawer', () => {
|
mittBus.on('openSetingsDrawer', () => {
|
||||||
openSetingsDrawer();
|
openSetingsDrawer();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取缓存中的布局配置
|
// 获取缓存中的布局配置
|
||||||
if (getLocal('themeConfig')) {
|
const tc = getThemeConfig();
|
||||||
themeConfigStores.setThemeConfig({ themeConfig: getLocal('themeConfig') });
|
if (tc) {
|
||||||
|
themeConfigStores.setThemeConfig({ themeConfig: tc });
|
||||||
document.documentElement.style.cssText = getLocal('themeConfigStyle');
|
document.documentElement.style.cssText = getLocal('themeConfigStyle');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 是否开启水印
|
||||||
|
useWatermark().then((res) => {
|
||||||
|
themeConfigStores.setWatermarkConfig(res);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 监听 themeConfig isWartermark配置文件的变化
|
||||||
|
watch(
|
||||||
|
() => themeConfig.value.isWatermark,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setWatermarkContent();
|
||||||
|
refreshWatermarkTime();
|
||||||
|
resume();
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 刷新水印时间
|
||||||
|
const { pause, resume } = useIntervalFn(() => {
|
||||||
|
if (!themeConfig.value.isWatermark) {
|
||||||
|
pause();
|
||||||
|
}
|
||||||
|
refreshWatermarkTime();
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
const setWatermarkContent = () => {
|
||||||
|
themeConfigStores.setWatermarkUser();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新水印时间
|
||||||
|
*/
|
||||||
|
const refreshWatermarkTime = () => {
|
||||||
|
themeConfigStores.setWatermarkNowTime();
|
||||||
|
};
|
||||||
|
|
||||||
// 页面销毁时,关闭监听布局配置
|
// 页面销毁时,关闭监听布局配置
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
mittBus.off('openSetingsDrawer', () => {});
|
mittBus.off('openSetingsDrawer', () => {});
|
||||||
@@ -60,8 +108,6 @@ watch(
|
|||||||
() => route.path,
|
() => route.path,
|
||||||
() => {
|
() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 路由变化更新水印
|
|
||||||
Watermark.use();
|
|
||||||
document.title = `${route.meta.title} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
|
document.title = `${route.meta.title} - ${themeConfig.value.globalTitle}` || themeConfig.value.globalTitle;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,13 @@
|
|||||||
"unicode": "e8b7",
|
"unicode": "e8b7",
|
||||||
"unicode_decimal": 59575
|
"unicode_decimal": 59575
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "12295203",
|
||||||
|
"name": "达梦数据库",
|
||||||
|
"font_class": "db-dm",
|
||||||
|
"unicode": "e6f0",
|
||||||
|
"unicode_decimal": 59120
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "10055634",
|
"icon_id": "10055634",
|
||||||
"name": "云数据库MongoDB",
|
"name": "云数据库MongoDB",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 39 KiB |
1
mayfly_go_web/src/assets/image/401.svg
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 36 KiB |
1
mayfly_go_web/src/assets/image/404.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 458 KiB |
1
mayfly_go_web/src/assets/image/login-bg-main.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
19
mayfly_go_web/src/assets/image/login-bg-split.svg
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
@@ -1,4 +1,5 @@
|
|||||||
import request from './request';
|
import request from './request';
|
||||||
|
import { useApiFetch } from './useRequest';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可用于各模块定义各自api请求
|
* 可用于各模块定义各自api请求
|
||||||
@@ -14,11 +15,27 @@ class Api {
|
|||||||
*/
|
*/
|
||||||
method: string;
|
method: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求前处理函数
|
||||||
|
* param1: param请求参数
|
||||||
|
*/
|
||||||
|
beforeHandler: Function;
|
||||||
|
|
||||||
constructor(url: string, method: string) {
|
constructor(url: string, method: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置请求前处理回调函数
|
||||||
|
* @param func 请求前处理器
|
||||||
|
* @returns this
|
||||||
|
*/
|
||||||
|
withBeforeHandler(func: Function) {
|
||||||
|
this.beforeHandler = func;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取权限的完整url
|
* 获取权限的完整url
|
||||||
*/
|
*/
|
||||||
@@ -27,11 +44,34 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求对应的该api
|
* 响应式使用该api
|
||||||
|
* @param params 响应式params
|
||||||
|
* @param reqOptions 其他可选值
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
useApi<T>(params: any = null, reqOptions: RequestInit = {}) {
|
||||||
|
return useApiFetch<T>(this, params, reqOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch 请求对应的该api
|
||||||
* @param {Object} param 请求该api的参数
|
* @param {Object} param 请求该api的参数
|
||||||
*/
|
*/
|
||||||
request(param: any = null, options: any = null, headers: any = null): Promise<any> {
|
async request(param: any = null, options: any = {}): Promise<any> {
|
||||||
return request.request(this.method, this.url, param, headers, options);
|
const { execute, data } = this.useApi(param, options);
|
||||||
|
await execute();
|
||||||
|
return data.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* xhr 请求对应的该api
|
||||||
|
* @param {Object} param 请求该api的参数
|
||||||
|
*/
|
||||||
|
async xhrReq(param: any = null, options: any = {}): Promise<any> {
|
||||||
|
if (this.beforeHandler) {
|
||||||
|
this.beforeHandler(param);
|
||||||
|
}
|
||||||
|
return request.xhrReq(this.method, this.url, param, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 静态方法 **/
|
/** 静态方法 **/
|
||||||
@@ -79,3 +119,8 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Api;
|
export default Api;
|
||||||
|
|
||||||
|
export class PageRes {
|
||||||
|
list: any[] = [];
|
||||||
|
total: number = 0;
|
||||||
|
}
|
||||||
|
|||||||
9
mayfly_go_web/src/common/commonEnum.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import EnumValue from './Enum';
|
||||||
|
|
||||||
|
// 标签关联的资源类型
|
||||||
|
export const TagResourceTypeEnum = {
|
||||||
|
Machine: EnumValue.of(1, '机器'),
|
||||||
|
Db: EnumValue.of(2, '数据库'),
|
||||||
|
Redis: EnumValue.of(3, 'redis'),
|
||||||
|
Mongo: EnumValue.of(4, 'mongo'),
|
||||||
|
};
|
||||||
@@ -3,6 +3,10 @@ function getBaseApiUrl() {
|
|||||||
if (path == '/') {
|
if (path == '/') {
|
||||||
return window.location.host;
|
return window.location.host;
|
||||||
}
|
}
|
||||||
|
if (path.endsWith('/')) {
|
||||||
|
// 去除最后一个/
|
||||||
|
return window.location.host + path.replace(/\/$/, '');
|
||||||
|
}
|
||||||
return window.location.host + path;
|
return window.location.host + path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11,7 +15,7 @@ const config = {
|
|||||||
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
baseWsUrl: `${(window as any).globalConfig.BaseWsUrl || `${location.protocol == 'https:' ? 'wss:' : 'ws:'}//${getBaseApiUrl()}`}/api`,
|
||||||
|
|
||||||
// 系统版本
|
// 系统版本
|
||||||
version: 'v1.5.1',
|
version: 'v1.6.1',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ export default {
|
|||||||
otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
|
otpVerify: (param: any) => request.post('/auth/accounts/otp-verify', param),
|
||||||
getPublicKey: () => request.get('/common/public-key'),
|
getPublicKey: () => request.get('/common/public-key'),
|
||||||
getConfigValue: (params: any) => request.get('/sys/configs/value', params),
|
getConfigValue: (params: any) => request.get('/sys/configs/value', params),
|
||||||
oauth2LoginConfig: () => request.get('/sys/configs/oauth2-login'),
|
oauth2LoginConfig: () => request.get('/auth/oauth2-config'),
|
||||||
changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param),
|
changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param),
|
||||||
captcha: () => request.get('/sys/captcha'),
|
captcha: () => request.get('/sys/captcha'),
|
||||||
logout: () => request.post('/auth/accounts/logout'),
|
logout: () => request.post('/auth/accounts/logout'),
|
||||||
getPermissions: () => request.get('/sys/accounts/permissions'),
|
getPermissions: () => request.get('/sys/accounts/permissions'),
|
||||||
oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
|
oauth2Callback: (params: any) => request.get('/auth/oauth2/callback', params),
|
||||||
getLdapEnabled: () => request.get("/auth/ldap/enabled"),
|
getLdapEnabled: () => request.get('/auth/ldap/enabled'),
|
||||||
ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
|
ldapLogin: (param: any) => request.post('/auth/ldap/login', param),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,21 @@
|
|||||||
import router from '../router';
|
import router from '../router';
|
||||||
import Axios from 'axios';
|
|
||||||
import config from './config';
|
import config from './config';
|
||||||
import { getSession } from './utils/storage';
|
import { getClientId, getToken } from './utils/storage';
|
||||||
import { templateResolve } from './utils/string';
|
import { templateResolve } from './utils/string';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useApiFetch } from './useRequest';
|
||||||
|
import Api from './Api';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
request,
|
||||||
|
xhrReq,
|
||||||
|
get,
|
||||||
|
post,
|
||||||
|
put,
|
||||||
|
del,
|
||||||
|
getApiUrl,
|
||||||
|
};
|
||||||
|
|
||||||
export interface Result {
|
export interface Result {
|
||||||
/**
|
/**
|
||||||
@@ -20,7 +32,7 @@ export interface Result {
|
|||||||
data?: any;
|
data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResultEnum {
|
export enum ResultEnum {
|
||||||
SUCCESS = 200,
|
SUCCESS = 200,
|
||||||
ERROR = 400,
|
ERROR = 400,
|
||||||
PARAM_ERROR = 405,
|
PARAM_ERROR = 405,
|
||||||
@@ -28,8 +40,9 @@ enum ResultEnum {
|
|||||||
NO_PERMISSION = 501,
|
NO_PERMISSION = 501,
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseUrl: string = config.baseApiUrl;
|
export const baseUrl: string = config.baseApiUrl;
|
||||||
const baseWsUrl: string = config.baseWsUrl;
|
// const baseUrl: string = 'http://localhost:18888/api';
|
||||||
|
// const baseWsUrl: string = config.baseWsUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知错误消息
|
* 通知错误消息
|
||||||
@@ -41,19 +54,20 @@ function notifyErrorMsg(msg: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create an axios instance
|
// create an axios instance
|
||||||
const service = Axios.create({
|
const axiosInst = axios.create({
|
||||||
baseURL: baseUrl, // url = base url + request url
|
baseURL: baseUrl, // url = base url + request url
|
||||||
timeout: 20000, // request timeout
|
timeout: 60000, // request timeout
|
||||||
});
|
});
|
||||||
|
|
||||||
// request interceptor
|
// request interceptor
|
||||||
service.interceptors.request.use(
|
axiosInst.interceptors.request.use(
|
||||||
(config: any) => {
|
(config: any) => {
|
||||||
// do something before request is sent
|
// do something before request is sent
|
||||||
const token = getSession('token');
|
const token = getToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
// 设置token
|
// 设置token
|
||||||
config.headers['Authorization'] = token;
|
config.headers['Authorization'] = token;
|
||||||
|
config.headers['ClientId'] = getClientId();
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
@@ -63,24 +77,16 @@ service.interceptors.request.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// response interceptor
|
// response interceptor
|
||||||
service.interceptors.response.use(
|
axiosInst.interceptors.response.use(
|
||||||
(response) => {
|
(response) => response,
|
||||||
// 获取请求返回结果
|
|
||||||
const data: Result = response.data;
|
|
||||||
if (data.code === ResultEnum.SUCCESS) {
|
|
||||||
return data.data;
|
|
||||||
}
|
|
||||||
// 如果提示没有权限,则移除token,使其重新登录
|
|
||||||
if (data.code === ResultEnum.NO_PERMISSION) {
|
|
||||||
router.push({
|
|
||||||
path: '/401',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.reject(data);
|
|
||||||
},
|
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
const rejectPromise = Promise.reject(e);
|
const rejectPromise = Promise.reject(e);
|
||||||
|
|
||||||
|
if (axios.isCancel(e)) {
|
||||||
|
console.log('请求已取消');
|
||||||
|
return rejectPromise;
|
||||||
|
}
|
||||||
|
|
||||||
const statusCode = e.response?.status;
|
const statusCode = e.response?.status;
|
||||||
if (statusCode == 500) {
|
if (statusCode == 500) {
|
||||||
notifyErrorMsg('服务器未知异常');
|
notifyErrorMsg('服务器未知异常');
|
||||||
@@ -111,44 +117,62 @@ service.interceptors.response.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求uri
|
* xhr请求url
|
||||||
|
*
|
||||||
|
* @param method 请求方法
|
||||||
|
* @param url url
|
||||||
|
* @param params 参数
|
||||||
|
* @param options 可选
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function xhrReq(method: string, url: string, params: any = null, options: any = {}) {
|
||||||
|
if (!url) {
|
||||||
|
throw new Error('请求url不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单判断该url是否是restful风格
|
||||||
|
if (url.indexOf('{') != -1) {
|
||||||
|
url = templateResolve(url, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
const req: any = {
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
// post和put使用json格式传参
|
||||||
|
if (method === 'post' || method === 'put') {
|
||||||
|
req.data = params;
|
||||||
|
} else {
|
||||||
|
req.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
return axiosInst
|
||||||
|
.request(req)
|
||||||
|
.then((response) => {
|
||||||
|
// 获取请求返回结果
|
||||||
|
const result: Result = response.data;
|
||||||
|
return parseResult(result);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
return Promise.reject(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch请求url
|
||||||
|
*
|
||||||
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
|
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
|
||||||
*
|
*
|
||||||
* @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
|
* @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
|
||||||
* @param {Object} uri uri
|
* @param {Object} uri uri
|
||||||
* @param {Object} params 参数
|
* @param {Object} params 参数
|
||||||
*/
|
*/
|
||||||
function request(method: string, url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
|
async function request(method: string, url: string, params: any = null, options: any = {}): Promise<any> {
|
||||||
if (!url) throw new Error('请求url不能为空');
|
const { execute, data } = useApiFetch(Api.create(url, method), params, options);
|
||||||
// 简单判断该url是否是restful风格
|
await execute();
|
||||||
if (url.indexOf('{') != -1) {
|
return data.value;
|
||||||
url = templateResolve(url, params);
|
|
||||||
}
|
|
||||||
const query: any = {
|
|
||||||
method,
|
|
||||||
url: url,
|
|
||||||
...options,
|
|
||||||
};
|
|
||||||
if (headers) {
|
|
||||||
query.headers = headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
// post和put使用json格式传参
|
|
||||||
if (method === 'post' || method === 'put') {
|
|
||||||
query.data = params;
|
|
||||||
} else {
|
|
||||||
query.params = params;
|
|
||||||
}
|
|
||||||
return service
|
|
||||||
.request(query)
|
|
||||||
.then((res) => res)
|
|
||||||
.catch((e) => {
|
|
||||||
// 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可
|
|
||||||
if (e.msg) {
|
|
||||||
notifyErrorMsg(e.msg);
|
|
||||||
}
|
|
||||||
return Promise.reject(e);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,32 +182,48 @@ function request(method: string, url: string, params: any = null, headers: any =
|
|||||||
* @param {Object} url uri
|
* @param {Object} url uri
|
||||||
* @param {Object} params 参数
|
* @param {Object} params 参数
|
||||||
*/
|
*/
|
||||||
function get(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
|
function get(url: string, params: any = null, options: any = {}): Promise<any> {
|
||||||
return request('get', url, params, headers, options);
|
return request('get', url, params, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function post(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
|
function post(url: string, params: any = null, options: any = {}): Promise<any> {
|
||||||
return request('post', url, params, headers, options);
|
return request('post', url, params, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function put(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
|
function put(url: string, params: any = null, options: any = {}): Promise<any> {
|
||||||
return request('put', url, params, headers, options);
|
return request('put', url, params, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function del(url: string, params: any = null, headers: any = null, options: any = null): Promise<any> {
|
function del(url: string, params: any = null, options: any = {}): Promise<any> {
|
||||||
return request('delete', url, params, headers, options);
|
return request('delete', url, params, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getApiUrl(url: string) {
|
function getApiUrl(url: string) {
|
||||||
// 只是返回api地址而不做请求,用在上传组件之类的
|
// 只是返回api地址而不做请求,用在上传组件之类的
|
||||||
return baseUrl + url + '?token=' + getSession('token');
|
return baseUrl + url + '?' + joinClientParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
// 组装客户端参数,包括 token 和 clientId
|
||||||
request,
|
export function joinClientParams(): string {
|
||||||
get,
|
return `token=${getToken()}&clientId=${getClientId()}`;
|
||||||
post,
|
}
|
||||||
put,
|
|
||||||
del,
|
function parseResult(result: Result) {
|
||||||
getApiUrl,
|
if (result.code === ResultEnum.SUCCESS) {
|
||||||
};
|
return result.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果提示没有权限,则移除token,使其重新登录
|
||||||
|
if (result.code === ResultEnum.NO_PERMISSION) {
|
||||||
|
router.push({
|
||||||
|
path: '/401',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
|
||||||
|
if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
|
||||||
|
notifyErrorMsg(result.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(result);
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export async function RsaEncrypt(value: any) {
|
|||||||
if (!value) {
|
if (!value) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (encryptor != null) {
|
if (encryptor != null && sessionStorage.getItem('RsaPublicKey') != null) {
|
||||||
return encryptor.encrypt(value);
|
return encryptor.encrypt(value);
|
||||||
}
|
}
|
||||||
encryptor = new JSEncrypt();
|
encryptor = new JSEncrypt();
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import Config from './config';
|
|
||||||
import { ElNotification } from 'element-plus';
|
|
||||||
import SocketBuilder from './SocketBuilder';
|
|
||||||
import { getSession } from '@/common/utils/storage';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 全局系统消息websocket
|
|
||||||
*/
|
|
||||||
sysMsgSocket() {
|
|
||||||
const token = getSession('token');
|
|
||||||
if (!token) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return SocketBuilder.builder(`${Config.baseWsUrl}/sysmsg?token=${token}`)
|
|
||||||
.message((event: { data: string }) => {
|
|
||||||
const message = JSON.parse(event.data);
|
|
||||||
let mtype: string;
|
|
||||||
switch (message.type) {
|
|
||||||
case 0:
|
|
||||||
mtype = 'error';
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
mtype = 'info';
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
mtype = 'success';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
mtype = 'info';
|
|
||||||
}
|
|
||||||
if (mtype == undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ElNotification({
|
|
||||||
duration: 0,
|
|
||||||
title: message.title,
|
|
||||||
message: message.msg,
|
|
||||||
type: mtype as any,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.open((event: any) => console.log(event))
|
|
||||||
.build();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -2,8 +2,8 @@ import openApi from './openApi';
|
|||||||
|
|
||||||
// 登录是否使用验证码配置key
|
// 登录是否使用验证码配置key
|
||||||
const AccountLoginSecurity = 'AccountLoginSecurity';
|
const AccountLoginSecurity = 'AccountLoginSecurity';
|
||||||
const UseLoginCaptchaConfigKey = 'UseLoginCaptcha';
|
const UseWatermarkConfigKey = 'UseWatermark';
|
||||||
const UseWartermarkConfigKey = 'UseWartermark';
|
const MachineConfig = 'MachineConfig';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统配置值
|
* 获取系统配置值
|
||||||
@@ -44,28 +44,26 @@ export async function getAccountLoginSecurity(): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否使用登录验证码
|
* 是否启用水印信息配置
|
||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export async function useLoginCaptcha(): Promise<boolean> {
|
export async function useWatermark(): Promise<any> {
|
||||||
return await getBoolConfigValue(UseLoginCaptchaConfigKey, true);
|
const value = await getConfigValue(UseWatermarkConfigKey);
|
||||||
}
|
const defaultValue = {
|
||||||
|
isUse: true,
|
||||||
/**
|
};
|
||||||
* 是否启用水印
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export async function useWartermark(): Promise<boolean> {
|
|
||||||
return await getBoolConfigValue(UseWartermarkConfigKey, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertBool(value: string, defaultValue: boolean) {
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
return value == '1' || value == 'true';
|
try {
|
||||||
|
const jsonValue = JSON.parse(value);
|
||||||
|
// 将字符串转为bool
|
||||||
|
jsonValue.isUse = convertBool(jsonValue.isUse, true);
|
||||||
|
return jsonValue;
|
||||||
|
} catch (e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,3 +76,31 @@ export async function getLdapEnabled(): Promise<any> {
|
|||||||
return convertBool(value, false);
|
return convertBool(value, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用水印信息配置
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function getMachineConfig(): Promise<any> {
|
||||||
|
const value = await getConfigValue(MachineConfig);
|
||||||
|
const defaultValue = {
|
||||||
|
// 默认1gb
|
||||||
|
uploadMaxFileSize: '1GB',
|
||||||
|
};
|
||||||
|
if (!value) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const jsonValue = JSON.parse(value);
|
||||||
|
return jsonValue;
|
||||||
|
} catch (e) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertBool(value: string, defaultValue: boolean) {
|
||||||
|
if (!value) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
return value == '1' || value == 'true';
|
||||||
|
}
|
||||||
|
|||||||
100
mayfly_go_web/src/common/syssocket.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import Config from './config';
|
||||||
|
import { ElNotification } from 'element-plus';
|
||||||
|
import SocketBuilder from './SocketBuilder';
|
||||||
|
import { getToken } from '@/common/utils/storage';
|
||||||
|
|
||||||
|
import { joinClientParams } from './request';
|
||||||
|
|
||||||
|
class SysSocket {
|
||||||
|
/**
|
||||||
|
* socket连接
|
||||||
|
*/
|
||||||
|
socket: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key -> 消息类别,value -> 消息对应的处理器函数
|
||||||
|
*/
|
||||||
|
categoryHandlers: Map<string, any> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息类型
|
||||||
|
*/
|
||||||
|
messageTypes = {
|
||||||
|
0: 'error',
|
||||||
|
1: 'success',
|
||||||
|
2: 'info',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化全局系统消息websocket
|
||||||
|
*/
|
||||||
|
init() {
|
||||||
|
// 存在则不需要重新建立连接
|
||||||
|
if (this.socket) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const token = getToken();
|
||||||
|
if (!token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
console.log('init system ws');
|
||||||
|
const sysMsgUrl = `${Config.baseWsUrl}/sysmsg?${joinClientParams()}`;
|
||||||
|
this.socket = SocketBuilder.builder(sysMsgUrl)
|
||||||
|
.message((event: { data: string }) => {
|
||||||
|
const message = JSON.parse(event.data);
|
||||||
|
// 存在消息类别对应的处理器,则进行处理,否则进行默认通知处理
|
||||||
|
const handler = this.categoryHandlers.get(message.category);
|
||||||
|
if (handler) {
|
||||||
|
handler(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = this.getMsgType(message.type);
|
||||||
|
ElNotification({
|
||||||
|
duration: 0,
|
||||||
|
title: message.title,
|
||||||
|
message: message.msg,
|
||||||
|
type: type,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.open((event: any) => console.log(event))
|
||||||
|
.close(() => {
|
||||||
|
console.log('close sys socket');
|
||||||
|
this.socket = null;
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
destory() {
|
||||||
|
this.socket.close();
|
||||||
|
this.socket = null;
|
||||||
|
this.categoryHandlers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册消息处理函数
|
||||||
|
*
|
||||||
|
* @param category 消息类别
|
||||||
|
* @param handlerFunc 消息处理函数
|
||||||
|
*/
|
||||||
|
registerMsgHandler(category: any, handlerFunc: any) {
|
||||||
|
this.init();
|
||||||
|
if (this.categoryHandlers.has(category)) {
|
||||||
|
console.log(`${category}该类别消息处理器已存在...`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof handlerFunc != 'function') {
|
||||||
|
throw new Error('message handler需为函数');
|
||||||
|
}
|
||||||
|
this.categoryHandlers.set(category, handlerFunc);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMsgType(msgType: any) {
|
||||||
|
return this.messageTypes[msgType];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局系统消息websocket;
|
||||||
|
const sysSocket = new SysSocket();
|
||||||
|
|
||||||
|
export default sysSocket;
|
||||||
134
mayfly_go_web/src/common/useRequest.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import router from '../router';
|
||||||
|
import { getClientId, getToken } from './utils/storage';
|
||||||
|
import { templateResolve } from './utils/string';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { createFetch } from '@vueuse/core';
|
||||||
|
import Api from './Api';
|
||||||
|
import { Result, ResultEnum } from './request';
|
||||||
|
import config from './config';
|
||||||
|
import { unref } from 'vue';
|
||||||
|
|
||||||
|
const baseUrl: string = config.baseApiUrl;
|
||||||
|
|
||||||
|
const useCustomFetch = createFetch({
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
combination: 'chain',
|
||||||
|
options: {
|
||||||
|
immediate: false,
|
||||||
|
timeout: 60000,
|
||||||
|
// beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch
|
||||||
|
async beforeFetch({ options }) {
|
||||||
|
const token = getToken();
|
||||||
|
|
||||||
|
const headers = new Headers(options.headers || {});
|
||||||
|
if (token) {
|
||||||
|
headers.set('Authorization', token);
|
||||||
|
headers.set('ClientId', getClientId());
|
||||||
|
}
|
||||||
|
options.headers = headers;
|
||||||
|
|
||||||
|
return { options };
|
||||||
|
},
|
||||||
|
async afterFetch(ctx) {
|
||||||
|
const result: Result = await ctx.response.json();
|
||||||
|
ctx.data = result;
|
||||||
|
return ctx;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function useApiFetch<T>(api: Api, params: any = null, reqOptions: RequestInit = {}) {
|
||||||
|
const uaf = useCustomFetch<T>(api.url, {
|
||||||
|
beforeFetch({ url, options }) {
|
||||||
|
options.method = api.method;
|
||||||
|
if (!params) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let paramsValue = unref(params);
|
||||||
|
if (api.beforeHandler) {
|
||||||
|
paramsValue = api.beforeHandler(paramsValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
let apiUrl = url;
|
||||||
|
// 简单判断该url是否是restful风格
|
||||||
|
if (apiUrl.indexOf('{') != -1) {
|
||||||
|
apiUrl = templateResolve(apiUrl, paramsValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paramsValue) {
|
||||||
|
const method = options.method?.toLowerCase();
|
||||||
|
// post和put使用json格式传参
|
||||||
|
if (method === 'post' || method === 'put') {
|
||||||
|
options.body = JSON.stringify(paramsValue);
|
||||||
|
} else {
|
||||||
|
const searchParam = new URLSearchParams();
|
||||||
|
Object.keys(paramsValue).forEach((key) => {
|
||||||
|
const val = paramsValue[key];
|
||||||
|
if (val) {
|
||||||
|
searchParam.append(key, val);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
apiUrl = `${apiUrl}?${searchParam.toString()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: apiUrl,
|
||||||
|
options: {
|
||||||
|
...options,
|
||||||
|
...reqOptions,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
execute: async function () {
|
||||||
|
try {
|
||||||
|
await uaf.execute(true);
|
||||||
|
} catch (e: any) {
|
||||||
|
const rejectPromise = Promise.reject(e);
|
||||||
|
|
||||||
|
if (e?.name == 'AbortError') {
|
||||||
|
console.log('请求已取消');
|
||||||
|
return rejectPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(e);
|
||||||
|
ElMessage.error('网络请求错误');
|
||||||
|
return rejectPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Result = uaf.data.value as any;
|
||||||
|
if (!result) {
|
||||||
|
ElMessage.error('网络请求失败');
|
||||||
|
return Promise.reject(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果返回为成功结果,则将结果的data赋值给响应式data
|
||||||
|
if (result.code === ResultEnum.SUCCESS) {
|
||||||
|
uaf.data.value = result.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果提示没有权限,则移除token,使其重新登录
|
||||||
|
if (result.code === ResultEnum.NO_PERMISSION) {
|
||||||
|
router.push({
|
||||||
|
path: '/401',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
|
||||||
|
if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
|
||||||
|
ElMessage.error(result.msg);
|
||||||
|
uaf.error.value = new Error(result.msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(result);
|
||||||
|
},
|
||||||
|
isFetching: uaf.isFetching,
|
||||||
|
data: uaf.data,
|
||||||
|
abort: uaf.abort,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -6,6 +6,11 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
|
|||||||
let dataValueArr: any = [];
|
let dataValueArr: any = [];
|
||||||
for (let column of columns) {
|
for (let column of columns) {
|
||||||
let val: any = data[column];
|
let val: any = data[column];
|
||||||
|
if (val == null || val == undefined) {
|
||||||
|
dataValueArr.push('');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof val == 'string' && val) {
|
if (typeof val == 'string' && val) {
|
||||||
// csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
|
// csv格式如果有逗号,整体用双引号括起来;如果里面还有双引号就替换成两个双引号,这样导出来的格式就不会有问题了
|
||||||
if (val.indexOf(',') != -1) {
|
if (val.indexOf(',') != -1) {
|
||||||
@@ -16,23 +21,27 @@ export function exportCsv(filename: string, columns: string[], datas: []) {
|
|||||||
// 再将逗号转义
|
// 再将逗号转义
|
||||||
val = `"${val}"`;
|
val = `"${val}"`;
|
||||||
}
|
}
|
||||||
dataValueArr.push(val);
|
dataValueArr.push(val + '\t');
|
||||||
} else {
|
} else {
|
||||||
dataValueArr.push(val);
|
dataValueArr.push(val + '\t');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cvsData.push(dataValueArr);
|
cvsData.push(dataValueArr);
|
||||||
}
|
}
|
||||||
const csvString = cvsData.map((e) => e.join(',')).join('\n');
|
const csvString = cvsData.map((e) => e.join(',')).join('\n');
|
||||||
|
exportFile(`${filename}.csv`, csvString);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function exportFile(filename: string, content: string) {
|
||||||
// 导出
|
// 导出
|
||||||
let link = document.createElement('a');
|
let link = document.createElement('a');
|
||||||
let exportContent = '\uFEFF';
|
let exportContent = '\uFEFF';
|
||||||
let blob = new Blob([exportContent + csvString], {
|
let blob = new Blob([exportContent + content], {
|
||||||
type: 'text/plain;charset=utf-8',
|
type: 'text/plain;charset=utf-8',
|
||||||
});
|
});
|
||||||
link.id = 'download-csv';
|
link.id = 'download-file';
|
||||||
link.setAttribute('href', URL.createObjectURL(blob));
|
link.setAttribute('href', URL.createObjectURL(blob));
|
||||||
link.setAttribute('download', `${filename}.csv`);
|
link.setAttribute('download', `${filename}`);
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,21 +3,47 @@
|
|||||||
* @param size byte size
|
* @param size byte size
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function formatByteSize(size: any) {
|
export function formatByteSize(size: number, fixed = 2) {
|
||||||
const value = Number(size);
|
if (size === 0) {
|
||||||
if (size && !isNaN(value)) {
|
return '0B';
|
||||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
|
|
||||||
let index = 0;
|
|
||||||
let k = value;
|
|
||||||
if (value >= 1024) {
|
|
||||||
while (k > 1024) {
|
|
||||||
k = k / 1024;
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
const base = 1024;
|
||||||
|
const exponent = Math.floor(Math.log(size) / Math.log(base));
|
||||||
|
|
||||||
|
return parseFloat((size / Math.pow(base, exponent)).toFixed(fixed)) + units[exponent];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 容量转为对应的字节大小,如 1KB转为 1024
|
||||||
|
* @param sizeString 1kb 1gb等
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function convertToBytes(sizeStr: string) {
|
||||||
|
sizeStr = sizeStr.trim();
|
||||||
|
const unit = sizeStr.slice(-2);
|
||||||
|
|
||||||
|
const valueStr = sizeStr.slice(0, -2);
|
||||||
|
const value = parseInt(valueStr, 10);
|
||||||
|
|
||||||
|
let bytes = 0;
|
||||||
|
|
||||||
|
switch (unit.toUpperCase()) {
|
||||||
|
case 'KB':
|
||||||
|
bytes = value * 1024;
|
||||||
|
break;
|
||||||
|
case 'MB':
|
||||||
|
bytes = value * 1024 * 1024;
|
||||||
|
break;
|
||||||
|
case 'GB':
|
||||||
|
bytes = value * 1024 * 1024 * 1024;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid size unit');
|
||||||
}
|
}
|
||||||
return `${k.toFixed(2)}${units[index]}`;
|
|
||||||
}
|
return bytes;
|
||||||
return '-';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,20 +1,16 @@
|
|||||||
import { nextTick } from 'vue';
|
import { nextTick } from 'vue';
|
||||||
import loadingCss from '@/theme/loading.scss?inline';
|
import '@/theme/loading.scss';
|
||||||
|
|
||||||
// 定义方法
|
/**
|
||||||
|
* 页面全局 Loading
|
||||||
|
* @method start 创建 loading
|
||||||
|
* @method done 移除 loading
|
||||||
|
*/
|
||||||
export const NextLoading = {
|
export const NextLoading = {
|
||||||
// 载入 css
|
|
||||||
setCss: () => {
|
|
||||||
let link = document.createElement('link');
|
|
||||||
link.rel = 'stylesheet';
|
|
||||||
link.href = loadingCss;
|
|
||||||
link.crossOrigin = 'anonymous';
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(link);
|
|
||||||
},
|
|
||||||
// 创建 loading
|
// 创建 loading
|
||||||
start: () => {
|
start: () => {
|
||||||
const bodys: any = document.body;
|
const bodys: Element = document.body;
|
||||||
const div = document.createElement('div');
|
const div = <HTMLElement>document.createElement('div');
|
||||||
div.setAttribute('class', 'loading-next');
|
div.setAttribute('class', 'loading-next');
|
||||||
const htmls = `
|
const htmls = `
|
||||||
<div class="loading-next-box">
|
<div class="loading-next-box">
|
||||||
@@ -35,12 +31,16 @@ export const NextLoading = {
|
|||||||
bodys.insertBefore(div, bodys.childNodes[0]);
|
bodys.insertBefore(div, bodys.childNodes[0]);
|
||||||
},
|
},
|
||||||
// 移除 loading
|
// 移除 loading
|
||||||
done: () => {
|
done: (time: number = 1000) => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const el = document.querySelector('.loading-next');
|
const el = <HTMLElement>document.querySelector('.loading-next');
|
||||||
el && el.parentNode?.removeChild(el);
|
el?.parentNode?.removeChild(el);
|
||||||
}, 1000);
|
}, time);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function sleep(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,90 @@
|
|||||||
|
import { randomUuid } from './string';
|
||||||
|
|
||||||
|
const TokenKey = 'm-token';
|
||||||
|
const UserKey = 'm-user';
|
||||||
|
const TagViewsKey = 'm-tagViews';
|
||||||
|
const ClientIdKey = 'm-clientId';
|
||||||
|
|
||||||
|
// 获取请求token
|
||||||
|
export function getToken(): string {
|
||||||
|
return getLocal(TokenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存用户访问token
|
||||||
|
export function saveToken(token: string) {
|
||||||
|
setLocal(TokenKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取登录用户基础信息
|
||||||
|
export function getUser() {
|
||||||
|
return getLocal(UserKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存用户信息
|
||||||
|
export function saveUser(userinfo: any) {
|
||||||
|
setLocal(UserKey, userinfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveThemeConfig(themeConfig: any) {
|
||||||
|
setLocal('themeConfig', themeConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getThemeConfig() {
|
||||||
|
return getLocal('themeConfig');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除用户相关的用户信息
|
||||||
|
export function clearUser() {
|
||||||
|
removeLocal(TokenKey);
|
||||||
|
removeLocal(UserKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTagViews() {
|
||||||
|
return getSession(TagViewsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTagViews(tagViews: Array<object>) {
|
||||||
|
setSession(TagViewsKey, tagViews);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeTagViews() {
|
||||||
|
removeSession(TagViewsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取客户端UUID
|
||||||
|
export function getClientId(): string {
|
||||||
|
let uuid = getSession(ClientIdKey);
|
||||||
|
if (uuid == null) {
|
||||||
|
uuid = randomUuid();
|
||||||
|
setSession(ClientIdKey, uuid);
|
||||||
|
}
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
// 1. localStorage
|
// 1. localStorage
|
||||||
// 设置永久缓存
|
// 设置永久缓存
|
||||||
export function setLocal(key: string, val: any) {
|
export function setLocal(key: string, val: any) {
|
||||||
window.localStorage.setItem(key, JSON.stringify(val));
|
if (typeof val == 'object') {
|
||||||
|
val = JSON.stringify(val);
|
||||||
|
}
|
||||||
|
window.localStorage.setItem(key, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取永久缓存
|
// 获取永久缓存
|
||||||
export function getLocal(key: string) {
|
export function getLocal(key: string) {
|
||||||
let json: any = window.localStorage.getItem(key);
|
let val: any = window.localStorage.getItem(key);
|
||||||
return JSON.parse(json);
|
try {
|
||||||
|
return JSON.parse(val);
|
||||||
|
} catch (e) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除永久缓存
|
// 移除永久缓存
|
||||||
export function removeLocal(key: string) {
|
export function removeLocal(key: string) {
|
||||||
window.localStorage.removeItem(key);
|
window.localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除全部永久缓存
|
// 移除全部永久缓存
|
||||||
export function clearLocal() {
|
export function clearLocal() {
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
@@ -20,35 +93,29 @@ export function clearLocal() {
|
|||||||
// 2. sessionStorage
|
// 2. sessionStorage
|
||||||
// 设置临时缓存
|
// 设置临时缓存
|
||||||
export function setSession(key: string, val: any) {
|
export function setSession(key: string, val: any) {
|
||||||
window.sessionStorage.setItem(key, JSON.stringify(val));
|
if (typeof val == 'object') {
|
||||||
|
val = JSON.stringify(val);
|
||||||
|
}
|
||||||
|
window.sessionStorage.setItem(key, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取临时缓存
|
// 获取临时缓存
|
||||||
export function getSession(key: string) {
|
export function getSession(key: string) {
|
||||||
let json: any = window.sessionStorage.getItem(key);
|
let val: any = window.sessionStorage.getItem(key);
|
||||||
return JSON.parse(json);
|
try {
|
||||||
|
return JSON.parse(val);
|
||||||
|
} catch (e) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除临时缓存
|
// 移除临时缓存
|
||||||
export function removeSession(key: string) {
|
export function removeSession(key: string) {
|
||||||
window.sessionStorage.removeItem(key);
|
window.sessionStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除全部临时缓存
|
// 移除全部临时缓存
|
||||||
export function clearSession() {
|
export function clearSession() {
|
||||||
|
clearUser();
|
||||||
window.sessionStorage.clear();
|
window.sessionStorage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserInfo4Session() {
|
|
||||||
return getSession('userInfo');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setUserInfo2Session(userinfo: any) {
|
|
||||||
setSession('userInfo', userinfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取是否开启水印
|
|
||||||
export function getUseWatermark4Session() {
|
|
||||||
return getSession('useWatermark');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setUseWatermark2Session(useWatermark: boolean) {
|
|
||||||
setSession('useWatermark', useWatermark);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import { v1 as uuidv1 } from 'uuid';
|
||||||
|
import Clipboard from 'clipboard';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模板字符串解析,如:template = 'hahaha{name}_{id}' ,param = {name: 'hh', id: 1}
|
* 模板字符串解析,如:template = 'hahaha{name}_{id}' ,param = {name: 'hh', id: 1}
|
||||||
* 解析后为 hahahahh_1
|
* 解析后为 hahahahh_1
|
||||||
@@ -129,3 +133,49 @@ export function getContentWidth(content: any): number {
|
|||||||
// }
|
// }
|
||||||
return flexWidth;
|
return flexWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns uuid
|
||||||
|
*/
|
||||||
|
export function randomUuid() {
|
||||||
|
return uuidv1();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拷贝文本至剪贴板
|
||||||
|
* @param txt 需要拷贝到剪贴板的文本
|
||||||
|
* @param selector click事件对应的元素selector,默认为 #copyValue
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function copyToClipboard(txt: string, selector: string = '#copyValue') {
|
||||||
|
// navigator clipboard 需要https等安全上下文
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
|
// navigator clipboard 向剪贴板写文本
|
||||||
|
try {
|
||||||
|
// 拷贝单元格数据
|
||||||
|
await navigator.clipboard.writeText(txt);
|
||||||
|
ElMessage.success('复制成功');
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error('复制失败');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clipboard = new Clipboard(selector, {
|
||||||
|
text: function () {
|
||||||
|
return txt;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
clipboard.on('success', () => {
|
||||||
|
ElMessage.success('复制成功');
|
||||||
|
// 释放内存
|
||||||
|
clipboard.destroy();
|
||||||
|
});
|
||||||
|
clipboard.on('error', () => {
|
||||||
|
// 不支持复制
|
||||||
|
ElMessage.error('该浏览器不支持自动复制');
|
||||||
|
// 释放内存
|
||||||
|
clipboard.destroy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
13
mayfly_go_web/src/common/utils/url.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const mode = import.meta.env.VITE_ROUTER_MODE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取不同路由模式所对应的 url
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
export function getNowUrl() {
|
||||||
|
const url = {
|
||||||
|
hash: location.hash.substring(1),
|
||||||
|
history: location.pathname + location.search,
|
||||||
|
};
|
||||||
|
return url[mode];
|
||||||
|
}
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { getUseWatermark4Session, getUserInfo4Session } from '@/common/utils/storage';
|
|
||||||
import { dateFormat2 } from '@/common/utils/date';
|
|
||||||
|
|
||||||
// 页面添加水印效果
|
|
||||||
const setWatermark = (str: any) => {
|
|
||||||
const id = '1.23452384164.123412416';
|
|
||||||
if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
|
|
||||||
const can = document.createElement('canvas');
|
|
||||||
can.width = 400;
|
|
||||||
can.height = 250;
|
|
||||||
const cans: any = can.getContext('2d');
|
|
||||||
cans.rotate((-20 * Math.PI) / 180);
|
|
||||||
cans.font = '14px Vedana';
|
|
||||||
cans.fillStyle = 'rgba(200, 200, 200, 0.35)';
|
|
||||||
cans.textAlign = 'left';
|
|
||||||
cans.textBaseline = 'Middle';
|
|
||||||
// cans.fillText('mayfly go', can.width / 4, can.height )
|
|
||||||
cans.fillText(str, can.width / 8, can.height / 2);
|
|
||||||
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.id = id;
|
|
||||||
div.style.pointerEvents = 'none';
|
|
||||||
div.style.top = '30px';
|
|
||||||
div.style.left = '0px';
|
|
||||||
div.style.position = 'fixed';
|
|
||||||
div.style.zIndex = '10000000';
|
|
||||||
div.style.width = document.documentElement.clientWidth + 'px';
|
|
||||||
div.style.height = document.documentElement.clientHeight + 'px';
|
|
||||||
div.style.background = `url(${can.toDataURL('image/png')}) left top repeat`;
|
|
||||||
document.body.appendChild(div);
|
|
||||||
return id;
|
|
||||||
};
|
|
||||||
|
|
||||||
function set(str: any) {
|
|
||||||
let id = setWatermark(str);
|
|
||||||
if (document.getElementById(id) === null) id = setWatermark(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
function del() {
|
|
||||||
let id = '1.23452384164.123412416';
|
|
||||||
if (document.getElementById(id) !== null) document.body.removeChild(document.getElementById(id) as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
const watermark = {
|
|
||||||
use: () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
const userinfo = getUserInfo4Session();
|
|
||||||
if (userinfo && getUseWatermark4Session()) {
|
|
||||||
set(`${userinfo.username} ${dateFormat2('yyyy-MM-dd HH:mm:ss', new Date())}`);
|
|
||||||
} else {
|
|
||||||
del();
|
|
||||||
}
|
|
||||||
}, 1500);
|
|
||||||
},
|
|
||||||
// 设置水印
|
|
||||||
set: (str: any) => {
|
|
||||||
set(str);
|
|
||||||
},
|
|
||||||
// 删除水印
|
|
||||||
del: () => {
|
|
||||||
del();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default watermark;
|
|
||||||
66
mayfly_go_web/src/components/Grid/components/GridItem.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="isShow" :style="style">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts" name="GridItem">
|
||||||
|
import { computed, inject, Ref, ref, useAttrs, watch } from 'vue';
|
||||||
|
import { BreakPoint, Responsive } from '../interface/index';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
offset?: number;
|
||||||
|
span?: number;
|
||||||
|
suffix?: boolean;
|
||||||
|
xs?: Responsive;
|
||||||
|
sm?: Responsive;
|
||||||
|
md?: Responsive;
|
||||||
|
lg?: Responsive;
|
||||||
|
xl?: Responsive;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
offset: 0,
|
||||||
|
span: 1,
|
||||||
|
suffix: false,
|
||||||
|
xs: undefined,
|
||||||
|
sm: undefined,
|
||||||
|
md: undefined,
|
||||||
|
lg: undefined,
|
||||||
|
xl: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const attrs = useAttrs() as { index: string };
|
||||||
|
const isShow = ref(true);
|
||||||
|
|
||||||
|
// 注入断点
|
||||||
|
const breakPoint = inject<Ref<BreakPoint>>('breakPoint', ref('xl'));
|
||||||
|
const shouldHiddenIndex = inject<Ref<number>>('shouldHiddenIndex', ref(-1));
|
||||||
|
watch(
|
||||||
|
() => [shouldHiddenIndex.value, breakPoint.value],
|
||||||
|
(n) => {
|
||||||
|
if (attrs.index) {
|
||||||
|
isShow.value = !(n[0] !== -1 && parseInt(attrs.index) >= Number(n[0]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const gap = inject('gap', 0);
|
||||||
|
const cols = inject('cols', ref(4));
|
||||||
|
const style = computed(() => {
|
||||||
|
let span = props[breakPoint.value]?.span ?? props.span;
|
||||||
|
let offset = props[breakPoint.value]?.offset ?? props.offset;
|
||||||
|
if (props.suffix) {
|
||||||
|
return {
|
||||||
|
gridColumnStart: cols.value - span - offset + 1,
|
||||||
|
gridColumnEnd: `span ${span + offset}`,
|
||||||
|
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
gridColumn: `span ${span + offset > cols.value ? cols.value : span + offset}/span ${span + offset > cols.value ? cols.value : span + offset}`,
|
||||||
|
marginLeft: offset !== 0 ? `calc(((100% + ${gap}px) / ${span + offset}) * ${offset})` : 'unset',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
159
mayfly_go_web/src/components/Grid/index.vue
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<template>
|
||||||
|
<div :style="style">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="Grid">
|
||||||
|
import { ref, watch, useSlots, computed, provide, onBeforeMount, onMounted, onUnmounted, onDeactivated, onActivated, VNodeArrayChildren, VNode } from 'vue';
|
||||||
|
import type { BreakPoint } from './interface/index';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
cols?: number | Record<BreakPoint, number>;
|
||||||
|
collapsed?: boolean;
|
||||||
|
collapsedRows?: number;
|
||||||
|
gap?: [number, number] | number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
cols: () => ({ xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }),
|
||||||
|
collapsed: false,
|
||||||
|
collapsedRows: 1,
|
||||||
|
gap: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeMount(() => props.collapsed && findIndex());
|
||||||
|
onMounted(() => {
|
||||||
|
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
});
|
||||||
|
onActivated(() => {
|
||||||
|
resize({ target: { innerWidth: window.innerWidth } } as unknown as UIEvent);
|
||||||
|
window.addEventListener('resize', resize);
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
});
|
||||||
|
onDeactivated(() => {
|
||||||
|
window.removeEventListener('resize', resize);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 监听屏幕变化
|
||||||
|
const resize = (e: UIEvent) => {
|
||||||
|
let width = (e.target as Window).innerWidth;
|
||||||
|
switch (!!width) {
|
||||||
|
case width < 768:
|
||||||
|
breakPoint.value = 'xs';
|
||||||
|
break;
|
||||||
|
case width >= 768 && width < 992:
|
||||||
|
breakPoint.value = 'sm';
|
||||||
|
break;
|
||||||
|
case width >= 992 && width < 1200:
|
||||||
|
breakPoint.value = 'md';
|
||||||
|
break;
|
||||||
|
case width >= 1200 && width < 1920:
|
||||||
|
breakPoint.value = 'lg';
|
||||||
|
break;
|
||||||
|
case width >= 1920:
|
||||||
|
breakPoint.value = 'xl';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 注入 gap 间距
|
||||||
|
provide('gap', Array.isArray(props.gap) ? props.gap[0] : props.gap);
|
||||||
|
|
||||||
|
// 注入响应式断点
|
||||||
|
let breakPoint = ref<BreakPoint>('xl');
|
||||||
|
provide('breakPoint', breakPoint);
|
||||||
|
|
||||||
|
// 注入要开始折叠的 index
|
||||||
|
const hiddenIndex = ref(-1);
|
||||||
|
provide('shouldHiddenIndex', hiddenIndex);
|
||||||
|
|
||||||
|
// 注入 cols
|
||||||
|
const gridCols = computed(() => {
|
||||||
|
if (typeof props.cols === 'object') return props.cols[breakPoint.value] ?? props.cols;
|
||||||
|
return props.cols;
|
||||||
|
});
|
||||||
|
provide('cols', gridCols);
|
||||||
|
|
||||||
|
// 寻找需要开始折叠的字段 index
|
||||||
|
const slots = useSlots().default!();
|
||||||
|
|
||||||
|
const findIndex = () => {
|
||||||
|
let fields: VNodeArrayChildren = [];
|
||||||
|
let suffix: VNode | null = null;
|
||||||
|
slots.forEach((slot: any) => {
|
||||||
|
// suffix
|
||||||
|
if (typeof slot.type === 'object' && slot.type.__name === 'GridItem' && slot.props?.suffix !== undefined) {
|
||||||
|
suffix = slot;
|
||||||
|
}
|
||||||
|
// slot children
|
||||||
|
if (typeof slot.type === 'symbol' && Array.isArray(slot.children)) {
|
||||||
|
fields.push(...slot.children);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 计算 suffix 所占用的列
|
||||||
|
let suffixCols = 0;
|
||||||
|
if (suffix) {
|
||||||
|
suffixCols =
|
||||||
|
((suffix as VNode).props![breakPoint.value]?.span ?? (suffix as VNode).props?.span ?? 1) +
|
||||||
|
((suffix as VNode).props![breakPoint.value]?.offset ?? (suffix as VNode).props?.offset ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let find = false;
|
||||||
|
fields.reduce((prev = 0, current, index) => {
|
||||||
|
prev +=
|
||||||
|
((current as VNode)!.props![breakPoint.value]?.span ?? (current as VNode)!.props?.span ?? 1) +
|
||||||
|
((current as VNode)!.props![breakPoint.value]?.offset ?? (current as VNode)!.props?.offset ?? 0);
|
||||||
|
if (Number(prev) > props.collapsedRows * gridCols.value - suffixCols) {
|
||||||
|
hiddenIndex.value = index;
|
||||||
|
find = true;
|
||||||
|
throw 'find it';
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, 0);
|
||||||
|
if (!find) hiddenIndex.value = -1;
|
||||||
|
} catch (e) {
|
||||||
|
// console.warn(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 断点变化时执行 findIndex
|
||||||
|
watch(
|
||||||
|
() => breakPoint.value,
|
||||||
|
() => {
|
||||||
|
if (props.collapsed) findIndex();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 监听 collapsed
|
||||||
|
watch(
|
||||||
|
() => props.collapsed,
|
||||||
|
(value) => {
|
||||||
|
if (value) return findIndex();
|
||||||
|
hiddenIndex.value = -1;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置间距
|
||||||
|
const gridGap = computed(() => {
|
||||||
|
if (typeof props.gap === 'number') return `${props.gap}px`;
|
||||||
|
if (Array.isArray(props.gap)) return `${props.gap[1]}px ${props.gap[0]}px`;
|
||||||
|
return 'unset';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设置 style
|
||||||
|
const style = computed(() => {
|
||||||
|
return {
|
||||||
|
display: 'grid',
|
||||||
|
gridGap: gridGap.value,
|
||||||
|
gridTemplateColumns: `repeat(${gridCols.value}, minmax(0, 1fr))`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
defineExpose({ breakPoint });
|
||||||
|
</script>
|
||||||
6
mayfly_go_web/src/components/Grid/interface/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export type BreakPoint = "xs" | "sm" | "md" | "lg" | "xl";
|
||||||
|
|
||||||
|
export type Responsive = {
|
||||||
|
span?: number;
|
||||||
|
offset?: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="item?.render ?? `el-${item.type}`"
|
||||||
|
v-bind="{ ...handleSearchProps, ...placeholder, clearable: true }"
|
||||||
|
v-model.trim="itemValue"
|
||||||
|
:data="item.type === 'tree-select' ? item.options : []"
|
||||||
|
:options="['cascader', 'select-v2'].includes(item.type!) ? item.options : []"
|
||||||
|
>
|
||||||
|
<template v-if="item.type === 'cascader'" #default="{ data }">
|
||||||
|
<span>{{ data[fieldNames.label] }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="item.type === 'select'">
|
||||||
|
<component
|
||||||
|
:is="`el-option`"
|
||||||
|
v-for="(col, index) in item.options"
|
||||||
|
:key="index"
|
||||||
|
:label="col[fieldNames.label]"
|
||||||
|
:value="col[fieldNames.value]"
|
||||||
|
></component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<slot v-else></slot>
|
||||||
|
</component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="SearchFormItem">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { SearchItem } from '../index';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
interface SearchFormItemProps {
|
||||||
|
modelValue: any;
|
||||||
|
item: SearchItem;
|
||||||
|
}
|
||||||
|
const props = defineProps<SearchFormItemProps>();
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const itemValue = useVModel(props, 'modelValue', emit);
|
||||||
|
|
||||||
|
// 判断 fieldNames 设置 label && value && children 的 key 值
|
||||||
|
const fieldNames = computed(() => {
|
||||||
|
return {
|
||||||
|
label: props.item?.fieldNames?.label ?? 'label',
|
||||||
|
value: props.item?.fieldNames?.value ?? 'value',
|
||||||
|
children: props.item.fieldNames?.children ?? 'children',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 接收 enumMap (el 为 select-v2 需单独处理 enumData)
|
||||||
|
// const enumMap = inject('enumMap', ref(new Map()));
|
||||||
|
// const columnEnum = computed(() => {
|
||||||
|
// let enumData = enumMap.value.get(props.item.prop);
|
||||||
|
// if (!enumData) return [];
|
||||||
|
// if (props.item?.type === 'select-v2' && props.item.fieldNames) {
|
||||||
|
// enumData = enumData.map((item: { [key: string]: any }) => {
|
||||||
|
// return { ...item, label: item[fieldNames.value.label], value: item[fieldNames.value.value] };
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// return enumData;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// 处理透传的 searchProps (type 为 tree-select、cascader 的时候需要给下默认 label && value && children)
|
||||||
|
const handleSearchProps = computed(() => {
|
||||||
|
const label = fieldNames.value.label;
|
||||||
|
const value = fieldNames.value.value;
|
||||||
|
const children = fieldNames.value.children;
|
||||||
|
const searchEl = props.item?.type;
|
||||||
|
let searchProps = props.item?.props ?? {};
|
||||||
|
if (searchEl === 'tree-select') {
|
||||||
|
searchProps = { ...searchProps, props: { ...searchProps.props, label, children }, nodeKey: value };
|
||||||
|
}
|
||||||
|
if (searchEl === 'cascader') {
|
||||||
|
searchProps = { ...searchProps, props: { ...searchProps.props, label, value, children } };
|
||||||
|
}
|
||||||
|
return searchProps;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 处理默认 placeholder
|
||||||
|
const placeholder = computed(() => {
|
||||||
|
const search = props.item;
|
||||||
|
const label = search.label;
|
||||||
|
if (['datetimerange', 'daterange', 'monthrange'].includes(search?.props?.type) || search?.props?.isRange) {
|
||||||
|
return {
|
||||||
|
rangeSeparator: search?.props?.rangeSeparator ?? '至',
|
||||||
|
startPlaceholder: search?.props?.startPlaceholder ?? '开始时间',
|
||||||
|
endPlaceholder: search?.props?.endPlaceholder ?? '结束时间',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const placeholder = search?.props?.placeholder ?? (search?.type?.includes('input') ? `请输入${label}` : `请选择${label}`);
|
||||||
|
return { placeholder };
|
||||||
|
});
|
||||||
|
</script>
|
||||||
115
mayfly_go_web/src/components/SearchForm/index.ts
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import { VNode } from 'vue';
|
||||||
|
|
||||||
|
export type FieldNamesProps = {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
children?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SearchItemType =
|
||||||
|
| 'input'
|
||||||
|
| 'input-number'
|
||||||
|
| 'select'
|
||||||
|
| 'select-v2'
|
||||||
|
| 'tree-select'
|
||||||
|
| 'cascader'
|
||||||
|
| 'date-picker'
|
||||||
|
| 'time-picker'
|
||||||
|
| 'time-select'
|
||||||
|
| 'switch'
|
||||||
|
| 'slider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索项
|
||||||
|
*/
|
||||||
|
export class SearchItem {
|
||||||
|
/**
|
||||||
|
* 属性字段
|
||||||
|
*/
|
||||||
|
prop: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前项搜索框的 label
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单项类型,input、select、date等
|
||||||
|
*/
|
||||||
|
type: SearchItemType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* select等组件的可选值
|
||||||
|
*/
|
||||||
|
options: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插槽名
|
||||||
|
*/
|
||||||
|
slot: string;
|
||||||
|
|
||||||
|
props?: any; // 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件
|
||||||
|
|
||||||
|
tooltip?: string; // 搜索提示
|
||||||
|
|
||||||
|
span?: number; // 搜索项所占用的列数,默认为 1 列
|
||||||
|
|
||||||
|
offset?: number; // 搜索字段左侧偏移列数
|
||||||
|
|
||||||
|
fieldNames: FieldNamesProps; // 指定 label && value && children 的 key 值,用于select等类型组件
|
||||||
|
|
||||||
|
render?: (scope: any) => VNode; // 自定义搜索内容渲染(tsx语法)
|
||||||
|
|
||||||
|
constructor(prop: string, label: string) {
|
||||||
|
this.prop = prop;
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
static new(prop: string, label: string): SearchItem {
|
||||||
|
return new SearchItem(prop, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
static text(prop: string, label: string): SearchItem {
|
||||||
|
const tq = new SearchItem(prop, label);
|
||||||
|
tq.type = 'input';
|
||||||
|
return tq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static select(prop: string, label: string): SearchItem {
|
||||||
|
const tq = new SearchItem(prop, label);
|
||||||
|
tq.type = 'select';
|
||||||
|
return tq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static date(prop: string, label: string): SearchItem {
|
||||||
|
const tq = new SearchItem(prop, label);
|
||||||
|
tq.type = 'date-picker';
|
||||||
|
return tq;
|
||||||
|
}
|
||||||
|
|
||||||
|
static slot(prop: string, label: string, slotName: string): SearchItem {
|
||||||
|
const tq = new SearchItem(prop, label);
|
||||||
|
tq.slot = slotName;
|
||||||
|
return tq;
|
||||||
|
}
|
||||||
|
|
||||||
|
withSpan(span: number): SearchItem {
|
||||||
|
this.span = span;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置枚举值用于选择等
|
||||||
|
* @param enumValues 枚举值对象
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
withEnum(enumValues: any): SearchItem {
|
||||||
|
this.options = Object.values(enumValues);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOptions(options: any): SearchItem {
|
||||||
|
this.options = options;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
136
mayfly_go_web/src/components/SearchForm/index.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="items.length" class="search-form">
|
||||||
|
<el-form ref="formRef" :model="searchParam" label-width="auto">
|
||||||
|
<Grid ref="gridRef" :collapsed="collapsed" :gap="[20, 0]" :cols="searchCol">
|
||||||
|
<GridItem v-for="(item, index) in items" :key="item.prop" v-bind="getResponsive(item)" :index="index">
|
||||||
|
<el-form-item>
|
||||||
|
<template #label>
|
||||||
|
<el-space :size="4">
|
||||||
|
<span>{{ `${item?.label}` }}</span>
|
||||||
|
<el-tooltip v-if="item.tooltip" :content="item?.tooltip" placement="top">
|
||||||
|
<SvgIcon name="QuestionFilled" />
|
||||||
|
</el-tooltip>
|
||||||
|
</el-space>
|
||||||
|
<span>:</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<SearchFormItem v-if="!item.slot" :item="item" v-model="searchParam[item.prop]" />
|
||||||
|
|
||||||
|
<slot v-else :name="item.slot"></slot>
|
||||||
|
</el-form-item>
|
||||||
|
</GridItem>
|
||||||
|
<GridItem suffix>
|
||||||
|
<div class="operation">
|
||||||
|
<el-button type="primary" :icon="Search" @click="search" plain> 搜索 </el-button>
|
||||||
|
<el-button :icon="Delete" @click="reset"> 重置 </el-button>
|
||||||
|
<el-button v-if="showCollapse" type="primary" link class="search-isOpen" @click="collapsed = !collapsed">
|
||||||
|
{{ collapsed ? '展开' : '合并' }}
|
||||||
|
<el-icon class="el-icon--right">
|
||||||
|
<component :is="collapsed ? ArrowDown : ArrowUp"></component>
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</GridItem>
|
||||||
|
</Grid>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts" name="SearchForm">
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import { BreakPoint } from '@/components/Grid/interface/index';
|
||||||
|
import { Delete, Search, ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||||
|
import SearchFormItem from './components/SearchFormItem.vue';
|
||||||
|
import Grid from '@/components/Grid/index.vue';
|
||||||
|
import GridItem from '@/components/Grid/components/GridItem.vue';
|
||||||
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
|
import { SearchItem } from './index';
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
interface ProTableProps {
|
||||||
|
items: SearchItem[]; // 搜索配置项
|
||||||
|
modelValue?: { [key: string]: any }; // 搜索参数
|
||||||
|
searchCol: number | Record<BreakPoint, number>;
|
||||||
|
search: (params: any) => void; // 搜索方法
|
||||||
|
reset: (params: any) => void; // 重置方法
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认值
|
||||||
|
const props = withDefaults(defineProps<ProTableProps>(), {
|
||||||
|
items: () => [],
|
||||||
|
modelValue: () => ({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const searchParam = useVModel(props, 'modelValue', emit);
|
||||||
|
|
||||||
|
// 获取响应式设置
|
||||||
|
const getResponsive = (item: SearchItem) => {
|
||||||
|
return {
|
||||||
|
span: item?.span,
|
||||||
|
offset: item.offset ?? 0,
|
||||||
|
// xs: item.search?.xs,
|
||||||
|
// sm: item.search?.sm,
|
||||||
|
// md: item.search?.md,
|
||||||
|
// lg: item.search?.lg,
|
||||||
|
// xl: item.search?.xl,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 是否默认折叠搜索项
|
||||||
|
const collapsed = ref(true);
|
||||||
|
|
||||||
|
// 获取响应式断点
|
||||||
|
const gridRef = ref();
|
||||||
|
const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint);
|
||||||
|
|
||||||
|
// 判断是否显示 展开/合并 按钮
|
||||||
|
const showCollapse = computed(() => {
|
||||||
|
let show = false;
|
||||||
|
props.items.reduce((prev, current) => {
|
||||||
|
prev += (current![breakPoint.value]?.span ?? current?.span ?? 1) + (current![breakPoint.value]?.offset ?? current?.offset ?? 0);
|
||||||
|
if (typeof props.searchCol !== 'number') {
|
||||||
|
if (prev >= props.searchCol[breakPoint.value]) show = true;
|
||||||
|
} else {
|
||||||
|
if (prev >= props.searchCol) show = true;
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
}, 0);
|
||||||
|
return show;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.search-form {
|
||||||
|
padding: 18px 18px 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background-color: var(--el-bg-color);
|
||||||
|
border: 1px solid var(--el-border-color-light);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 0 12px rgb(0 0 0 / 5%);
|
||||||
|
|
||||||
|
.el-form {
|
||||||
|
.el-form-item__content > * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去除时间选择器上下 padding
|
||||||
|
.el-range-editor.el-input__wrapper {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 18px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,6 +6,9 @@ import { useUserInfo } from '@/store/userInfo';
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function hasPerm(code: string) {
|
export function hasPerm(code: string) {
|
||||||
|
if (!code) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return useUserInfo().userInfo.permissions.some((v: any) => v === code);
|
return useUserInfo().userInfo.permissions.some((v: any) => v === code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
mayfly_go_web/src/components/contextmenu/index.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import Contextmenu from './index.vue';
|
||||||
|
|
||||||
|
class ContextmenuItem {
|
||||||
|
clickId: any;
|
||||||
|
|
||||||
|
txt: string;
|
||||||
|
|
||||||
|
icon: string;
|
||||||
|
|
||||||
|
affix: boolean;
|
||||||
|
|
||||||
|
permission: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否隐藏回调函数
|
||||||
|
*/
|
||||||
|
hideFunc: (data: any) => boolean;
|
||||||
|
|
||||||
|
onClickFunc: (data: any) => void;
|
||||||
|
|
||||||
|
constructor(clickId: any, txt: string) {
|
||||||
|
this.clickId = clickId;
|
||||||
|
this.txt = txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
withIcon(icon: string) {
|
||||||
|
this.icon = icon;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withPermission(permission: string) {
|
||||||
|
this.permission = permission;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withHideFunc(func: (data: any) => boolean) {
|
||||||
|
this.hideFunc = func;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
withOnClick(func: (data: any) => void) {
|
||||||
|
this.onClickFunc = func;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否隐藏
|
||||||
|
* @param data 点击数据项
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
isHide(data: any) {
|
||||||
|
if (this.hideFunc) {
|
||||||
|
return this.hideFunc(data);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Contextmenu, ContextmenuItem };
|
||||||
@@ -1,36 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<transition name="el-zoom-in-center">
|
<transition @enter="onEnter" name="el-zoom-in-center">
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
|
class="el-dropdown__popper el-popper is-light is-pure custom-contextmenu"
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
data-popper-placement="bottom"
|
data-popper-placement="bottom"
|
||||||
:style="`top: ${dropdowns.y + 5}px;left: ${dropdowns.x}px;`"
|
:style="`top: ${state.dropdown.y + 5}px;left: ${state.dropdown.x}px;`"
|
||||||
:key="Math.random()"
|
:key="Math.random()"
|
||||||
v-show="state.isShow"
|
v-show="state.isShow && !allHide"
|
||||||
>
|
>
|
||||||
<ul class="el-dropdown-menu">
|
<ul class="el-dropdown-menu">
|
||||||
<template v-for="(v, k) in state.dropdownList">
|
<template v-for="(v, k) in state.dropdownList">
|
||||||
<li
|
<li
|
||||||
|
:id="v.clickId"
|
||||||
|
v-auth="v.permission"
|
||||||
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 && !v.isHide(state.item)"
|
||||||
@click="onCurrentContextmenuClick(v.contextMenuClickId)"
|
@click="onCurrentContextmenuClick(v)"
|
||||||
>
|
>
|
||||||
<SvgIcon :name="v.icon" />
|
<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: `${state.arrowLeft}px` }"></div>
|
<div v-if="state.arrowLeft > 0" class="el-popper__arrow" :style="{ left: `${state.arrowLeft}px` }"></div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
<script setup lang="ts" name="layoutTagsViewContextmenu">
|
||||||
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
import { computed, reactive, onMounted, onUnmounted, watch } from 'vue';
|
||||||
|
import { ContextmenuItem } from './index';
|
||||||
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
|
import { useWindowSize } from '@vueuse/core';
|
||||||
|
|
||||||
// 定义父组件传过来的值
|
// 定义父组件传过来的值
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -44,38 +49,82 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
type: Array,
|
type: Array<ContextmenuItem>,
|
||||||
default: [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 定义子组件向父组件传值/事件
|
// 定义子组件向父组件传值/事件
|
||||||
const emit = defineEmits(['currentContextmenuClick']);
|
const emit = defineEmits(['currentContextmenuClick']);
|
||||||
|
|
||||||
|
const { width: vw, height: vh } = useWindowSize();
|
||||||
|
|
||||||
// 定义变量内容
|
// 定义变量内容
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
isShow: false,
|
isShow: false,
|
||||||
dropdownList: [{ contextMenuClickId: 0, txt: '刷新', affix: false, icon: 'RefreshRight' }],
|
dropdownList: [] as ContextmenuItem[],
|
||||||
item: {} as any,
|
item: {} as any,
|
||||||
arrowLeft: 10,
|
arrowLeft: 10,
|
||||||
|
dropdown: {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 父级传过来的坐标 x,y 值
|
// 下拉菜单宽高
|
||||||
const dropdowns = computed(() => {
|
let contextmenuWidth = 117;
|
||||||
// 117 为 `Dropdown 下拉菜单` 的宽度
|
let contextmenuHeight = 117;
|
||||||
if (props.dropdown.x + 117 > document.documentElement.clientWidth) {
|
// 下拉菜单元素
|
||||||
return {
|
let ele = null as any;
|
||||||
x: document.documentElement.clientWidth - 117 - 5,
|
|
||||||
y: props.dropdown.y,
|
const onEnter = (el: any) => {
|
||||||
};
|
if (ele || el.offsetHeight == 0) {
|
||||||
} else {
|
return;
|
||||||
return props.dropdown;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
// 当前项菜单点击
|
ele = el;
|
||||||
const onCurrentContextmenuClick = (contextMenuClickId: number) => {
|
contextmenuHeight = el.offsetHeight;
|
||||||
emit('currentContextmenuClick', { id: contextMenuClickId, item: state.item });
|
contextmenuWidth = el.offsetWidth;
|
||||||
|
setDropdowns(props.dropdown);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setDropdowns = (dropdown: any) => {
|
||||||
|
let { x, y } = dropdown;
|
||||||
|
|
||||||
|
state.arrowLeft = 10;
|
||||||
|
|
||||||
|
// `Dropdown 下拉菜单` 的宽度
|
||||||
|
if (x + contextmenuWidth > vw.value) {
|
||||||
|
state.arrowLeft = contextmenuWidth - (vw.value - x);
|
||||||
|
x = vw.value - contextmenuWidth - 5;
|
||||||
|
}
|
||||||
|
if (y + contextmenuHeight > vh.value) {
|
||||||
|
y = vh.value - contextmenuHeight - 5;
|
||||||
|
state.arrowLeft = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.dropdown.x = x;
|
||||||
|
state.dropdown.y = y;
|
||||||
|
};
|
||||||
|
|
||||||
|
const allHide = computed(() => {
|
||||||
|
for (let item of state.dropdownList) {
|
||||||
|
if (!item.isHide(state.item)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 当前项菜单点击
|
||||||
|
const onCurrentContextmenuClick = (ci: ContextmenuItem) => {
|
||||||
|
// 存在点击事件,则触发该事件函数
|
||||||
|
if (ci.onClickFunc) {
|
||||||
|
ci.onClickFunc(state.item);
|
||||||
|
}
|
||||||
|
emit('currentContextmenuClick', { id: ci.clickId, item: state.item });
|
||||||
|
};
|
||||||
|
|
||||||
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
// 打开右键菜单:判断是否固定,固定则不显示关闭按钮
|
||||||
const openContextmenu = (item: any) => {
|
const openContextmenu = (item: any) => {
|
||||||
state.item = item;
|
state.item = item;
|
||||||
@@ -84,29 +133,34 @@ const openContextmenu = (item: any) => {
|
|||||||
state.isShow = true;
|
state.isShow = true;
|
||||||
}, 10);
|
}, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭右键菜单
|
// 关闭右键菜单
|
||||||
const closeContextmenu = () => {
|
const closeContextmenu = () => {
|
||||||
state.isShow = false;
|
state.isShow = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听页面监听进行右键菜单的关闭
|
// 监听页面监听进行右键菜单的关闭
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.body.addEventListener('click', closeContextmenu);
|
document.body.addEventListener('click', closeContextmenu);
|
||||||
|
state.dropdownList = props.items;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 页面卸载时,移除右键菜单监听事件
|
// 页面卸载时,移除右键菜单监听事件
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.body.removeEventListener('click', closeContextmenu);
|
document.body.removeEventListener('click', closeContextmenu);
|
||||||
});
|
});
|
||||||
// 监听下拉菜单位置
|
|
||||||
watch(
|
watch(
|
||||||
() => props.dropdown,
|
() => props.dropdown,
|
||||||
({ x }) => {
|
() => {
|
||||||
if (x + 117 > document.documentElement.clientWidth) state.arrowLeft = 117 - (document.documentElement.clientWidth - x);
|
// 元素置为空,重新在onEnter赋值元素,否则会造成堆栈溢出
|
||||||
else state.arrowLeft = 10;
|
ele = null;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
deep: true,
|
deep: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.items,
|
() => props.items,
|
||||||
(x: any) => {
|
(x: any) => {
|
||||||
@@ -140,3 +194,4 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
.
|
||||||
|
|||||||
@@ -1,212 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dynamic-form">
|
<div class="dynamic-form">
|
||||||
<el-form
|
<el-form v-bind="$attrs" ref="formRef" :model="formData" label-width="auto">
|
||||||
:model="form"
|
<el-form-item v-for="item in formItems as any" :key="item.name" :prop="item.model" :label="item.name" required>
|
||||||
ref="dynamicForm"
|
<el-input v-if="!item.options" v-model="formData[item.model]" :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
|
||||||
:label-width="formInfo.labelWidth ? formInfo.labelWidth : '100px'"
|
|
||||||
:size="formInfo.size ? formInfo.size : 'small'"
|
|
||||||
>
|
|
||||||
<el-row v-for="fr in formInfo.formRows" :key="fr.key">
|
|
||||||
<el-col v-for="item in fr" :key="item.key" :span="item.span ? item.span : 24 / fr.length">
|
|
||||||
<el-form-item :prop="item.name" :label="item.label" :label-width="item.labelWidth" :required="item.required" :rules="item.rules">
|
|
||||||
<!-- input输入框 -->
|
|
||||||
<el-input
|
|
||||||
v-if="item.type === 'input'"
|
|
||||||
v-model.trim="form[item.name]"
|
|
||||||
:placeholder="item.placeholder"
|
|
||||||
:type="item.inputType"
|
|
||||||
clearable
|
|
||||||
|
|
||||||
@change="item.change ? item.change(form) : ''"
|
<el-select v-else v-model="formData[item.model]" :placeholder="item.placeholder" filterable autocomplete="off" clearable style="width: 100%">
|
||||||
></el-input>
|
<el-option v-for="option in item.options.split(',')" :key="option" :label="option" :value="option" />
|
||||||
|
|
||||||
<!-- 普通文本信息(可用于不可修改字段等) -->
|
|
||||||
<span v-else-if="item.type === 'text'">{{ form[item.name] }}</span>
|
|
||||||
|
|
||||||
<!-- select选择框 -->
|
|
||||||
<!-- optionProps.label: 指定option中的label为options对象的某个属性值,默认就是label字段 -->
|
|
||||||
<!-- optionProps.value: 指定option中的value为options对象的某个属性值,默认就是value字段 -->
|
|
||||||
<el-select
|
|
||||||
v-else-if="item.type === 'select'"
|
|
||||||
v-model.trim="form[item.name]"
|
|
||||||
:placeholder="item.placeholder"
|
|
||||||
:filterable="item.filterable"
|
|
||||||
:remote="item.remote"
|
|
||||||
:remote-method="item.remoteMethod"
|
|
||||||
@focus="item.focus ? item.focus(form) : ''"
|
|
||||||
clearable
|
|
||||||
:disabled="item.updateDisabled && form.id != null"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="i in item.options"
|
|
||||||
:key="i.key"
|
|
||||||
:label="i[item.optionProps ? item.optionProps.label || 'label' : 'label']"
|
|
||||||
:value="i[item.optionProps ? item.optionProps.value || 'value' : 'value']"
|
|
||||||
></el-option>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
|
|
||||||
<el-row type="flex" justify="center">
|
|
||||||
<slot name="btns" :submitDisabled="submitDisabled" :data="form" :submit="submit">
|
|
||||||
<el-button @click="reset" size="small">重 置</el-button>
|
|
||||||
<el-button type="primary" @click="submit" size="small">保 存</el-button>
|
|
||||||
</slot>
|
|
||||||
</el-row>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { watch, ref, toRefs, reactive, onMounted, defineComponent } from 'vue';
|
import { useVModel } from '@vueuse/core';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'DynamicForm',
|
formItems: { type: Array },
|
||||||
|
modelValue: { type: Object },
|
||||||
props: {
|
|
||||||
formInfo: { type: Object },
|
|
||||||
formData: { type: [Object, Boolean] },
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props: any, context) {
|
|
||||||
const dynamicForm: any = ref();
|
|
||||||
const state = reactive({
|
|
||||||
form: {},
|
|
||||||
submitDisabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(props.formData, (newValue, oldValue) => {
|
|
||||||
if (props.formData) {
|
|
||||||
state.form = { ...props.formData };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit = () => {
|
|
||||||
dynamicForm.value.validate((valid: boolean) => {
|
|
||||||
if (valid) {
|
|
||||||
// 提交的表单数据
|
|
||||||
const subform = { ...state.form };
|
|
||||||
const operation = state.form['id'] ? props.formInfo['updateApi'] : props.formInfo['createApi'];
|
|
||||||
if (operation) {
|
|
||||||
state.submitDisabled = true;
|
|
||||||
operation.request(state.form).then(
|
|
||||||
(res: any) => {
|
|
||||||
ElMessage.success('保存成功');
|
|
||||||
context.emit('submitSuccess', subform);
|
|
||||||
state.submitDisabled = false;
|
|
||||||
// this.cancel()
|
|
||||||
},
|
|
||||||
(e: any) => {
|
|
||||||
state.submitDisabled = false;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ElMessage.error('表单未设置对应的提交权限');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const reset = () => {
|
|
||||||
context.emit('reset');
|
|
||||||
resetFieldsAndData();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重置表单以及表单数据
|
|
||||||
*/
|
|
||||||
const resetFieldsAndData = () => {
|
|
||||||
// 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
|
|
||||||
const df: any = dynamicForm;
|
|
||||||
df.resetFields();
|
|
||||||
// 重置表单数据
|
|
||||||
state.form = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
dynamicForm,
|
|
||||||
submit,
|
|
||||||
reset,
|
|
||||||
resetFieldsAndData,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
// @Component({
|
|
||||||
// name: 'DynamicForm'
|
|
||||||
// })
|
|
||||||
// export default class DynamicForm extends Vue {
|
|
||||||
// @Prop()
|
|
||||||
// formInfo: object
|
|
||||||
// @Prop()
|
|
||||||
// formData: [object,boolean]|undefined
|
|
||||||
|
|
||||||
// form = {}
|
const emit = defineEmits(['update:modelValue']);
|
||||||
// submitDisabled = false
|
|
||||||
|
|
||||||
// @Watch('formData', { deep: true })
|
const formRef: any = ref();
|
||||||
// onRoleChange() {
|
|
||||||
// if (this.formData) {
|
|
||||||
// this.form = { ...this.formData }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// submit() {
|
const formData: any = useVModel(props, 'modelValue', emit);
|
||||||
// const dynamicForm: any = this.$refs['dynamicForm']
|
|
||||||
// dynamicForm.validate((valid: boolean) => {
|
|
||||||
// if (valid) {
|
|
||||||
// // 提交的表单数据
|
|
||||||
// const subform = { ...this.form }
|
|
||||||
// const operation = this.form['id']
|
|
||||||
// ? this.formInfo['updateApi']
|
|
||||||
// : this.formInfo['createApi']
|
|
||||||
// if (operation) {
|
|
||||||
// this.submitDisabled = true
|
|
||||||
// operation.request(this.form).then(
|
|
||||||
// (res: any) => {
|
|
||||||
// ElMessage.success('保存成功')
|
|
||||||
// this.$emit('submitSuccess', subform)
|
|
||||||
// this.submitDisabled = false
|
|
||||||
// // this.cancel()
|
|
||||||
// },
|
|
||||||
// (e: any) => {
|
|
||||||
// this.submitDisabled = false
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// } else {
|
|
||||||
// ElMessage.error('表单未设置对应的提交权限')
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// reset() {
|
const validate = async (func: any) => {
|
||||||
// this.$emit('reset')
|
await formRef.value.validate(func);
|
||||||
// this.resetFieldsAndData()
|
};
|
||||||
// }
|
|
||||||
|
|
||||||
// /**
|
const resetFields = () => {
|
||||||
// * 重置表单以及表单数据
|
formRef.value.resetFields();
|
||||||
// */
|
};
|
||||||
// resetFieldsAndData() {
|
|
||||||
// // 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
|
|
||||||
// const df: any = this.$refs['dynamicForm']
|
|
||||||
// df.resetFields()
|
|
||||||
// // 重置表单数据
|
|
||||||
// this.form = {}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// mounted() {
|
defineExpose({
|
||||||
// // 组件可能还没有初始化,第一次初始化的时候无法watch对象
|
validate,
|
||||||
// this.form = { ...this.formData }
|
resetFields,
|
||||||
// }
|
});
|
||||||
|
|
||||||
// }
|
|
||||||
</script>
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
|
|||||||
@@ -1,60 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="form-dialog">
|
<div class="form-dialog">
|
||||||
<el-dialog :title="title" v-model="visible" :width="dialogWidth ? dialogWidth : '500px'">
|
<el-dialog @close="close" v-bind="$attrs" :title="title" v-model="dialogVisible" :width="width">
|
||||||
<dynamic-form ref="df" :form-info="formInfo" :form-data="formData" @submitSuccess="submitSuccess">
|
<dynamic-form ref="df" :form-items="formItems" v-model="formData" />
|
||||||
<template #btns="props">
|
|
||||||
|
<template #footer>
|
||||||
|
<span>
|
||||||
<slot name="btns">
|
<slot name="btns">
|
||||||
<el-button :disabled="props.submitDisabled" type="primary" @click="props.submit" size="small">保 存</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
<el-button :disabled="props.submitDisabled" @click="close()" size="small">取 消</el-button>
|
<el-button type="primary" @click="confirm">确 定</el-button>
|
||||||
</slot>
|
</slot>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</dynamic-form>
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { watch, ref, toRefs, reactive, onMounted, defineComponent } from 'vue';
|
import { ref } from 'vue';
|
||||||
import DynamicForm from './DynamicForm.vue';
|
import DynamicForm from './DynamicForm.vue';
|
||||||
export default defineComponent({
|
import { useVModel } from '@vueuse/core';
|
||||||
name: 'DynamicFormDialog',
|
|
||||||
components: {
|
|
||||||
DynamicForm,
|
|
||||||
},
|
|
||||||
|
|
||||||
props: {
|
const emit = defineEmits(['update:visible', 'update:modelValue', 'close', 'confirm']);
|
||||||
visible: { type: Boolean },
|
|
||||||
dialogWidth: { type: String },
|
const props = defineProps({
|
||||||
title: { type: String },
|
title: { type: String },
|
||||||
formInfo: { type: Object },
|
visible: { type: Boolean },
|
||||||
formData: { type: [Object, Boolean] },
|
width: { type: [String, Number], default: '500px' },
|
||||||
},
|
formItems: { type: Array },
|
||||||
|
modelValue: { type: Object },
|
||||||
setup(props: any, context) {
|
|
||||||
const df: any = ref();
|
|
||||||
|
|
||||||
const close = () => {
|
|
||||||
// 更新父组件visible prop对应的值为false
|
|
||||||
context.emit('update:visible', false);
|
|
||||||
// 关闭窗口,则将表单数据置为null
|
|
||||||
context.emit('update:formData', null);
|
|
||||||
context.emit('close');
|
|
||||||
// 取消动态表单的校验以及form数据
|
|
||||||
setTimeout(() => {
|
|
||||||
df.resetFieldsAndData();
|
|
||||||
}, 200);
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitSuccess = (form: any) => {
|
|
||||||
context.emit('submitSuccess', form);
|
|
||||||
close();
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
df,
|
|
||||||
close,
|
|
||||||
submitSuccess,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const df: any = ref();
|
||||||
|
|
||||||
|
const formData: any = useVModel(props, 'modelValue', emit);
|
||||||
|
const dialogVisible: any = useVModel(props, 'visible', emit);
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit('close');
|
||||||
|
// 取消动态表单的校验
|
||||||
|
setTimeout(() => {
|
||||||
|
formData.value = {};
|
||||||
|
df.value.resetFields();
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirm = () => {
|
||||||
|
df.value.validate((valid: any) => {
|
||||||
|
if (!valid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
emit('confirm', formData.value);
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dynamic-form-edit w100">
|
||||||
|
<el-table :data="formItems" stripe class="w100" empty-text="暂无表单项">
|
||||||
|
<el-table-column prop="name" label="model" min-width="100px">
|
||||||
|
<template #header>
|
||||||
|
<el-button class="ml0" type="primary" circle size="small" icon="Plus" @click="addItem()"> </el-button>
|
||||||
|
<span class="ml10">model</span>
|
||||||
|
</template>
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input v-model="scope.row['model']" placeholder="字段model" clearable> </el-input>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="name" label="label" min-width="100px">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input v-model="scope.row['name']" placeholder="字段title" clearable> </el-input>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="placeholder" label="字段说明" min-width="140px">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input v-model="scope.row['placeholder']" placeholder="字段说明" clearable> </el-input>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column prop="options" label="可选值" min-width="140px">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-input v-model="scope.row['options']" placeholder="可选值 ,分割" clearable> </el-input>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column label="操作" wdith="20px">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button type="danger" @click="deleteItem(scope.$index)" icon="delete" plain></el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: { type: Array },
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
const formItems: any = useVModel(props, 'modelValue', emit);
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
formItems.value.push({});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteItem = (index: any) => {
|
||||||
|
formItems.value.splice(index, 1);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export { default as DynamicForm } from './DynamicForm.vue';
|
export { default as DynamicForm } from './DynamicForm.vue';
|
||||||
export { default as DynamicFormDialog } from './DynamicFormDialog.vue';
|
export { default as DynamicFormDialog } from './DynamicFormDialog.vue';
|
||||||
|
export { default as DynamicFormEdit } from './DynamicFormEdit.vue';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="monaco-editor" style="border: 1px solid #ccc">
|
<div class="monaco-editor" style="border: 1px solid var(--el-border-color-light, #ebeef5); height: 100%">
|
||||||
<div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
|
<div class="monaco-editor-content" ref="monacoTextarea" :style="{ height: height }"></div>
|
||||||
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
|
<el-select v-if="canChangeMode" class="code-mode-select" v-model="languageMode" @change="changeLanguage">
|
||||||
<el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
<el-option v-for="mode in languageArr" :key="mode.value" :label="mode.label" :value="mode.value"> </el-option>
|
||||||
@@ -9,9 +9,31 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch, toRefs, reactive, onMounted, onBeforeUnmount } from 'vue';
|
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/esm/vs/editor/editor.api';
|
||||||
import * as monaco from 'monaco-editor';
|
// 相关语言
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/shell/shell.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/html/html.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/css/css.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/python/python.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/markdown/markdown.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/java/java.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/basic-languages/sql/sql.contribution.js';
|
||||||
|
import 'monaco-editor/esm/vs/language/json/monaco.contribution';
|
||||||
|
// 右键菜单
|
||||||
|
import 'monaco-editor/esm/vs/editor/contrib/contextmenu/browser/contextmenu.js';
|
||||||
|
import 'monaco-editor/esm/vs/editor/contrib/caretOperations/browser/caretOperations.js';
|
||||||
|
import 'monaco-editor/esm/vs/editor/contrib/clipboard//browser/clipboard.js';
|
||||||
|
import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController.js';
|
||||||
|
import 'monaco-editor/esm/vs/editor/contrib/format//browser/formatActions.js';
|
||||||
|
// 提示
|
||||||
|
import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestController.js';
|
||||||
|
import 'monaco-editor/esm/vs/editor/contrib/suggest/browser/suggestInlineCompletions.js';
|
||||||
|
|
||||||
import { editor, languages } from 'monaco-editor';
|
import { editor, languages } from 'monaco-editor';
|
||||||
|
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker.js?worker';
|
||||||
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||||
// 主题仓库 https://github.com/brijeshb42/monaco-themes
|
// 主题仓库 https://github.com/brijeshb42/monaco-themes
|
||||||
// 主题例子 https://editor.bitwiser.in/
|
// 主题例子 https://editor.bitwiser.in/
|
||||||
@@ -25,6 +47,11 @@ import SolarizedLight from 'monaco-themes/themes/Solarized-light.json';
|
|||||||
import { language as shellLan } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
|
import { language as shellLan } from 'monaco-editor/esm/vs/basic-languages/shell/shell.js';
|
||||||
import { ElOption, ElSelect } from 'element-plus';
|
import { ElOption, ElSelect } from 'element-plus';
|
||||||
|
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
|
|
||||||
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -105,7 +132,7 @@ const languageArr = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const options = {
|
const defaultOptions = {
|
||||||
language: 'shell',
|
language: 'shell',
|
||||||
theme: 'SolarizedLight',
|
theme: 'SolarizedLight',
|
||||||
automaticLayout: true, //自适应宽高布局
|
automaticLayout: true, //自适应宽高布局
|
||||||
@@ -127,6 +154,20 @@ const options = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const monacoTextarea: any = ref();
|
||||||
|
|
||||||
|
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 state = reactive({
|
const state = reactive({
|
||||||
languageMode: 'shell',
|
languageMode: 'shell',
|
||||||
});
|
});
|
||||||
@@ -145,6 +186,7 @@ onBeforeUnmount(() => {
|
|||||||
monacoEditorIns.dispose();
|
monacoEditorIns.dispose();
|
||||||
}
|
}
|
||||||
if (completionItemProvider) {
|
if (completionItemProvider) {
|
||||||
|
console.log('unmount=> dispose completion item provider');
|
||||||
completionItemProvider.dispose();
|
completionItemProvider.dispose();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -166,37 +208,28 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const monacoTextarea: any = ref(null);
|
// 监听 themeConfig editorTheme配置文件的变化
|
||||||
|
watch(
|
||||||
let monacoEditorIns: editor.IStandaloneCodeEditor = null as any;
|
() => themeConfig.value.editorTheme,
|
||||||
let completionItemProvider: any = null;
|
(val) => {
|
||||||
|
console.log('monaco editor theme change: ', val);
|
||||||
self.MonacoEnvironment = {
|
monaco?.editor?.setTheme(val);
|
||||||
getWorker(_: any, label: string) {
|
|
||||||
if (label === 'json') {
|
|
||||||
return new JsonWorker();
|
|
||||||
}
|
}
|
||||||
return new EditorWorker();
|
);
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const initMonacoEditorIns = () => {
|
const initMonacoEditorIns = () => {
|
||||||
console.log('初始化monaco编辑器');
|
console.log('初始化monaco编辑器');
|
||||||
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
|
// options参数参考 https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html#language
|
||||||
// 初始化一些主题
|
// 初始化一些主题
|
||||||
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
|
monaco.editor.defineTheme('SolarizedLight', SolarizedLight);
|
||||||
options.language = state.languageMode;
|
defaultOptions.language = state.languageMode;
|
||||||
// 从localStorage中获取,通过store可能存在父子组件都使用store报错
|
defaultOptions.theme = themeConfig.value.editorTheme;
|
||||||
options.theme = JSON.parse(localStorage.getItem('themeConfig') as string).editorTheme || 'vs';
|
monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(defaultOptions, props.options as any));
|
||||||
monacoEditorIns = monaco.editor.create(monacoTextarea.value, Object.assign(options, props.options as any));
|
|
||||||
|
|
||||||
// 监听内容改变,双向绑定
|
// 监听内容改变,双向绑定
|
||||||
monacoEditorIns.onDidChangeModelContent(() => {
|
monacoEditorIns.onDidChangeModelContent(() => {
|
||||||
emit('update:modelValue', monacoEditorIns.getModel()?.getValue());
|
emit('update:modelValue', monacoEditorIns.getModel()?.getValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
// 动态设置主题
|
|
||||||
// monaco.editor.setTheme('hc-black');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeLanguage = (value: any) => {
|
const changeLanguage = (value: any) => {
|
||||||
@@ -227,6 +260,7 @@ const setEditorValue = (value: any) => {
|
|||||||
*/
|
*/
|
||||||
const registerCompletionItemProvider = () => {
|
const registerCompletionItemProvider = () => {
|
||||||
if (completionItemProvider) {
|
if (completionItemProvider) {
|
||||||
|
console.log('exist competion item provider, dispose now');
|
||||||
completionItemProvider.dispose();
|
completionItemProvider.dispose();
|
||||||
}
|
}
|
||||||
if (state.languageMode == 'shell') {
|
if (state.languageMode == 'shell') {
|
||||||
@@ -266,7 +300,11 @@ const format = () => {
|
|||||||
monacoEditorIns.trigger('', 'editor.action.formatDocument', '');
|
monacoEditorIns.trigger('', 'editor.action.formatDocument', '');
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({ format });
|
const getEditor = () => {
|
||||||
|
return monacoEditorIns;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ getEditor, format });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key: language, value: CompletionItemProvider
|
||||||
|
*/
|
||||||
|
const completionItemProviders: Map<string, any> = new Map();
|
||||||
|
|
||||||
|
export function registerCompletionItemProvider(language: string, completionItemProvider: any, replace: boolean = true) {
|
||||||
|
const exist = completionItemProviders.get(language);
|
||||||
|
if (exist) {
|
||||||
|
if (!replace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
exist.dispose();
|
||||||
|
}
|
||||||
|
completionItemProviders.set(language, monaco.languages.registerCompletionItemProvider(language, completionItemProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dispposeCompletionItemProvider(language: string) {
|
||||||
|
const exist = completionItemProviders.get(language);
|
||||||
|
if (exist) {
|
||||||
|
exist.dispose();
|
||||||
|
completionItemProviders.delete(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,87 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="page-table">
|
|
||||||
<!--
|
|
||||||
实现:通过我们配置好的 查询条件
|
|
||||||
首先去创建form表单,根据我们配置的查询条件去做一个循环判断,展示出不用类型所对应不同的输入框
|
|
||||||
比如:text对应普通的输入框,select对应下拉选择,dateTime对应日期时间选择器
|
|
||||||
在使用时,父组件会传来一个queryForm空的对象,
|
|
||||||
循环出来的输入框会绑定表格配置中的prop字段绑定在queryForm对象中
|
|
||||||
-->
|
|
||||||
<el-card>
|
|
||||||
<div class="query" ref="queryRef">
|
|
||||||
<div>
|
<div>
|
||||||
<div v-if="props.query.length > 0">
|
<!-- 查询表单 -->
|
||||||
<el-form :model="props.queryForm" label-width="auto" :size="props.size">
|
<SearchForm v-show="isShowSearch" :items="searchItems" v-model="queryForm_" :search="queryData" :reset="reset" :search-col="searchCol">
|
||||||
<el-row
|
<!-- 遍历父组件传入的 solts 透传给子组件 -->
|
||||||
v-for="i in Math.ceil((props.query.length + 1) / (defaultQueryCount + 1))"
|
<template v-for="(_, key) in useSlots()" v-slot:[key]>
|
||||||
:key="i"
|
<slot :name="key"></slot>
|
||||||
v-show="i == 1 || isOpenMoreQuery"
|
|
||||||
:class="i > 1 && isOpenMoreQuery ? 'is-open' : ''"
|
|
||||||
>
|
|
||||||
<el-form-item
|
|
||||||
:label="item.label"
|
|
||||||
style="margin-right: 12px; margin-bottom: 0px"
|
|
||||||
v-for="item in getRowQueryItem(i)"
|
|
||||||
:key="item.prop"
|
|
||||||
>
|
|
||||||
<!-- 这里只获取指定个数的筛选条件 -->
|
|
||||||
<el-input
|
|
||||||
v-model="queryForm[item.prop]"
|
|
||||||
:placeholder="'输入' + item.label + '关键字'"
|
|
||||||
clearable
|
|
||||||
v-if="item.type == 'text'"
|
|
||||||
></el-input>
|
|
||||||
|
|
||||||
<el-select-v2
|
|
||||||
v-model="queryForm[item.prop]"
|
|
||||||
:options="item.options"
|
|
||||||
clearable
|
|
||||||
:placeholder="'选择' + item.label + '关键字'"
|
|
||||||
v-else-if="item.type == 'select'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<el-date-picker
|
|
||||||
v-model="queryForm[item.prop]"
|
|
||||||
clearable
|
|
||||||
type="datetimerange"
|
|
||||||
format="YYYY-MM-DD hh:mm:ss"
|
|
||||||
value-format="x"
|
|
||||||
range-separator="至"
|
|
||||||
start-placeholder="开始时间"
|
|
||||||
end-placeholder="结束时间"
|
|
||||||
v-else-if="item.type == 'date'"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template v-else-if="item.slot == 'queryBtns'">
|
|
||||||
<template v-if="props.query?.length > defaultQueryCount">
|
|
||||||
<el-button
|
|
||||||
@click="isOpenMoreQuery = !isOpenMoreQuery"
|
|
||||||
v-if="!isOpenMoreQuery"
|
|
||||||
icon="ArrowDownBold"
|
|
||||||
circle
|
|
||||||
></el-button>
|
|
||||||
<el-button @click="isOpenMoreQuery = !isOpenMoreQuery" v-else icon="ArrowUpBold" circle></el-button>
|
|
||||||
</template>
|
</template>
|
||||||
|
</SearchForm>
|
||||||
|
|
||||||
<el-button @click="queryData()" type="primary" icon="search" plain>查询</el-button>
|
<el-card>
|
||||||
<el-button @click="reset()" icon="RefreshRight">重置</el-button>
|
<div class="table-main">
|
||||||
</template>
|
<!-- 表格头部 操作按钮 -->
|
||||||
|
<div class="table-header">
|
||||||
<slot :name="item.slot"></slot>
|
<div class="header-button-lf">
|
||||||
</el-form-item>
|
<slot name="tableHeader" />
|
||||||
</el-row>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="slot">
|
<div v-if="toolButton" class="header-button-ri">
|
||||||
<!-- 查询栏右侧slot插槽(用来添加表格其他操作,比如,新增数据,删除数据等其他操作) -->
|
<slot name="toolButton">
|
||||||
<slot name="queryRight"></slot>
|
<el-button v-if="showToolButton('refresh')" icon="Refresh" circle @click="execQuery()" />
|
||||||
|
|
||||||
|
<el-button v-if="showToolButton('search') && searchItems?.length" icon="Search" circle @click="isShowSearch = !isShowSearch" />
|
||||||
|
|
||||||
<!--
|
|
||||||
动态表头显示,根据表格每条配置项中的show字段来决定改列是否显示或者隐藏
|
|
||||||
columns 就是我们表格配置的数组对象
|
|
||||||
-->
|
|
||||||
<el-popover
|
<el-popover
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
title="表格配置"
|
title="表格配置"
|
||||||
@@ -89,14 +29,14 @@
|
|||||||
width="auto"
|
width="auto"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
>
|
>
|
||||||
<div v-for="(item, index) in props.columns" :key="index">
|
<div v-for="(item, index) in tableColumns" :key="index">
|
||||||
<el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
|
<el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
|
||||||
</div>
|
</div>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<!-- 一个Element Plus中的图标 -->
|
<el-button icon="Operation" circle :size="props.size"></el-button>
|
||||||
<el-button icon="Operation" :size="props.size"></el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -104,14 +44,15 @@
|
|||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
:max-height="tableMaxHeight"
|
:max-height="tableMaxHeight"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
:data="props.data"
|
:data="state.data"
|
||||||
highlight-current-row
|
highlight-current-row
|
||||||
v-loading="loadingData"
|
v-loading="state.loading"
|
||||||
:size="props.size"
|
:size="props.size"
|
||||||
|
:border="border"
|
||||||
>
|
>
|
||||||
<el-table-column v-if="props.showSelection" type="selection" width="40" />
|
<el-table-column v-if="props.showSelection" type="selection" width="40" />
|
||||||
|
|
||||||
<template v-for="(item, index) in columns">
|
<template v-for="(item, index) in tableColumns">
|
||||||
<el-table-column
|
<el-table-column
|
||||||
:key="index"
|
:key="index"
|
||||||
v-if="item.show"
|
v-if="item.show"
|
||||||
@@ -145,13 +86,7 @@
|
|||||||
width="600px"
|
width="600px"
|
||||||
>
|
>
|
||||||
<template #default>
|
<template #default>
|
||||||
<el-input
|
<el-input :autosize="{ minRows: 3, maxRows: 15 }" disabled v-model="formatVal" type="textarea" />
|
||||||
input-style="color: black;"
|
|
||||||
:autosize="{ minRows: 3, maxRows: 15 }"
|
|
||||||
disabled
|
|
||||||
v-model="formatVal"
|
|
||||||
type="textarea"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<template #reference>
|
<template #reference>
|
||||||
<el-link
|
<el-link
|
||||||
@@ -169,17 +104,18 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</template>
|
</template>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<el-row style="margin-top: 20px" type="flex" justify="end">
|
<el-row class="mt20" type="flex" justify="end">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
:small="props.size == 'small'"
|
:small="props.size == 'small'"
|
||||||
@current-change="handlePageChange"
|
@current-change="handlePageChange"
|
||||||
@size-change="handleSizeChange"
|
@size-change="handleSizeChange"
|
||||||
style="text-align: right"
|
style="text-align: right"
|
||||||
layout="prev, pager, next, total, sizes, jumper"
|
layout="prev, pager, next, total, sizes, jumper"
|
||||||
:total="props.total"
|
:total="state.total"
|
||||||
v-model:current-page="state.pageNum"
|
v-model:current-page="queryForm_.pageNum"
|
||||||
v-model:page-size="state.pageSize"
|
v-model:page-size="queryForm_.pageSize"
|
||||||
:page-sizes="pageSizes"
|
:page-sizes="pageSizes"
|
||||||
/>
|
/>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -188,256 +124,289 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toRefs, watch, reactive, onMounted } from 'vue';
|
import { toRefs, watch, reactive, onMounted, Ref, ref, useSlots } from 'vue';
|
||||||
import { TableColumn, TableQuery } from './index';
|
import { TableColumn } from './index';
|
||||||
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
import EnumTag from '@/components/enumtag/EnumTag.vue';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useVModel, useEventListener } from '@vueuse/core';
|
||||||
|
import Api from '@/common/Api';
|
||||||
|
import SearchForm from '@/components/SearchForm/index.vue';
|
||||||
|
import { SearchItem } from '../SearchForm/index';
|
||||||
|
|
||||||
const emit = defineEmits(['update:queryForm', 'update:pageNum', 'update:pageSize', 'update:selectionData', 'pageChange'])
|
const emit = defineEmits(['update:queryForm', 'update:selectionData', 'pageChange']);
|
||||||
|
|
||||||
const props = defineProps({
|
export interface PageTableProps {
|
||||||
size: {
|
size?: string;
|
||||||
type: String,
|
showSelection?: boolean;
|
||||||
default: '',
|
showSearch?: boolean; // 是否显示搜索表单
|
||||||
},
|
columns: TableColumn[]; // 列配置项 ==> 必传
|
||||||
inputWidth: {
|
data?: any[]; // 静态 table data 数据,若存在则不会使用 requestApi 返回的 data ==> 非必传
|
||||||
type: [Number, String],
|
pageApi: Api; // 请求表格数据的 api
|
||||||
default: 0,
|
lazy?: boolean; // 是否自动执行请求 api ==> 非必传(默认为false)
|
||||||
},
|
beforeQueryFn?: (params: any) => any; // 执行查询时对查询参数进行处理,调整等
|
||||||
// 是否显示选择列
|
dataHandlerFn?: (data: any) => any; // 数据处理回调函数,用于将请求回来的数据二次加工处理等
|
||||||
showSelection: {
|
searchItems?: SearchItem[];
|
||||||
type: Boolean,
|
queryForm?: any; // 查询表单参数 ==> 非必传(默认为{pageNum:1, pageSize: 10})
|
||||||
default: false,
|
border?: boolean; // 是否带有纵向边框 ==> 非必传(默认为false)
|
||||||
},
|
toolButton?: ('refresh' | 'setting' | 'search')[] | boolean; // 是否显示表格功能按钮 ==> 非必传(默认为true)
|
||||||
// 当前选择的数据
|
searchCol?: any; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
|
||||||
selectionData: {
|
}
|
||||||
type: Array<any>
|
|
||||||
},
|
// 接受父组件参数,配置默认值
|
||||||
// 列信息
|
const props = withDefaults(defineProps<PageTableProps>(), {
|
||||||
columns: {
|
columns: () => [],
|
||||||
type: Array<TableColumn>,
|
showSelection: false,
|
||||||
default: function () {
|
lazy: false,
|
||||||
return [];
|
initParam: {},
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
// 表格数据
|
|
||||||
data: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
total: {
|
|
||||||
type: [Number],
|
|
||||||
default: 0,
|
|
||||||
},
|
|
||||||
pageNum: {
|
|
||||||
type: Number,
|
|
||||||
default: 1,
|
|
||||||
},
|
|
||||||
pageSize: {
|
|
||||||
type: [Number],
|
|
||||||
default: 10,
|
|
||||||
},
|
|
||||||
// 查询条件配置
|
|
||||||
query: {
|
|
||||||
type: Array<TableQuery>,
|
|
||||||
default: function () {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 绑定的查询表单
|
|
||||||
queryForm: {
|
queryForm: {
|
||||||
type: Object,
|
pageNum: 1,
|
||||||
default: function () {
|
pageSize: 0,
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
border: false,
|
||||||
|
toolButton: true,
|
||||||
|
showSearch: false,
|
||||||
|
searchItems: () => [],
|
||||||
|
searchCol: () => ({ xs: 1, sm: 3, md: 3, lg: 4, xl: 4 }),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 接收 columns 并设置为响应式
|
||||||
|
const tableColumns = reactive<TableColumn[]>(props.columns);
|
||||||
|
|
||||||
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
pageSizes: [] as any, // 可选每页显示的数据量
|
pageSizes: [] as any, // 可选每页显示的数据量
|
||||||
pageSize: 10,
|
|
||||||
pageNum: 1,
|
|
||||||
isOpenMoreQuery: false,
|
isOpenMoreQuery: false,
|
||||||
defaultQueryCount: 2, // 默认显示的查询参数个数,展开后每行显示查询条件个数为该值加1。第一行用最后一列来占用按钮
|
defaultQueryCount: 2, // 默认显示的查询参数个数,展开后每行显示查询条件个数为该值加1。第一行用最后一列来占用按钮
|
||||||
queryForm: {} as any,
|
loading: false,
|
||||||
loadingData: false,
|
data: [],
|
||||||
|
total: 0,
|
||||||
// 输入框宽度
|
// 输入框宽度
|
||||||
inputWidth: "200px" as any,
|
inputWidth_: '200px' as any,
|
||||||
formatVal: '', // 格式化后的值
|
formatVal: '', // 格式化后的值
|
||||||
tableMaxHeight: window.innerHeight - 240 + 'px',
|
tableMaxHeight: '500px',
|
||||||
})
|
});
|
||||||
|
|
||||||
const {
|
// 是否显示搜索模块
|
||||||
pageSizes,
|
const isShowSearch = ref(props.showSearch);
|
||||||
isOpenMoreQuery,
|
|
||||||
defaultQueryCount,
|
|
||||||
queryForm,
|
|
||||||
loadingData,
|
|
||||||
inputWidth,
|
|
||||||
formatVal,
|
|
||||||
tableMaxHeight,
|
|
||||||
} = toRefs(state)
|
|
||||||
|
|
||||||
watch(() => props.queryForm, (newValue: any) => {
|
// 控制 ToolButton 显示
|
||||||
state.queryForm = newValue;
|
const showToolButton = (key: 'refresh' | 'setting' | 'search') => {
|
||||||
})
|
return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton;
|
||||||
|
};
|
||||||
|
|
||||||
watch(() => props.pageNum, (newValue: any) => {
|
const { pageSizes, formatVal, tableMaxHeight } = toRefs(state);
|
||||||
state.pageNum = newValue;
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => props.pageSize, (newValue: any) => {
|
const queryForm_: Ref<any> = useVModel(props, 'queryForm', emit);
|
||||||
state.pageSize = newValue;
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => props.data, (newValue: any) => {
|
watch(
|
||||||
|
() => state.data,
|
||||||
|
(newValue: any) => {
|
||||||
if (newValue && newValue.length > 0) {
|
if (newValue && newValue.length > 0) {
|
||||||
props.columns.forEach(item => {
|
props.columns.forEach((item) => {
|
||||||
if (item.autoWidth && item.show) {
|
if (item.autoWidth && item.show) {
|
||||||
item.autoCalculateMinWidth(props.data);
|
item.autoCalculateMinWidth(state.data);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(() => {
|
watch(
|
||||||
const pageSize = props.pageSize;
|
() => isShowSearch.value,
|
||||||
|
() => {
|
||||||
|
console.log('watch show sa');
|
||||||
|
calcuTableHeight();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
state.pageNum = props.pageNum;
|
onMounted(async () => {
|
||||||
state.pageSize = pageSize;
|
calcuTableHeight();
|
||||||
state.queryForm = props.queryForm;
|
useEventListener(window, 'resize', calcuTableHeight);
|
||||||
|
|
||||||
|
let pageSize = queryForm_.value.pageSize;
|
||||||
|
// 如果pageSize设为0,则使用系统全局配置的pageSize
|
||||||
|
if (!pageSize) {
|
||||||
|
pageSize = themeConfig.value.defaultListPageSize;
|
||||||
|
// 可能storage已经存在配置json,则可能没值,需要清storage重试
|
||||||
|
if (!pageSize) {
|
||||||
|
pageSize = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryForm_.value.pageNum = 1;
|
||||||
|
queryForm_.value.pageSize = pageSize;
|
||||||
state.pageSizes = [pageSize, pageSize * 2, pageSize * 3, pageSize * 4, pageSize * 5];
|
state.pageSizes = [pageSize, pageSize * 2, pageSize * 3, pageSize * 4, pageSize * 5];
|
||||||
|
|
||||||
// 如果没传输入框宽度,则根据组件size设置默认宽度
|
if (!props.lazy) {
|
||||||
if (!props.inputWidth) {
|
await reqPageApi();
|
||||||
state.inputWidth = props.size == 'small' ? '150px' : '200px';
|
|
||||||
} else {
|
|
||||||
state.inputWidth = props.inputWidth;
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
calcuTableHeight();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
const calcuTableHeight = () => {
|
const calcuTableHeight = () => {
|
||||||
state.tableMaxHeight = window.innerHeight - 240 + 'px';
|
const headerHeight = isShowSearch.value ? 320 : 240;
|
||||||
}
|
state.tableMaxHeight = window.innerHeight - headerHeight + 'px';
|
||||||
|
};
|
||||||
|
|
||||||
const formatText = (data: any)=> {
|
const formatText = (data: any) => {
|
||||||
state.formatVal = '';
|
state.formatVal = '';
|
||||||
try {
|
try {
|
||||||
state.formatVal = JSON.stringify(JSON.parse(data), null, 4);
|
state.formatVal = JSON.stringify(JSON.parse(data), null, 4);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.formatVal = data;
|
state.formatVal = data;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getRowQueryItem = (row: number) => {
|
|
||||||
// 第一行需要加个查询等按钮列
|
|
||||||
if (row === 1) {
|
|
||||||
const res = props.query.slice(row - 1, defaultQueryCount.value);
|
|
||||||
// 查询等按钮列
|
|
||||||
res.push(TableQuery.slot("", "", "queryBtns"));
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
const columnCount = defaultQueryCount.value + 1;
|
|
||||||
return props.query.slice((row - 1) * columnCount - 1, row * columnCount - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelectionChange = (val: any) => {
|
const handleSelectionChange = (val: any) => {
|
||||||
emit('update:selectionData', val);
|
emit('update:selectionData', val);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handlePageChange = () => {
|
const reqPageApi = async () => {
|
||||||
emit('update:pageNum', state.pageNum);
|
try {
|
||||||
|
state.loading = true;
|
||||||
|
|
||||||
|
let qf = queryForm_.value;
|
||||||
|
if (props.beforeQueryFn) {
|
||||||
|
qf = await props.beforeQueryFn(qf);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await props.pageApi?.request(qf);
|
||||||
|
if (!res) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.total = res.total;
|
||||||
|
if (props.dataHandlerFn) {
|
||||||
|
state.data = await props.dataHandlerFn(res.list);
|
||||||
|
} else {
|
||||||
|
state.data = res.list;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
state.loading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePageChange = (val: number) => {
|
||||||
|
queryForm_.value.pageNum = val;
|
||||||
execQuery();
|
execQuery();
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSizeChange = () => {
|
const handleSizeChange = () => {
|
||||||
changePageNum(1);
|
changePageNum(1);
|
||||||
emit('update:pageSize', state.pageSize);
|
|
||||||
execQuery();
|
execQuery();
|
||||||
}
|
};
|
||||||
|
|
||||||
const queryData = () => {
|
const queryData = () => {
|
||||||
changePageNum(1);
|
changePageNum(1);
|
||||||
execQuery();
|
execQuery();
|
||||||
}
|
};
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
// 将查询参数绑定的值置空,并重新粗发查询接口
|
// 将查询参数绑定的值置空,并重新粗发查询接口
|
||||||
for (let qi of props.query) {
|
for (let qi of props.searchItems) {
|
||||||
state.queryForm[qi.prop] = null;
|
queryForm_.value[qi.prop] = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
changePageNum(1);
|
changePageNum(1);
|
||||||
emit('update:queryForm', state.queryForm);
|
|
||||||
execQuery();
|
execQuery();
|
||||||
}
|
};
|
||||||
|
|
||||||
const changePageNum = (pageNum: number) => {
|
const changePageNum = (pageNum: number) => {
|
||||||
state.pageNum = pageNum;
|
queryForm_.value.pageNum = pageNum;
|
||||||
emit('update:pageNum', state.pageNum);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const execQuery = () => {
|
const execQuery = async () => {
|
||||||
emit('pageChange');
|
await reqPageApi();
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
defineExpose({
|
||||||
* 是否正在加载数据
|
search: execQuery,
|
||||||
*/
|
});
|
||||||
const loading = (loading: boolean) => {
|
|
||||||
state.loadingData = loading;
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({ loading })
|
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.page-table {
|
.table-box,
|
||||||
.query {
|
.table-main {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
// 表格 header 样式
|
||||||
|
.table-header {
|
||||||
|
width: 100%;
|
||||||
|
.header-button-lf {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-button-ri {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.is-open {
|
|
||||||
// padding: 10px 0;
|
|
||||||
max-height: 200px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
.slot {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
// el-table 表格样式
|
||||||
margin-top: 10px;
|
.el-table {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
// 修复 safari 浏览器表格错位 https://github.com/HalseySpicy/Geeker-Admin/issues/83
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .el-table__header th {
|
||||||
|
// height: 45px;
|
||||||
|
// font-size: 15px;
|
||||||
|
// font-weight: bold;
|
||||||
|
// color: var(--el-text-color-primary);
|
||||||
|
// background: var(--el-fill-color-light);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .el-table__row {
|
||||||
|
// height: 45px;
|
||||||
|
// font-size: 14px;
|
||||||
|
|
||||||
|
// .move {
|
||||||
|
// cursor: move;
|
||||||
|
|
||||||
|
// .el-icon {
|
||||||
|
// cursor: move;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 设置 el-table 中 header 文字不换行,并省略
|
||||||
|
.el-table__header .el-table__cell > .cell {
|
||||||
|
// white-space: nowrap;
|
||||||
|
white-space: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解决表格数据为空时样式不居中问题(仅在element-plus中)
|
||||||
|
// .el-table__empty-block {
|
||||||
|
// position: absolute;
|
||||||
|
// top: 50%;
|
||||||
|
// left: 50%;
|
||||||
|
// transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
// .table-empty {
|
||||||
|
// line-height: 30px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// table 中 image 图片样式
|
||||||
|
.table-image {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.el-form-item__label) {
|
::v-deep(.el-form-item__label) {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-select-v2 {
|
|
||||||
width: v-bind(inputWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-input {
|
|
||||||
width: v-bind(inputWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-select {
|
|
||||||
width: v-bind(inputWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-date-editor {
|
|
||||||
width: 380px !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -242,68 +242,3 @@ export class TableColumn {
|
|||||||
this.minWidth = (flexWidth > 400 ? 400 : flexWidth) + this.addWidth;
|
this.minWidth = (flexWidth > 400 ? 400 : flexWidth) + this.addWidth;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TableQuery {
|
|
||||||
/**
|
|
||||||
* 属性字段
|
|
||||||
*/
|
|
||||||
prop: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示表头
|
|
||||||
*/
|
|
||||||
label: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询类型,text、select、date
|
|
||||||
*/
|
|
||||||
type: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* select可选值
|
|
||||||
*/
|
|
||||||
options: any;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插槽名
|
|
||||||
*/
|
|
||||||
slot: string;
|
|
||||||
|
|
||||||
constructor(prop: string, label: string) {
|
|
||||||
this.prop = prop;
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
static new(prop: string, label: string): TableQuery {
|
|
||||||
return new TableQuery(prop, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
static text(prop: string, label: string): TableQuery {
|
|
||||||
const tq = new TableQuery(prop, label);
|
|
||||||
tq.type = 'text';
|
|
||||||
return tq;
|
|
||||||
}
|
|
||||||
|
|
||||||
static select(prop: string, label: string): TableQuery {
|
|
||||||
const tq = new TableQuery(prop, label);
|
|
||||||
tq.type = 'select';
|
|
||||||
return tq;
|
|
||||||
}
|
|
||||||
|
|
||||||
static date(prop: string, label: string): TableQuery {
|
|
||||||
const tq = new TableQuery(prop, label);
|
|
||||||
tq.type = 'date';
|
|
||||||
return tq;
|
|
||||||
}
|
|
||||||
|
|
||||||
static slot(prop: string, label: string, slotName: string): TableQuery {
|
|
||||||
const tq = new TableQuery(prop, label);
|
|
||||||
tq.slot = slotName;
|
|
||||||
return tq;
|
|
||||||
}
|
|
||||||
|
|
||||||
setOptions(options: any): TableQuery {
|
|
||||||
this.options = options;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export const buildProgressProps = (): any => {
|
||||||
|
return {
|
||||||
|
progress: {
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
executedStatements: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<el-descriptions border size="small" :title="`${progress.title}`">
|
||||||
|
<el-descriptions-item label="时间">{{ state.elapsedTime }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="已处理">{{ progress.executedStatements }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onMounted, onUnmounted, reactive } from 'vue';
|
||||||
|
import { formatTime } from 'element-plus/es/components/countdown/src/utils';
|
||||||
|
import { buildProgressProps } from './progress-notify';
|
||||||
|
|
||||||
|
const props = defineProps(buildProgressProps());
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
elapsedTime: '00:00:00',
|
||||||
|
});
|
||||||
|
|
||||||
|
let timer: any = undefined;
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
timer = setInterval(() => {
|
||||||
|
const elapsed = Date.now() - startTime;
|
||||||
|
state.elapsedTime = formatTime(elapsed, 'HH:mm:ss');
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(async () => {
|
||||||
|
if (timer != undefined) {
|
||||||
|
clearInterval(timer); // 在Vue实例销毁前,清除我们的定时器
|
||||||
|
timer = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
285
mayfly_go_web/src/components/terminal/TerminalBody.vue
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
<template>
|
||||||
|
<div id="terminal-body" :style="{ height, background: themeConfig.terminalBackground }">
|
||||||
|
<div ref="terminalRef" class="terminal" />
|
||||||
|
|
||||||
|
<TerminalSearch ref="terminalSearchRef" :search-addon="state.addon.search" @close="focus" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import 'xterm/css/xterm.css';
|
||||||
|
import { Terminal } from 'xterm';
|
||||||
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
|
import { SearchAddon } from 'xterm-addon-search';
|
||||||
|
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||||
|
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
|
import { ref, nextTick, reactive, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||||
|
import TerminalSearch from './TerminalSearch.vue';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import { TerminalStatus } from './common';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* 初始化执行命令
|
||||||
|
*/
|
||||||
|
cmd: { type: String },
|
||||||
|
/**
|
||||||
|
* 连接url
|
||||||
|
*/
|
||||||
|
socketUrl: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 高度
|
||||||
|
*/
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['statusChange']);
|
||||||
|
|
||||||
|
const terminalRef: any = ref(null);
|
||||||
|
const terminalSearchRef: any = ref(null);
|
||||||
|
|
||||||
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
|
|
||||||
|
// 终端实例
|
||||||
|
let term: Terminal;
|
||||||
|
let socket: WebSocket;
|
||||||
|
let pingInterval: any;
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
// 插件
|
||||||
|
addon: {
|
||||||
|
fit: null as any,
|
||||||
|
search: null as any,
|
||||||
|
weblinks: null as any,
|
||||||
|
},
|
||||||
|
status: TerminalStatus.NoConnected,
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => state.status,
|
||||||
|
() => {
|
||||||
|
emit('statusChange', state.status);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
close();
|
||||||
|
});
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (term) {
|
||||||
|
console.log('重新连接...');
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
term = new Terminal({
|
||||||
|
fontSize: themeConfig.value.terminalFontSize || 15,
|
||||||
|
fontWeight: themeConfig.value.terminalFontWeight || 'normal',
|
||||||
|
fontFamily: 'JetBrainsMono, monaco, Consolas, Lucida Console, monospace',
|
||||||
|
cursorBlink: true,
|
||||||
|
disableStdin: false,
|
||||||
|
allowProposedApi: true,
|
||||||
|
theme: {
|
||||||
|
foreground: themeConfig.value.terminalForeground || '#7e9192', //字体
|
||||||
|
background: themeConfig.value.terminalBackground || '#002833', //背景色
|
||||||
|
cursor: themeConfig.value.terminalCursor || '#268F81', //设置光标
|
||||||
|
// cursorAccent: "red", // 光标停止颜色
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
term.open(terminalRef.value);
|
||||||
|
|
||||||
|
// 注册自适应组件
|
||||||
|
const fitAddon = new FitAddon();
|
||||||
|
state.addon.fit = fitAddon;
|
||||||
|
term.loadAddon(fitAddon);
|
||||||
|
fitTerminal();
|
||||||
|
|
||||||
|
// 注册搜索组件
|
||||||
|
const searchAddon = new SearchAddon();
|
||||||
|
state.addon.search = searchAddon;
|
||||||
|
term.loadAddon(searchAddon);
|
||||||
|
|
||||||
|
// 注册 url link组件
|
||||||
|
const weblinks = new WebLinksAddon();
|
||||||
|
state.addon.weblinks = weblinks;
|
||||||
|
term.loadAddon(weblinks);
|
||||||
|
|
||||||
|
// 初始化websocket
|
||||||
|
initSocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 连接成功
|
||||||
|
*/
|
||||||
|
const onConnected = () => {
|
||||||
|
// 注册心跳
|
||||||
|
pingInterval = setInterval(sendPing, 15000);
|
||||||
|
|
||||||
|
// 注册 terminal 事件
|
||||||
|
term.onResize((event) => sendResize(event.cols, event.rows));
|
||||||
|
term.onData((event) => sendCmd(event));
|
||||||
|
|
||||||
|
// 注册自定义快捷键
|
||||||
|
term.attachCustomKeyEventHandler((event: KeyboardEvent) => {
|
||||||
|
// 注册搜索键 ctrl + f
|
||||||
|
if (event.key === 'f' && (event.ctrlKey || event.metaKey) && event.type === 'keydown') {
|
||||||
|
event.preventDefault();
|
||||||
|
terminalSearchRef.value.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.status = TerminalStatus.Connected;
|
||||||
|
|
||||||
|
// 注册窗口大小监听器
|
||||||
|
window.addEventListener('resize', debounce(fitTerminal, 400));
|
||||||
|
|
||||||
|
focus();
|
||||||
|
|
||||||
|
// 如果有初始要执行的命令,则发送执行命令
|
||||||
|
if (props.cmd) {
|
||||||
|
sendCmd(props.cmd + ' \r');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 自适应终端
|
||||||
|
const fitTerminal = () => {
|
||||||
|
const dimensions = state.addon.fit && state.addon.fit.proposeDimensions();
|
||||||
|
if (!dimensions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (dimensions?.cols && dimensions?.rows) {
|
||||||
|
term.resize(dimensions.cols, dimensions.rows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const focus = () => {
|
||||||
|
setTimeout(() => term.focus(), 400);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
term.clear();
|
||||||
|
term.clearSelection();
|
||||||
|
term.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
function initSocket() {
|
||||||
|
if (props.socketUrl) {
|
||||||
|
let socketUrl = `${props.socketUrl}&rows=${term.rows}&cols=${term.cols}`;
|
||||||
|
socket = new WebSocket(socketUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听socket连接
|
||||||
|
socket.onopen = () => {
|
||||||
|
onConnected();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听socket错误信息
|
||||||
|
socket.onerror = (e: Event) => {
|
||||||
|
term.writeln('\r\n\x1b[31m提示: 连接错误...');
|
||||||
|
state.status = TerminalStatus.Error;
|
||||||
|
console.log('连接错误', e);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = (e: CloseEvent) => {
|
||||||
|
console.log('terminal socket close...', e.reason);
|
||||||
|
// 关闭窗口大小监听器
|
||||||
|
window.removeEventListener('resize', debounce(fitTerminal, 100));
|
||||||
|
// 清除 ping
|
||||||
|
pingInterval && clearInterval(pingInterval);
|
||||||
|
state.status = TerminalStatus.Disconnected;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听socket消息
|
||||||
|
socket.onmessage = getMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMessage(msg: any) {
|
||||||
|
// msg.data是真正后端返回的数据
|
||||||
|
term.write(msg.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MsgType {
|
||||||
|
Resize = 1,
|
||||||
|
Data = 2,
|
||||||
|
Ping = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
const send = (msg: any) => {
|
||||||
|
state.status == TerminalStatus.Connected && socket.send(JSON.stringify(msg));
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendResize = (cols: number, rows: number) => {
|
||||||
|
send({
|
||||||
|
type: MsgType.Resize,
|
||||||
|
Cols: cols,
|
||||||
|
Rows: rows,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendPing = () => {
|
||||||
|
send({
|
||||||
|
type: MsgType.Ping,
|
||||||
|
msg: 'ping',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function sendCmd(key: any) {
|
||||||
|
send({
|
||||||
|
type: MsgType.Data,
|
||||||
|
msg: key,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSocket() {
|
||||||
|
// 关闭 websocket
|
||||||
|
socket && socket.readyState === 1 && socket.close();
|
||||||
|
// 清除 ping
|
||||||
|
pingInterval && clearInterval(pingInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
console.log('in terminal body close');
|
||||||
|
closeSocket();
|
||||||
|
if (term) {
|
||||||
|
state.addon.search.dispose();
|
||||||
|
state.addon.fit.dispose();
|
||||||
|
state.addon.weblinks.dispose();
|
||||||
|
term.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatus = (): TerminalStatus => {
|
||||||
|
return state.status;
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ init, fitTerminal, focus, clear, close, getStatus });
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
#terminal-body {
|
||||||
|
background: #212529;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.terminal {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.xterm .xterm-viewport {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
321
mayfly_go_web/src/components/terminal/TerminalDialog.vue
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="terminal-dialog-container" v-for="openTerminal of terminals" :key="openTerminal.terminalId">
|
||||||
|
<el-dialog
|
||||||
|
title="终端"
|
||||||
|
v-model="openTerminal.visible"
|
||||||
|
top="32px"
|
||||||
|
class="terminal-dialog"
|
||||||
|
width="75%"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:modal="true"
|
||||||
|
:show-close="false"
|
||||||
|
:fullscreen="openTerminal.fullscreen"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="terminal-title-wrapper">
|
||||||
|
<!-- 左侧 -->
|
||||||
|
<div class="title-left-fixed">
|
||||||
|
<!-- title信息 -->
|
||||||
|
<div>
|
||||||
|
<slot name="headerTitle" :terminalInfo="openTerminal">
|
||||||
|
{{ openTerminal.headerTitle }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧 -->
|
||||||
|
<div class="title-right-fixed">
|
||||||
|
<el-popconfirm @confirm="reConnect(openTerminal.terminalId)" title="确认重新连接?">
|
||||||
|
<template #reference>
|
||||||
|
<div class="mr15 pointer">
|
||||||
|
<el-tag v-if="openTerminal.status == TerminalStatus.Connected" type="success" effect="light" round> 已连接 </el-tag>
|
||||||
|
<el-tag v-else type="danger" effect="light" round> 未连接 </el-tag>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-popconfirm>
|
||||||
|
|
||||||
|
<el-popover placement="bottom" :width="200" trigger="hover">
|
||||||
|
<template #reference>
|
||||||
|
<SvgIcon name="QuestionFilled" :size="20" class="pointer-icon mr10" />
|
||||||
|
</template>
|
||||||
|
<div>ctrl | command + f (搜索)</div>
|
||||||
|
<div class="mt5">点击连接状态可重连</div>
|
||||||
|
</el-popover>
|
||||||
|
|
||||||
|
<SvgIcon
|
||||||
|
name="ArrowDown"
|
||||||
|
v-if="props.visibleMinimize"
|
||||||
|
@click="minimize(openTerminal.terminalId)"
|
||||||
|
:size="20"
|
||||||
|
class="pointer-icon mr10"
|
||||||
|
title="最小化"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SvgIcon name="FullScreen" @click="handlerFullScreen(openTerminal)" :size="20" class="pointer-icon mr10" title="全屏|退出全屏" />
|
||||||
|
|
||||||
|
<SvgIcon name="Close" class="pointer-icon" @click="close(openTerminal.terminalId)" title="关闭" :size="20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="terminal-wrapper" :style="{ height: `calc(100vh - ${openTerminal.fullscreen ? '47px' : '200px'})` }">
|
||||||
|
<TerminalBody
|
||||||
|
@status-change="terminalStatusChange(openTerminal.terminalId, $event)"
|
||||||
|
:ref="(el) => setTerminalRef(el, openTerminal.terminalId)"
|
||||||
|
:cmd="openTerminal.cmd"
|
||||||
|
:socket-url="openTerminal.socketUrl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 终端最小化 -->
|
||||||
|
<div class="terminal-minimize-container">
|
||||||
|
<el-card
|
||||||
|
v-for="minimizeTerminal of minimizeTerminals"
|
||||||
|
:key="minimizeTerminal.terminalId"
|
||||||
|
:class="`terminal-minimize-item pointer ${minimizeTerminal.styleClass}`"
|
||||||
|
size="small"
|
||||||
|
@click="maximize(minimizeTerminal.terminalId)"
|
||||||
|
>
|
||||||
|
<el-tooltip :content="minimizeTerminal.desc" placement="top">
|
||||||
|
<span>
|
||||||
|
{{ minimizeTerminal.title }}
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 关闭按钮 -->
|
||||||
|
<SvgIcon name="CloseBold" @click.stop="closeMinimizeTerminal(minimizeTerminal.terminalId)" class="ml10 pointer-icon fr" :size="20" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { toRefs, reactive } from 'vue';
|
||||||
|
import TerminalBody from '@/components/terminal/TerminalBody.vue';
|
||||||
|
import SvgIcon from '@/components/svgIcon/index.vue';
|
||||||
|
import { TerminalStatus } from './common';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
visibleMinimize: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['close', 'minimize']);
|
||||||
|
|
||||||
|
const openTerminalRefs: any = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
terminal对象信息:
|
||||||
|
|
||||||
|
visible: false,
|
||||||
|
machineId: null as any,
|
||||||
|
terminalId: null as any,
|
||||||
|
machine: {} as any,
|
||||||
|
fullscreen: false,
|
||||||
|
*/
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
terminals: {} as any, // key -> terminalId value -> terminal
|
||||||
|
minimizeTerminals: {} as any, // key -> terminalId value -> 简易terminal
|
||||||
|
});
|
||||||
|
|
||||||
|
const { terminals, minimizeTerminals } = toRefs(state);
|
||||||
|
|
||||||
|
const setTerminalRef = (el: any, terminalId: any) => {
|
||||||
|
if (terminalId) {
|
||||||
|
openTerminalRefs[terminalId] = el;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function open(terminalInfo: any, cmd: string = '') {
|
||||||
|
let terminalId = terminalInfo.terminalId;
|
||||||
|
if (!terminalId) {
|
||||||
|
terminalId = Date.now();
|
||||||
|
}
|
||||||
|
state.terminals[terminalId] = {
|
||||||
|
...terminalInfo,
|
||||||
|
terminalId,
|
||||||
|
visible: true,
|
||||||
|
cmd,
|
||||||
|
status: TerminalStatus.NoConnected,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const terminalStatusChange = (terminalId: string, status: TerminalStatus) => {
|
||||||
|
const terminal = state.terminals[terminalId];
|
||||||
|
if (terminal) {
|
||||||
|
terminal.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minTerminal = state.minimizeTerminals[terminalId];
|
||||||
|
if (!minTerminal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
minTerminal.styleClass = getTerminalStatysStyleClass(terminalId, status);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTerminalStatysStyleClass = (terminalId: any, status: any = null) => {
|
||||||
|
if (status == null) {
|
||||||
|
status = openTerminalRefs[terminalId].getStatus();
|
||||||
|
}
|
||||||
|
if (status == TerminalStatus.Connected) {
|
||||||
|
return 'terminal-status-success';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == TerminalStatus.NoConnected) {
|
||||||
|
return 'terminal-status-no-connect';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'terminal-status-error';
|
||||||
|
};
|
||||||
|
|
||||||
|
const reConnect = (terminalId: any) => {
|
||||||
|
openTerminalRefs[terminalId].init();
|
||||||
|
};
|
||||||
|
|
||||||
|
function close(terminalId: any) {
|
||||||
|
console.log('in terminal dialog close');
|
||||||
|
delete state.terminals[terminalId];
|
||||||
|
|
||||||
|
// 关闭终端,并删除终端ref
|
||||||
|
const terminalRef = openTerminalRefs[terminalId];
|
||||||
|
terminalRef && terminalRef.close();
|
||||||
|
delete openTerminalRefs[terminalId];
|
||||||
|
|
||||||
|
emit('close', terminalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function minimize(terminalId: number) {
|
||||||
|
console.log('in terminal dialog minimize: ', terminalId);
|
||||||
|
|
||||||
|
const terminal = state.terminals[terminalId];
|
||||||
|
if (!terminal) {
|
||||||
|
console.warn('不存在该终端信息: ', terminalId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
terminal.visible = false;
|
||||||
|
|
||||||
|
const minTerminalInfo = {
|
||||||
|
terminalId: terminal.terminalId,
|
||||||
|
title: terminal.minTitle, // 截取terminalId最后两位区分多个terminal
|
||||||
|
desc: terminal.minDesc,
|
||||||
|
styleClass: getTerminalStatysStyleClass(terminalId),
|
||||||
|
};
|
||||||
|
state.minimizeTerminals[terminalId] = minTerminalInfo;
|
||||||
|
|
||||||
|
emit('minimize', minTerminalInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maximize(terminalId: any) {
|
||||||
|
console.log('in terminal dialog maximize: ', terminalId);
|
||||||
|
const minTerminal = state.minimizeTerminals[terminalId];
|
||||||
|
if (!minTerminal) {
|
||||||
|
console.log('no min terminal...');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delete state.minimizeTerminals[terminalId];
|
||||||
|
|
||||||
|
// 显示终端信息
|
||||||
|
state.terminals[terminalId].visible = true;
|
||||||
|
|
||||||
|
const terminalRef = openTerminalRefs[terminalId];
|
||||||
|
// fit
|
||||||
|
setTimeout(() => {
|
||||||
|
terminalRef.fitTerminal();
|
||||||
|
terminalRef.focus();
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlerFullScreen = (terminal: any) => {
|
||||||
|
terminal.fullscreen = !terminal.fullscreen;
|
||||||
|
const terminalRef = openTerminalRefs[terminal.terminalId];
|
||||||
|
// fit
|
||||||
|
setTimeout(() => {
|
||||||
|
terminalRef?.fitTerminal();
|
||||||
|
terminalRef?.focus();
|
||||||
|
}, 250);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeMinimizeTerminal = (terminalId: any) => {
|
||||||
|
delete state.minimizeTerminals[terminalId];
|
||||||
|
close(terminalId);
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
open,
|
||||||
|
close,
|
||||||
|
minimize,
|
||||||
|
maximize,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.terminal-dialog-container {
|
||||||
|
.el-dialog__header {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消body最大高度,否则全屏有问题
|
||||||
|
.el-dialog__body {
|
||||||
|
max-height: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-overlay .el-overlay-dialog .el-dialog .el-dialog__body {
|
||||||
|
padding: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
.title-right-fixed {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 20px;
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-minimize-container {
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
bottom: 16px;
|
||||||
|
z-index: 10;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.terminal-minimize-item {
|
||||||
|
min-width: 120px;
|
||||||
|
// box-shadow: 0 3px 4px #dee2e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 1px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-status-error {
|
||||||
|
box-shadow: 0 3px 4px var(--el-color-danger);
|
||||||
|
border-color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-status-no-connect {
|
||||||
|
box-shadow: 0 3px 4px var(--el-color-warning);
|
||||||
|
border-color: var(--el-color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-status-success {
|
||||||
|
box-shadow: 0 3px 4px var(--el-color-success);
|
||||||
|
border-color: var(--el-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card__body {
|
||||||
|
padding: 15px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
149
mayfly_go_web/src/components/terminal/TerminalSearch.vue
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<template>
|
||||||
|
<div id="search-card" v-show="search.visible" @keydown.esc="closeSearch">
|
||||||
|
<el-card title="搜索" size="small">
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<el-input
|
||||||
|
class="search-input"
|
||||||
|
ref="searchInputRef"
|
||||||
|
placeholder="请输入查找内容,回车搜索"
|
||||||
|
v-model="search.value"
|
||||||
|
@keyup.enter.native="searchKeywords(true)"
|
||||||
|
clearable
|
||||||
|
>
|
||||||
|
</el-input>
|
||||||
|
<!-- 选项 -->
|
||||||
|
<div class="search-options">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-checkbox class="usn" v-model="search.regex"> 正则匹配 </el-checkbox>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-checkbox class="usn" v-model="search.words"> 单词全匹配 </el-checkbox>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-checkbox class="usn" v-model="search.matchCase"> 区分大小写 </el-checkbox>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-checkbox class="usn" v-model="search.incremental"> 增量查找 </el-checkbox>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
<!-- 按钮 -->
|
||||||
|
<div class="search-buttons">
|
||||||
|
<el-button class="terminal-search-button search-button-prev" type="primary" size="small" @click="searchKeywords(false)"> 上一个 </el-button>
|
||||||
|
<el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="searchKeywords(true)"> 下一个 </el-button>
|
||||||
|
<el-button class="terminal-search-button search-button-next" type="primary" size="small" @click="closeSearch"> 关闭 </el-button>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, toRefs, nextTick, reactive } from 'vue';
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { SearchAddon, ISearchOptions } from 'xterm-addon-search';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
searchAddon: {
|
||||||
|
type: [SearchAddon],
|
||||||
|
require: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
search: {
|
||||||
|
visible: false,
|
||||||
|
value: '',
|
||||||
|
regex: false,
|
||||||
|
words: false,
|
||||||
|
matchCase: false,
|
||||||
|
incremental: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { search } = toRefs(state);
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
const searchInputRef: any = ref(null);
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
const visible = state.search.visible;
|
||||||
|
state.search.visible = !visible;
|
||||||
|
console.log(state.search.visible);
|
||||||
|
if (!visible) {
|
||||||
|
nextTick(() => {
|
||||||
|
searchInputRef.value.focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeSearch() {
|
||||||
|
state.search.visible = false;
|
||||||
|
state.search.value = '';
|
||||||
|
props.searchAddon?.clearDecorations();
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchKeywords(direction: any) {
|
||||||
|
if (!state.search.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const option = {
|
||||||
|
regex: state.search.regex,
|
||||||
|
wholeWord: state.search.words,
|
||||||
|
caseSensitive: state.search.matchCase,
|
||||||
|
incremental: state.search.incremental,
|
||||||
|
};
|
||||||
|
let res;
|
||||||
|
if (direction) {
|
||||||
|
res = props.searchAddon?.findNext(state.search.value, getSearchOptions(option));
|
||||||
|
} else {
|
||||||
|
res = props.searchAddon?.findPrevious(state.search.value, getSearchOptions(option));
|
||||||
|
}
|
||||||
|
if (!res) {
|
||||||
|
ElMessage.info('未查询到匹配项');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSearchOptions = (searchOptions?: ISearchOptions): ISearchOptions => {
|
||||||
|
return {
|
||||||
|
...searchOptions,
|
||||||
|
decorations: {
|
||||||
|
matchOverviewRuler: '#888888',
|
||||||
|
activeMatchColorOverviewRuler: '#ffff00',
|
||||||
|
matchBackground: '#888888',
|
||||||
|
activeMatchBackground: '#ffff00',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
#search-card {
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 1200;
|
||||||
|
width: 270px;
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-options {
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-buttons {
|
||||||
|
margin-top: 5px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-search-button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
6
mayfly_go_web/src/components/terminal/common.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export enum TerminalStatus {
|
||||||
|
Error = -1,
|
||||||
|
NoConnected = 0,
|
||||||
|
Connected = 1,
|
||||||
|
Disconnected = 2,
|
||||||
|
}
|
||||||
@@ -21,8 +21,8 @@ import pinia from '@/store/index';
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { useRoutesList } from '@/store/routesList';
|
import { useRoutesList } from '@/store/routesList';
|
||||||
import Logo from '@/views/layout/logo/index.vue';
|
import Logo from '@/layout/logo/index.vue';
|
||||||
import Vertical from '@/views/layout/navMenu/vertical.vue';
|
import Vertical from '@/layout/navMenu/vertical.vue';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { proxy } = getCurrentInstance() as any;
|
||||||
@@ -38,8 +38,7 @@ const state: any = reactive({
|
|||||||
// 设置菜单展开/收起时的宽度
|
// 设置菜单展开/收起时的宽度
|
||||||
const setCollapseWidth = computed(() => {
|
const setCollapseWidth = computed(() => {
|
||||||
let { layout, isCollapse, menuBar } = themeConfig.value;
|
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') {
|
||||||
// 分栏布局,菜单收起时宽度给 1px
|
// 分栏布局,菜单收起时宽度给 1px
|
||||||
if (isCollapse) {
|
if (isCollapse) {
|
||||||
@@ -2,11 +2,18 @@
|
|||||||
<div class="layout-columns-aside">
|
<div class="layout-columns-aside">
|
||||||
<el-scrollbar>
|
<el-scrollbar>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(v, k) in state.columnsAsideList" :key="k" @click="onColumnsAsideMenuClick(v, k)" :ref="
|
<li
|
||||||
|
v-for="(v, k) in state.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': state.liIndex === k }"
|
||||||
|
:title="v.meta.title"
|
||||||
|
>
|
||||||
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
|
<div class="layout-columns-aside-li-box" v-if="!v.meta.link || (v.meta.link && v.meta.linkType == 1)">
|
||||||
<i :class="v.meta.icon"></i>
|
<i :class="v.meta.icon"></i>
|
||||||
<div class="layout-columns-aside-li-box-title font12">
|
<div class="layout-columns-aside-li-box-title font12">
|
||||||
@@ -166,7 +173,7 @@ onBeforeRouteUpdate((to) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.columns-round {
|
.columns-round {
|
||||||
background: var(--color-primary);
|
background: var(--el-color-primary);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import NavBarsIndex from '@/views/layout/navBars/index.vue';
|
import NavBarsIndex from '@/layout/navBars/index.vue';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutHeader',
|
name: 'layoutHeader',
|
||||||
@@ -1,16 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-main class="layout-main">
|
<el-main class="layout-main">
|
||||||
<el-scrollbar class="layout-scrollbar" ref="layoutScrollbarRef"
|
<el-scrollbar
|
||||||
|
class="layout-scrollbar"
|
||||||
|
ref="layoutScrollbarRef"
|
||||||
v-show="!state.currentRouteMeta.link && state.currentRouteMeta.linkType != 1"
|
v-show="!state.currentRouteMeta.link && state.currentRouteMeta.linkType != 1"
|
||||||
:style="{ minHeight: `calc(100vh - ${state.headerHeight}` }">
|
:style="{ minHeight: `calc(100vh - ${state.headerHeight}` }"
|
||||||
|
>
|
||||||
<LayoutParentView />
|
<LayoutParentView />
|
||||||
<Footer v-if="themeConfig.isFooter" />
|
<Footer v-if="themeConfig.isFooter" />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<Link :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
|
<Link
|
||||||
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 2" />
|
:style="{ height: `calc(100vh - ${state.headerHeight}` }"
|
||||||
<Iframes :style="{ height: `calc(100vh - ${state.headerHeight}` }" :meta="state.currentRouteMeta"
|
:meta="state.currentRouteMeta"
|
||||||
|
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 2"
|
||||||
|
/>
|
||||||
|
<Iframes
|
||||||
|
:style="{ height: `calc(100vh - ${state.headerHeight}` }"
|
||||||
|
:meta="state.currentRouteMeta"
|
||||||
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 1 && state.isShowLink"
|
v-if="state.currentRouteMeta.link && state.currentRouteMeta.linkType == 1 && state.isShowLink"
|
||||||
@getCurrentRouteMeta="onGetCurrentRouteMeta" />
|
@getCurrentRouteMeta="onGetCurrentRouteMeta"
|
||||||
|
/>
|
||||||
</el-main>
|
</el-main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -19,10 +28,10 @@ import { reactive, getCurrentInstance, watch, onBeforeMount } from 'vue';
|
|||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import LayoutParentView from '@/views/layout/routerView/parent.vue';
|
import LayoutParentView from '@/layout/routerView/parent.vue';
|
||||||
import Footer from '@/views/layout/footer/index.vue';
|
import Footer from '@/layout/footer/index.vue';
|
||||||
import Link from '@/views/layout/routerView/link.vue';
|
import Link from '@/layout/routerView/link.vue';
|
||||||
import Iframes from '@/views/layout/routerView/iframes.vue';
|
import Iframes from '@/layout/routerView/iframes.vue';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { proxy } = getCurrentInstance() as any;
|
||||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
@@ -48,7 +57,7 @@ const initCurrentRouteMeta = (meta: object) => {
|
|||||||
// 设置 main 的高度
|
// 设置 main 的高度
|
||||||
const initHeaderHeight = () => {
|
const initHeaderHeight = () => {
|
||||||
let { isTagsview } = themeConfig.value;
|
let { isTagsview } = themeConfig.value;
|
||||||
if (isTagsview) return (state.headerHeight = `84px`);
|
if (isTagsview) return (state.headerHeight = `77px`);
|
||||||
else return (state.headerHeight = `50px`);
|
else return (state.headerHeight = `50px`);
|
||||||
};
|
};
|
||||||
// 页面加载前
|
// 页面加载前
|
||||||
@@ -58,7 +67,7 @@ onBeforeMount(() => {
|
|||||||
});
|
});
|
||||||
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
// 监听 themeConfig 配置文件的变化,更新菜单 el-scrollbar 的高度
|
||||||
watch(themeConfig.value, (val) => {
|
watch(themeConfig.value, (val) => {
|
||||||
state.headerHeight = val.isTagsview ? '84px' : '50px';
|
state.headerHeight = val.isTagsview ? '77px' : '50px';
|
||||||
if (val.isFixedHeaderChange !== val.isFixedHeader) {
|
if (val.isFixedHeaderChange !== val.isFixedHeader) {
|
||||||
if (!proxy.$refs.layoutScrollbarRef) return false;
|
if (!proxy.$refs.layoutScrollbarRef) return false;
|
||||||
proxy.$refs.layoutScrollbarRef.update();
|
proxy.$refs.layoutScrollbarRef.update();
|
||||||
@@ -10,10 +10,10 @@ import { onBeforeMount, onUnmounted } from 'vue';
|
|||||||
import { getLocal, setLocal } from '@/common/utils/storage';
|
import { getLocal, setLocal } from '@/common/utils/storage';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import Defaults from '@/views/layout/main/defaults.vue';
|
import Defaults from '@/layout/main/defaults.vue';
|
||||||
import Classic from '@/views/layout/main/classic.vue';
|
import Classic from '@/layout/main/classic.vue';
|
||||||
import Transverse from '@/views/layout/main/transverse.vue';
|
import Transverse from '@/layout/main/transverse.vue';
|
||||||
import Columns from '@/views/layout/main/columns.vue';
|
import Columns from '@/layout/main/columns.vue';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
|
|
||||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
352
mayfly_go_web/src/layout/lockScreen/index.vue
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<template>
|
||||||
|
<div v-show="state.isShowLockScreen">
|
||||||
|
<div class="layout-lock-screen-mask"></div>
|
||||||
|
<div class="layout-lock-screen-img" :class="{ 'layout-lock-screen-filter': state.isShowLoockLogin }"></div>
|
||||||
|
<div class="layout-lock-screen">
|
||||||
|
<div
|
||||||
|
class="layout-lock-screen-date"
|
||||||
|
ref="layoutLockScreenDateRef"
|
||||||
|
@mousedown="onDownPc"
|
||||||
|
@mousemove="onMovePc"
|
||||||
|
@mouseup="onEnd"
|
||||||
|
@touchstart.stop="onDownApp"
|
||||||
|
@touchmove.stop="onMoveApp"
|
||||||
|
@touchend.stop="onEnd"
|
||||||
|
>
|
||||||
|
<div class="layout-lock-screen-date-box">
|
||||||
|
<div class="layout-lock-screen-date-box-time">
|
||||||
|
{{ state.time.hm }}<span class="layout-lock-screen-date-box-minutes">{{ state.time.s }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="layout-lock-screen-date-box-info">{{ state.time.mdq }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="layout-lock-screen-date-top">
|
||||||
|
<SvgIcon name="ele-Top" />
|
||||||
|
<div class="layout-lock-screen-date-top-text">上滑解锁</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<transition name="el-zoom-in-center">
|
||||||
|
<div v-show="state.isShowLoockLogin" class="layout-lock-screen-login">
|
||||||
|
<div class="layout-lock-screen-login-box">
|
||||||
|
<div class="layout-lock-screen-login-box-img">
|
||||||
|
<img src="https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500" />
|
||||||
|
</div>
|
||||||
|
<div class="layout-lock-screen-login-box-name">Administrator</div>
|
||||||
|
<div class="layout-lock-screen-login-box-value">
|
||||||
|
<el-input
|
||||||
|
placeholder="请输入密码"
|
||||||
|
ref="layoutLockScreenInputRef"
|
||||||
|
v-model="state.lockScreenPassword"
|
||||||
|
@keyup.enter.native.stop="onLockScreenSubmit()"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button @click="onLockScreenSubmit">
|
||||||
|
<el-icon class="el-input__icon">
|
||||||
|
<ele-Right />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="layout-lock-screen-login-icon">
|
||||||
|
<SvgIcon name="ele-Microphone" :size="20" />
|
||||||
|
<SvgIcon name="ele-AlarmClock" :size="20" />
|
||||||
|
<SvgIcon name="ele-SwitchButton" :size="20" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="layoutLockScreen">
|
||||||
|
import { nextTick, onMounted, reactive, ref, onUnmounted } from 'vue';
|
||||||
|
import { formatDate } from '@/common/utils/format';
|
||||||
|
import { setLocal } from '@/common/utils/storage';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
|
|
||||||
|
// 定义变量内容
|
||||||
|
const layoutLockScreenDateRef = ref<any>();
|
||||||
|
const layoutLockScreenInputRef = ref();
|
||||||
|
const storesThemeConfig = useThemeConfig();
|
||||||
|
const { themeConfig } = storeToRefs(storesThemeConfig);
|
||||||
|
const state = reactive({
|
||||||
|
transparency: 1,
|
||||||
|
downClientY: 0,
|
||||||
|
moveDifference: 0,
|
||||||
|
isShowLoockLogin: false,
|
||||||
|
isFlags: false,
|
||||||
|
querySelectorEl: '' as any,
|
||||||
|
time: {
|
||||||
|
hm: '',
|
||||||
|
s: '',
|
||||||
|
mdq: '',
|
||||||
|
},
|
||||||
|
setIntervalTime: 0,
|
||||||
|
isShowLockScreen: false,
|
||||||
|
isShowLockScreenIntervalTime: 0,
|
||||||
|
lockScreenPassword: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 鼠标按下 pc
|
||||||
|
const onDownPc = (down: MouseEvent) => {
|
||||||
|
state.isFlags = true;
|
||||||
|
state.downClientY = down.clientY;
|
||||||
|
};
|
||||||
|
// 鼠标按下 app
|
||||||
|
const onDownApp = (down: TouchEvent) => {
|
||||||
|
state.isFlags = true;
|
||||||
|
state.downClientY = down.touches[0].clientY;
|
||||||
|
};
|
||||||
|
// 鼠标移动 pc
|
||||||
|
const onMovePc = (move: MouseEvent) => {
|
||||||
|
state.moveDifference = move.clientY - state.downClientY;
|
||||||
|
onMove();
|
||||||
|
};
|
||||||
|
// 鼠标移动 app
|
||||||
|
const onMoveApp = (move: TouchEvent) => {
|
||||||
|
state.moveDifference = move.touches[0].clientY - state.downClientY;
|
||||||
|
onMove();
|
||||||
|
};
|
||||||
|
// 鼠标移动事件
|
||||||
|
const onMove = () => {
|
||||||
|
if (state.isFlags) {
|
||||||
|
const el = <HTMLElement>state.querySelectorEl;
|
||||||
|
const opacitys = (state.transparency -= 1 / 200);
|
||||||
|
if (state.moveDifference >= 0) return false;
|
||||||
|
el.setAttribute('style', `top:${state.moveDifference}px;cursor:pointer;opacity:${opacitys};`);
|
||||||
|
if (state.moveDifference < -400) {
|
||||||
|
el.setAttribute('style', `top:${-el.clientHeight}px;cursor:pointer;transition:all 0.3s ease;`);
|
||||||
|
state.moveDifference = -el.clientHeight;
|
||||||
|
setTimeout(() => {
|
||||||
|
el && el.parentNode?.removeChild(el);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
if (state.moveDifference === -el.clientHeight) {
|
||||||
|
state.isShowLoockLogin = true;
|
||||||
|
layoutLockScreenInputRef.value.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 鼠标松开
|
||||||
|
const onEnd = () => {
|
||||||
|
state.isFlags = false;
|
||||||
|
state.transparency = 1;
|
||||||
|
if (state.moveDifference >= -400) {
|
||||||
|
(<HTMLElement>state.querySelectorEl).setAttribute('style', `top:0px;opacity:1;transition:all 0.3s ease;`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 获取要拖拽的初始元素
|
||||||
|
const initGetElement = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
state.querySelectorEl = layoutLockScreenDateRef.value;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 时间初始化
|
||||||
|
const initTime = () => {
|
||||||
|
state.time.hm = formatDate(new Date(), 'HH:MM');
|
||||||
|
state.time.s = formatDate(new Date(), 'SS');
|
||||||
|
state.time.mdq = formatDate(new Date(), 'mm月dd日,WWW');
|
||||||
|
};
|
||||||
|
// 时间初始化定时器
|
||||||
|
const initSetTime = () => {
|
||||||
|
initTime();
|
||||||
|
state.setIntervalTime = window.setInterval(() => {
|
||||||
|
initTime();
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
// 锁屏时间定时器
|
||||||
|
const initLockScreen = () => {
|
||||||
|
if (themeConfig.value.isLockScreen) {
|
||||||
|
state.isShowLockScreenIntervalTime = window.setInterval(() => {
|
||||||
|
if (themeConfig.value.lockScreenTime <= 1) {
|
||||||
|
state.isShowLockScreen = true;
|
||||||
|
setLocalThemeConfig();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
themeConfig.value.lockScreenTime--;
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
clearInterval(state.isShowLockScreenIntervalTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 存储布局配置
|
||||||
|
const setLocalThemeConfig = () => {
|
||||||
|
themeConfig.value.isDrawer = false;
|
||||||
|
setLocal('themeConfig', themeConfig.value);
|
||||||
|
};
|
||||||
|
// 密码输入点击事件
|
||||||
|
const onLockScreenSubmit = () => {
|
||||||
|
themeConfig.value.isLockScreen = false;
|
||||||
|
themeConfig.value.lockScreenTime = 30;
|
||||||
|
setLocalThemeConfig();
|
||||||
|
};
|
||||||
|
// 页面加载时
|
||||||
|
onMounted(() => {
|
||||||
|
initGetElement();
|
||||||
|
initSetTime();
|
||||||
|
initLockScreen();
|
||||||
|
});
|
||||||
|
// 页面卸载时
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.clearInterval(state.setIntervalTime);
|
||||||
|
window.clearInterval(state.isShowLockScreenIntervalTime);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.layout-lock-screen-fixed {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.layout-lock-screen-filter {
|
||||||
|
filter: blur(1px);
|
||||||
|
}
|
||||||
|
.layout-lock-screen-mask {
|
||||||
|
background: var(--el-color-white);
|
||||||
|
@extend .layout-lock-screen-fixed;
|
||||||
|
z-index: 9999990;
|
||||||
|
}
|
||||||
|
.layout-lock-screen-img {
|
||||||
|
@extend .layout-lock-screen-fixed;
|
||||||
|
background: url('@/assets/image/login-bg-main.svg') no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
z-index: 9999991;
|
||||||
|
}
|
||||||
|
.layout-lock-screen {
|
||||||
|
@extend .layout-lock-screen-fixed;
|
||||||
|
z-index: 9999992;
|
||||||
|
&-date {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--el-color-white);
|
||||||
|
z-index: 9999993;
|
||||||
|
user-select: none;
|
||||||
|
&-box {
|
||||||
|
position: absolute;
|
||||||
|
left: 30px;
|
||||||
|
bottom: 50px;
|
||||||
|
&-time {
|
||||||
|
font-size: 100px;
|
||||||
|
color: var(--el-color-white);
|
||||||
|
}
|
||||||
|
&-info {
|
||||||
|
font-size: 40px;
|
||||||
|
color: var(--el-color-white);
|
||||||
|
}
|
||||||
|
&-minutes {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-top {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
border-radius: 100%;
|
||||||
|
border: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: var(--el-color-white);
|
||||||
|
opacity: 0.8;
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
bottom: 50px;
|
||||||
|
text-align: center;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
i {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
&-text {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 150%;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-color-white);
|
||||||
|
left: 50%;
|
||||||
|
line-height: 1.2;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
width: 35px;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
box-shadow: 0 0 12px 0 rgba(255, 255, 255, 0.5);
|
||||||
|
color: var(--el-color-white);
|
||||||
|
opacity: 1;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
i {
|
||||||
|
transform: translateY(-40px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
.layout-lock-screen-date-top-text {
|
||||||
|
opacity: 1;
|
||||||
|
top: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-login {
|
||||||
|
position: relative;
|
||||||
|
z-index: 9999994;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--el-color-white);
|
||||||
|
&-box {
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
&-img {
|
||||||
|
width: 180px;
|
||||||
|
height: 180px;
|
||||||
|
margin: auto;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-name {
|
||||||
|
font-size: 26px;
|
||||||
|
margin: 15px 0 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&-icon {
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
bottom: 30px;
|
||||||
|
i {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-left: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.8;
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.el-input-group__append) {
|
||||||
|
background: var(--el-color-white);
|
||||||
|
padding: 0px 15px;
|
||||||
|
}
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
border-right-color: var(--el-border-color-extra-light);
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--el-border-color-extra-light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
<img src="@/assets/image/logo.svg" class="layout-logo-medium-img" />
|
<img src="@/assets/image/logo.svg" class="layout-logo-medium-img" />
|
||||||
<span>
|
<span>
|
||||||
{{ `${themeConfig.globalTitle}` }}
|
{{ `${themeConfig.globalTitle}` }}
|
||||||
<sub><span style="font-size: 10px;color:goldenrod">{{ ` ${config.version}` }}</span></sub>
|
<sub
|
||||||
|
><span style="font-size: 10px; color: goldenrod">{{ ` ${config.version}` }}</span></sub
|
||||||
|
>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
|
<div class="layout-logo-size" v-else @click="onThemeConfigChange">
|
||||||
@@ -41,14 +43,14 @@ const onThemeConfigChange = () => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
|
box-shadow: rgb(0 21 41 / 2%) 0px 1px 4px;
|
||||||
color: var(--color-primary);
|
color: var(--el-color-primary);
|
||||||
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(--el-color-primary-light-2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,10 +15,10 @@
|
|||||||
<script lang="ts" setup name="layoutClassic">
|
<script lang="ts" setup name="layoutClassic">
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import Aside from '@/views/layout/component/aside.vue';
|
import Aside from '@/layout/component/aside.vue';
|
||||||
import Header from '@/views/layout/component/header.vue';
|
import Header from '@/layout/component/header.vue';
|
||||||
import Main from '@/views/layout/component/main.vue';
|
import Main from '@/layout/component/main.vue';
|
||||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
|
||||||
|
|
||||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
</script>
|
</script>
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import Aside from '@/views/layout/component/aside.vue';
|
import Aside from '@/layout/component/aside.vue';
|
||||||
import Header from '@/views/layout/component/header.vue';
|
import Header from '@/layout/component/header.vue';
|
||||||
import Main from '@/views/layout/component/main.vue';
|
import Main from '@/layout/component/main.vue';
|
||||||
import ColumnsAside from '@/views/layout/component/columnsAside.vue';
|
import ColumnsAside from '@/layout/component/columnsAside.vue';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutColumns',
|
name: 'layoutColumns',
|
||||||
@@ -15,9 +15,9 @@
|
|||||||
<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 Aside from '@/views/layout/component/aside.vue';
|
import Aside from '@/layout/component/aside.vue';
|
||||||
import Header from '@/views/layout/component/header.vue';
|
import Header from '@/layout/component/header.vue';
|
||||||
import Main from '@/views/layout/component/main.vue';
|
import Main from '@/layout/component/main.vue';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutDefaults',
|
name: 'layoutDefaults',
|
||||||
@@ -32,7 +32,7 @@ export default {
|
|||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
() => {
|
() => {
|
||||||
proxy.$refs.layoutDefaultsScrollbarRef.wrap$.scrollTop = 0;
|
proxy.$refs.layoutScrollbarRef.wrapRef.scrollTop = 0;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@@ -7,8 +7,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Header from '@/views/layout/component/header.vue';
|
import Header from '@/layout/component/header.vue';
|
||||||
import Main from '@/views/layout/component/main.vue';
|
import Main from '@/layout/component/main.vue';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutTransverse',
|
name: 'layoutTransverse',
|
||||||
components: { Header, Main },
|
components: { Header, Main },
|
||||||
@@ -14,13 +14,12 @@ import pinia from '@/store/index';
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { useRoutesList } from '@/store/routesList';
|
import { useRoutesList } from '@/store/routesList';
|
||||||
import Breadcrumb from '@/views/layout/navBars/breadcrumb/breadcrumb.vue';
|
import Breadcrumb from '@/layout/navBars/breadcrumb/breadcrumb.vue';
|
||||||
import User from '@/views/layout/navBars/breadcrumb/user.vue';
|
import User from '@/layout/navBars/breadcrumb/user.vue';
|
||||||
import Logo from '@/views/layout/logo/index.vue';
|
import Logo from '@/layout/logo/index.vue';
|
||||||
import Horizontal from '@/views/layout/navMenu/horizontal.vue';
|
import Horizontal from '@/layout/navMenu/horizontal.vue';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
|
|
||||||
|
|
||||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
const { routesList } = storeToRefs(useRoutesList());
|
const { routesList } = storeToRefs(useRoutesList());
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -106,6 +105,6 @@ onUnmounted(() => {
|
|||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
background: var(--bg-topBar);
|
background: var(--bg-topBar);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-bottom: 1px solid #f1f2f3;
|
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,47 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-breadcrumb-seting">
|
<div class="layout-breadcrumb-seting">
|
||||||
<el-drawer title="布局设置" v-model="themeConfig.isDrawer" direction="rtl" destroy-on-close size="240px"
|
<el-drawer title="布局设置" v-model="themeConfig.isDrawer" direction="rtl" destroy-on-close size="240px" @close="onDrawerClose">
|
||||||
@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="themeConfig.terminalForeground" size="small"
|
<el-color-picker v-model="themeConfig.terminalForeground" size="small" @change="onColorPickerChange('terminalForeground')">
|
||||||
@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="themeConfig.terminalBackground" size="small"
|
<el-color-picker v-model="themeConfig.terminalBackground" size="small" @change="onColorPickerChange('terminalBackground')">
|
||||||
@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="themeConfig.terminalCursor" size="small"
|
<el-color-picker v-model="themeConfig.terminalCursor" size="small" @change="onColorPickerChange('terminalCursor')"> </el-color-picker>
|
||||||
@change="onColorPickerChange('terminalCursor')">
|
|
||||||
</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 v-model="themeConfig.terminalFontSize" controls-position="right" :min="12"
|
<el-input-number
|
||||||
:max="24" @change="setLocalThemeConfig" size="small" style="width: 90px">
|
v-model="themeConfig.terminalFontSize"
|
||||||
|
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="themeConfig.terminalFontWeight" size="small"
|
<el-select @change="setLocalThemeConfig" v-model="themeConfig.terminalFontWeight" size="small" style="width: 90px">
|
||||||
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>
|
||||||
@@ -52,8 +53,7 @@
|
|||||||
<div class="layout-breadcrumb-seting-bar-flex">
|
<div class="layout-breadcrumb-seting-bar-flex">
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
|
<div class="layout-breadcrumb-seting-bar-flex-label">主题</div>
|
||||||
<div class="layout-breadcrumb-seting-bar-flex-value">
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
<el-select @change="setLocalThemeConfig" v-model="themeConfig.editorTheme" size="small"
|
<el-select @change="setLocalThemeConfig" v-model="themeConfig.editorTheme" size="small" style="width: 130px">
|
||||||
style="width: 130px">
|
|
||||||
<el-option label="vs" value="vs"> </el-option>
|
<el-option label="vs" value="vs"> </el-option>
|
||||||
<el-option label="vs-dark" value="vs-dark"> </el-option>
|
<el-option label="vs-dark" value="vs-dark"> </el-option>
|
||||||
<el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
|
<el-option label="SolarizedLight" value="SolarizedLight"> </el-option>
|
||||||
@@ -61,41 +61,54 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 全局设置 -->
|
||||||
|
<el-divider content-position="left">全局设置</el-divider>
|
||||||
|
<div class="layout-breadcrumb-seting-bar-flex mt15">
|
||||||
|
<div class="layout-breadcrumb-seting-bar-flex-label">分页size</div>
|
||||||
|
<div class="layout-breadcrumb-seting-bar-flex-value">
|
||||||
|
<el-input-number
|
||||||
|
v-model="themeConfig.defaultListPageSize"
|
||||||
|
controls-position="right"
|
||||||
|
:min="10"
|
||||||
|
:max="50"
|
||||||
|
@change="setLocalThemeConfig"
|
||||||
|
size="small"
|
||||||
|
style="width: 90px"
|
||||||
|
>
|
||||||
|
</el-input-number>
|
||||||
|
</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="themeConfig.primary" size="small"
|
<el-color-picker v-model="themeConfig.primary" size="small" @change="onColorPickerChange('primary')"> </el-color-picker>
|
||||||
@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="themeConfig.success" size="small"
|
<el-color-picker v-model="themeConfig.success" size="small" @change="onColorPickerChange('success')"> </el-color-picker>
|
||||||
@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="themeConfig.info" size="small" @change="onColorPickerChange('info')">
|
<el-color-picker v-model="themeConfig.info" size="small" @change="onColorPickerChange('info')"> </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">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="themeConfig.warning" size="small"
|
<el-color-picker v-model="themeConfig.warning" size="small" @change="onColorPickerChange('warning')"> </el-color-picker>
|
||||||
@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="themeConfig.danger" size="small" @change="onColorPickerChange('danger')">
|
<el-color-picker v-model="themeConfig.danger" size="small" @change="onColorPickerChange('danger')"> </el-color-picker>
|
||||||
</el-color-picker>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -104,46 +117,37 @@
|
|||||||
<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="themeConfig.topBar" size="small"
|
<el-color-picker v-model="themeConfig.topBar" size="small" @change="onBgColorPickerChange('topBar')"> </el-color-picker>
|
||||||
@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="themeConfig.menuBar" size="small"
|
<el-color-picker v-model="themeConfig.menuBar" size="small" @change="onBgColorPickerChange('menuBar')"> </el-color-picker>
|
||||||
@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="themeConfig.columnsMenuBar" size="small"
|
<el-color-picker v-model="themeConfig.columnsMenuBar" size="small" @change="onBgColorPickerChange('columnsMenuBar')"> </el-color-picker>
|
||||||
@change="onBgColorPickerChange('columnsMenuBar')">
|
|
||||||
</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="themeConfig.topBarColor" size="small"
|
<el-color-picker v-model="themeConfig.topBarColor" size="small" @change="onBgColorPickerChange('topBarColor')"> </el-color-picker>
|
||||||
@change="onBgColorPickerChange('topBarColor')">
|
|
||||||
</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="themeConfig.menuBarColor" size="small"
|
<el-color-picker v-model="themeConfig.menuBarColor" size="small" @change="onBgColorPickerChange('menuBarColor')"> </el-color-picker>
|
||||||
@change="onBgColorPickerChange('menuBarColor')">
|
|
||||||
</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="themeConfig.columnsMenuBarColor" size="small"
|
<el-color-picker v-model="themeConfig.columnsMenuBarColor" size="small" @change="onBgColorPickerChange('columnsMenuBarColor')">
|
||||||
@change="onBgColorPickerChange('columnsMenuBarColor')">
|
|
||||||
</el-color-picker>
|
</el-color-picker>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,15 +166,13 @@
|
|||||||
<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="themeConfig.isColumnsMenuBarColorGradual"
|
<el-switch v-model="themeConfig.isColumnsMenuBarColorGradual" @change="onColumnsMenuBarGradualChange"></el-switch>
|
||||||
@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="themeConfig.isMenuBarColorHighlight"
|
<el-switch v-model="themeConfig.isMenuBarColorHighlight" @change="onMenuBarHighlightChange"></el-switch>
|
||||||
@change="onMenuBarHighlightChange"></el-switch>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -194,12 +196,10 @@
|
|||||||
<el-switch v-model="themeConfig.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"
|
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: themeConfig.layout !== 'classic' ? 0.5 : 1 }">
|
||||||
: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 v-model="themeConfig.isClassicSplitMenu" :disabled="themeConfig.layout !== 'classic'"
|
<el-switch v-model="themeConfig.isClassicSplitMenu" :disabled="themeConfig.layout !== 'classic'" @change="onClassicSplitMenuChange">
|
||||||
@change="onClassicSplitMenuChange">
|
|
||||||
</el-switch>
|
</el-switch>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -212,8 +212,15 @@
|
|||||||
<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 v-model="themeConfig.lockScreenTime" controls-position="right" :min="0" :max="9999"
|
<el-input-number
|
||||||
@change="setLocalThemeConfig" size="small" style="width: 90px">
|
v-model="themeConfig.lockScreenTime"
|
||||||
|
controls-position="right"
|
||||||
|
:min="0"
|
||||||
|
:max="9999"
|
||||||
|
@change="setLocalThemeConfig"
|
||||||
|
size="small"
|
||||||
|
style="width: 90px"
|
||||||
|
>
|
||||||
</el-input-number>
|
</el-input-number>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -226,12 +233,14 @@
|
|||||||
<el-switch v-model="themeConfig.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"
|
<div class="layout-breadcrumb-seting-bar-flex mt15" :style="{ opacity: themeConfig.layout === 'transverse' ? 0.5 : 1 }">
|
||||||
: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 v-model="themeConfig.isBreadcrumb" :disabled="themeConfig.layout === 'transverse'"
|
<el-switch
|
||||||
@change="onIsBreadcrumbChange"></el-switch>
|
v-model="themeConfig.isBreadcrumb"
|
||||||
|
:disabled="themeConfig.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">
|
||||||
@@ -288,8 +297,7 @@
|
|||||||
<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 v-model="themeConfig.tagsStyle" placeholder="请选择" size="small" style="width: 90px"
|
<el-select v-model="themeConfig.tagsStyle" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
|
||||||
@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>
|
||||||
@@ -299,8 +307,7 @@
|
|||||||
<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 v-model="themeConfig.animation" placeholder="请选择" size="small" style="width: 90px"
|
<el-select v-model="themeConfig.animation" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
|
||||||
@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>
|
||||||
@@ -310,8 +317,7 @@
|
|||||||
<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 v-model="themeConfig.columnsAsideStyle" placeholder="请选择" size="small"
|
<el-select v-model="themeConfig.columnsAsideStyle" placeholder="请选择" size="small" style="width: 90px" @change="setLocalThemeConfig">
|
||||||
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>
|
||||||
@@ -323,16 +329,14 @@
|
|||||||
<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"
|
<section class="el-container el-circular" :class="{ 'drawer-layout-active': themeConfig.layout === 'defaults' }">
|
||||||
: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"
|
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'defaults' }">
|
||||||
: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>
|
||||||
@@ -340,8 +344,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- classic 布局 -->
|
<!-- classic 布局 -->
|
||||||
<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
|
<div class="layout-drawer-content-item" @click="onSetLayout('classic')">
|
||||||
<section class="el-container is-vertical el-circular"
|
<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': themeConfig.layout === 'classic' }">
|
||||||
:class="{ 'drawer-layout-active': themeConfig.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>
|
||||||
@@ -350,8 +353,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<div class="layout-tips-warp"
|
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'classic' }">
|
||||||
: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>
|
||||||
@@ -359,8 +361,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- transverse 布局 -->
|
<!-- transverse 布局 -->
|
||||||
<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
|
<div class="layout-drawer-content-item" @click="onSetLayout('transverse')">
|
||||||
<section class="el-container is-vertical el-circular"
|
<section class="el-container is-vertical el-circular" :class="{ 'drawer-layout-active': themeConfig.layout === 'transverse' }">
|
||||||
:class="{ 'drawer-layout-active': themeConfig.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">
|
||||||
@@ -368,8 +369,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<div class="layout-tips-warp"
|
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'transverse' }">
|
||||||
: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>
|
||||||
@@ -377,8 +377,7 @@
|
|||||||
</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"
|
<section class="el-container el-circular" :class="{ 'drawer-layout-active': themeConfig.layout === 'columns' }">
|
||||||
: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">
|
||||||
@@ -386,21 +385,25 @@
|
|||||||
<main class="el-main"></main>
|
<main class="el-main"></main>
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<div class="layout-tips-warp"
|
<div class="layout-tips-warp" :class="{ 'layout-tips-warp-active': themeConfig.layout === 'columns' }">
|
||||||
: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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</div>
|
</div>
|
||||||
@@ -412,8 +415,8 @@ import { ElMessage } from 'element-plus';
|
|||||||
import ClipboardJS from 'clipboard';
|
import ClipboardJS from 'clipboard';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { getLightColor } from '@/common/utils/theme.ts';
|
import { getLightColor } from '@/common/utils/theme';
|
||||||
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage.ts';
|
import { setLocal, getLocal, removeLocal } from '@/common/utils/storage';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
|
|
||||||
const copyConfigBtnRef = ref();
|
const copyConfigBtnRef = ref();
|
||||||
@@ -428,7 +431,7 @@ const onColorPickerChange = (color: string) => {
|
|||||||
const setPropertyFun = (color: string, targetVal: any) => {
|
const setPropertyFun = (color: string, targetVal: any) => {
|
||||||
document.documentElement.style.setProperty(color, targetVal);
|
document.documentElement.style.setProperty(color, targetVal);
|
||||||
for (let i = 1; i <= 9; i++) {
|
for (let i = 1; i <= 9; i++) {
|
||||||
document.documentElement.style.setProperty(`${color}-light-${i}`, getLightColor(targetVal, i / 10));
|
document.documentElement.style.setProperty(`${color}-light-${i}`, getLightColor(targetVal, i / 10) as any);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 2、菜单 / 顶栏
|
// 2、菜单 / 顶栏
|
||||||
@@ -449,11 +452,7 @@ const onMenuBarGradualChange = () => {
|
|||||||
};
|
};
|
||||||
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
|
// 2、菜单 / 顶栏 --> 分栏菜单背景渐变
|
||||||
const onColumnsMenuBarGradualChange = () => {
|
const onColumnsMenuBarGradualChange = () => {
|
||||||
setGraduaFun(
|
setGraduaFun('.layout-container .layout-columns-aside', themeConfig.value.isColumnsMenuBarColorGradual, themeConfig.value.columnsMenuBar);
|
||||||
'.layout-container .layout-columns-aside',
|
|
||||||
themeConfig.value.isColumnsMenuBarColorGradual,
|
|
||||||
themeConfig.value.columnsMenuBar
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
// 2、菜单 / 顶栏 --> 背景渐变函数
|
// 2、菜单 / 顶栏 --> 背景渐变函数
|
||||||
const setGraduaFun = (el: string, bool: boolean, color: string) => {
|
const setGraduaFun = (el: string, bool: boolean, color: string) => {
|
||||||
@@ -522,17 +521,14 @@ const onSortableTagsViewChange = () => {
|
|||||||
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 (themeConfig.value.isGrayscale) themeConfig.value.isInvert = false;
|
if (themeConfig.value.isGrayscale) themeConfig.value.isInvert = false;
|
||||||
} else {
|
} else {
|
||||||
if (themeConfig.value.isInvert) themeConfig.value.isGrayscale = false;
|
if (themeConfig.value.isInvert) themeConfig.value.isGrayscale = false;
|
||||||
}
|
}
|
||||||
const cssAttr =
|
const cssAttr = attr === 'grayscale' ? `grayscale(${themeConfig.value.isGrayscale ? 1 : 0})` : `invert(${themeConfig.value.isInvert ? '80%' : '0%'})`;
|
||||||
attr === 'grayscale'
|
|
||||||
? `grayscale(${themeConfig.value.isGrayscale ? 1 : 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();
|
||||||
@@ -549,49 +545,37 @@ const onSetLayout = (layout: string) => {
|
|||||||
};
|
};
|
||||||
// 设置布局切换,重置主题样式
|
// 设置布局切换,重置主题样式
|
||||||
const initSetLayoutChange = () => {
|
const initSetLayoutChange = () => {
|
||||||
|
// themeConfig.value.menuBar = '#FFFFFF';
|
||||||
|
// themeConfig.value.menuBarColor = '#606266';
|
||||||
|
// themeConfig.value.topBar = '#ffffff';
|
||||||
|
// themeConfig.value.topBarColor = '#606266';
|
||||||
|
|
||||||
if (themeConfig.value.layout === 'classic') {
|
if (themeConfig.value.layout === 'classic') {
|
||||||
themeConfig.value.isShowLogo = true;
|
themeConfig.value.isShowLogo = true;
|
||||||
themeConfig.value.isBreadcrumb = true;
|
themeConfig.value.isBreadcrumb = true;
|
||||||
themeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
themeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
themeConfig.value.menuBar = '#FFFFFF';
|
|
||||||
themeConfig.value.menuBarColor = '#606266';
|
|
||||||
themeConfig.value.topBar = '#ffffff';
|
|
||||||
themeConfig.value.topBarColor = '#606266';
|
|
||||||
initLayoutChangeFun();
|
|
||||||
} else if (themeConfig.value.layout === 'transverse') {
|
} else if (themeConfig.value.layout === 'transverse') {
|
||||||
themeConfig.value.isShowLogo = true;
|
themeConfig.value.isShowLogo = true;
|
||||||
themeConfig.value.isBreadcrumb = false;
|
themeConfig.value.isBreadcrumb = false;
|
||||||
themeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
themeConfig.value.isTagsview = false;
|
themeConfig.value.isTagsview = true;
|
||||||
themeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
themeConfig.value.menuBarColor = '#FFFFFF';
|
|
||||||
themeConfig.value.topBar = '#545c64';
|
|
||||||
themeConfig.value.topBarColor = '#FFFFFF';
|
|
||||||
initLayoutChangeFun();
|
|
||||||
} else if (themeConfig.value.layout === 'columns') {
|
} else if (themeConfig.value.layout === 'columns') {
|
||||||
themeConfig.value.isShowLogo = true;
|
themeConfig.value.isShowLogo = true;
|
||||||
themeConfig.value.isBreadcrumb = true;
|
themeConfig.value.isBreadcrumb = true;
|
||||||
themeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
themeConfig.value.isTagsview = true;
|
themeConfig.value.isTagsview = true;
|
||||||
themeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
themeConfig.value.menuBar = '#FFFFFF';
|
|
||||||
themeConfig.value.menuBarColor = '#606266';
|
|
||||||
themeConfig.value.topBar = '#ffffff';
|
|
||||||
themeConfig.value.topBarColor = '#606266';
|
|
||||||
initLayoutChangeFun();
|
|
||||||
} else {
|
} else {
|
||||||
themeConfig.value.isShowLogo = false;
|
themeConfig.value.isShowLogo = false;
|
||||||
themeConfig.value.isBreadcrumb = true;
|
themeConfig.value.isBreadcrumb = true;
|
||||||
themeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
themeConfig.value.isTagsview = true;
|
themeConfig.value.isTagsview = true;
|
||||||
themeConfig.value.isClassicSplitMenu = false;
|
themeConfig.value.isClassicSplitMenu = false;
|
||||||
themeConfig.value.menuBar = '#545c64';
|
|
||||||
themeConfig.value.menuBarColor = '#eaeaea';
|
|
||||||
themeConfig.value.topBar = '#FFFFFF';
|
|
||||||
themeConfig.value.topBarColor = '#606266';
|
|
||||||
initLayoutChangeFun();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initLayoutChangeFun();
|
||||||
};
|
};
|
||||||
// 设置布局切换函数
|
// 设置布局切换函数
|
||||||
const initLayoutChangeFun = () => {
|
const initLayoutChangeFun = () => {
|
||||||
@@ -660,6 +644,7 @@ onMounted(() => {
|
|||||||
onMenuBarHighlightChange();
|
onMenuBarHighlightChange();
|
||||||
themeConfig.value.isCollapse = false;
|
themeConfig.value.isCollapse = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
// 刷新页面时,设置了值,直接取缓存中的值进行初始化
|
// 刷新页面时,设置了值,直接取缓存中的值进行初始化
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -691,7 +676,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
// // 语言国际化
|
// // 语言国际化
|
||||||
// if (getLocal('themeConfig')) proxy.$i18n.locale = getLocal('themeConfig').globalI18n;
|
// if (getLocal('themeConfig')) proxy.$i18n.locale = getLocal('themeConfig').globalI18n;
|
||||||
}, 1100);
|
}, 100);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -701,7 +686,7 @@ onUnmounted(() => {
|
|||||||
mittBus.off('layoutMobileResize');
|
mittBus.off('layoutMobileResize');
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({openDrawer})
|
defineExpose({ openDrawer });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@@ -767,7 +752,7 @@ defineExpose({openDrawer})
|
|||||||
|
|
||||||
.drawer-layout-active {
|
.drawer-layout-active {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-tips-warp,
|
.layout-tips-warp,
|
||||||
@@ -778,7 +763,7 @@ defineExpose({openDrawer})
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary-light-4);
|
border-color: var(--el-color-primary-light-4);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
|
|
||||||
@@ -788,7 +773,7 @@ defineExpose({openDrawer})
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary-light-4);
|
border-color: var(--el-color-primary-light-4);
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
|
|
||||||
.layout-tips-txt {
|
.layout-tips-txt {
|
||||||
@@ -799,7 +784,7 @@ defineExpose({openDrawer})
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: var(--color-primary-light-4);
|
color: var(--el-color-primary-light-4);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transform: rotate(30deg);
|
transform: rotate(30deg);
|
||||||
left: -1px;
|
left: -1px;
|
||||||
@@ -813,14 +798,14 @@ defineExpose({openDrawer})
|
|||||||
|
|
||||||
.layout-tips-warp-active {
|
.layout-tips-warp-active {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--el-color-primary);
|
||||||
|
|
||||||
.layout-tips-box {
|
.layout-tips-box {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--el-color-primary);
|
||||||
|
|
||||||
.layout-tips-txt {
|
.layout-tips-txt {
|
||||||
color: var(--color-primary) !important;
|
color: var(--el-color-primary) !important;
|
||||||
background-color: #e9eef3 !important;
|
background-color: #e9eef3 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -830,20 +815,20 @@ defineExpose({openDrawer})
|
|||||||
.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(--el-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(--el-color-primary);
|
||||||
|
|
||||||
.layout-tips-box {
|
.layout-tips-box {
|
||||||
transition: inherit;
|
transition: inherit;
|
||||||
border-color: var(--color-primary);
|
border-color: var(--el-color-primary);
|
||||||
|
|
||||||
.layout-tips-txt {
|
.layout-tips-txt {
|
||||||
transition: inherit;
|
transition: inherit;
|
||||||
color: var(--color-primary) !important;
|
color: var(--el-color-primary) !important;
|
||||||
background-color: #e9eef3 !important;
|
background-color: #e9eef3 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
|
<div class="layout-navbars-breadcrumb-user" :style="{ flex: layoutUserFlexNum }">
|
||||||
<el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
|
<div class="layout-navbars-breadcrumb-user-icon">
|
||||||
|
<el-switch
|
||||||
|
@change="switchDark()"
|
||||||
|
v-model="isDark"
|
||||||
|
active-action-icon="Moon"
|
||||||
|
inactive-action-icon="Sunny"
|
||||||
|
style="--el-switch-off-color: #c4c9c4; --el-switch-on-color: #2c2c2c"
|
||||||
|
class="dark-icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- <el-dropdown :show-timeout="70" :hide-timeout="50" trigger="click" @command="onComponentSizeChange">
|
||||||
<div class="layout-navbars-breadcrumb-user-icon">
|
<div class="layout-navbars-breadcrumb-user-icon">
|
||||||
<el-icon title="组件大小">
|
<el-icon title="组件大小">
|
||||||
<plus />
|
<plus />
|
||||||
@@ -13,7 +23,7 @@
|
|||||||
<el-dropdown-item command="small" :disabled="state.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 />
|
||||||
@@ -46,7 +56,7 @@
|
|||||||
<crop />
|
<crop />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
<el-dropdown :show-timeout="70" :hide-timeout="50" @command="onHandleCommandClick">
|
<el-dropdown trigger="click" :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="userInfo.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
<img :src="userInfo.photo" class="layout-navbars-breadcrumb-user-link-photo mr5" />
|
||||||
{{ userInfo.name || userInfo.username }}
|
{{ userInfo.name || userInfo.username }}
|
||||||
@@ -65,7 +75,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" name="layoutBreadcrumbUser">
|
<script setup lang="ts" name="layoutBreadcrumbUser">
|
||||||
import { ref, computed, reactive, onMounted } from 'vue';
|
import { ref, computed, reactive, onMounted, watch } 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';
|
||||||
@@ -73,11 +83,13 @@ import { resetRoute } from '@/router/index';
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { clearSession, setLocal, getLocal, removeLocal } from '@/common/utils/storage';
|
import { clearSession } from '@/common/utils/storage';
|
||||||
import UserNews from '@/views/layout/navBars/breadcrumb/userNews.vue';
|
import UserNews from '@/layout/navBars/breadcrumb/userNews.vue';
|
||||||
import SearchMenu from '@/views/layout/navBars/breadcrumb/search.vue';
|
import SearchMenu from '@/layout/navBars/breadcrumb/search.vue';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
import openApi from '@/common/openApi';
|
import openApi from '@/common/openApi';
|
||||||
|
import { saveThemeConfig, getThemeConfig } from '@/common/utils/storage';
|
||||||
|
import { useDark, usePreferredDark } from '@vueuse/core';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchRef = ref();
|
const searchRef = ref();
|
||||||
@@ -88,7 +100,8 @@ const state = reactive({
|
|||||||
disabledSize: '',
|
disabledSize: '',
|
||||||
});
|
});
|
||||||
const { userInfo } = storeToRefs(useUserInfo());
|
const { userInfo } = storeToRefs(useUserInfo());
|
||||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
const themeConfigStore = useThemeConfig();
|
||||||
|
const { themeConfig } = storeToRefs(themeConfigStore);
|
||||||
|
|
||||||
// 设置分割样式
|
// 设置分割样式
|
||||||
const layoutUserFlexNum = computed(() => {
|
const layoutUserFlexNum = computed(() => {
|
||||||
@@ -152,24 +165,42 @@ const onHandleCommandClick = (path: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isDark = useDark();
|
||||||
|
const preDark = usePreferredDark();
|
||||||
|
|
||||||
|
watch(preDark, (newValue) => {
|
||||||
|
isDark.value = newValue;
|
||||||
|
switchDark();
|
||||||
|
});
|
||||||
|
|
||||||
|
const switchDark = () => {
|
||||||
|
themeConfig.value.isDark = isDark.value;
|
||||||
|
if (isDark.value) {
|
||||||
|
themeConfig.value.editorTheme = 'vs-dark';
|
||||||
|
} else {
|
||||||
|
themeConfig.value.editorTheme = 'vs';
|
||||||
|
}
|
||||||
|
saveThemeConfig(themeConfig.value);
|
||||||
|
};
|
||||||
|
|
||||||
// // 菜单搜索点击
|
// // 菜单搜索点击
|
||||||
const onSearchClick = () => {
|
const onSearchClick = () => {
|
||||||
searchRef.value.openSearch();
|
searchRef.value.openSearch();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 组件大小改变
|
// 组件大小改变
|
||||||
const onComponentSizeChange = (size: string) => {
|
// const onComponentSizeChange = (size: string) => {
|
||||||
removeLocal('themeConfig');
|
// removeLocal('themeConfig');
|
||||||
themeConfig.value.globalComponentSize = size;
|
// themeConfig.value.globalComponentSize = size;
|
||||||
setLocal('themeConfig', themeConfig.value);
|
// saveThemeConfig(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 (getThemeConfig().globalComponentSize) {
|
||||||
case '':
|
case '':
|
||||||
state.disabledSize = '';
|
state.disabledSize = '';
|
||||||
break;
|
break;
|
||||||
@@ -187,8 +218,10 @@ const initComponentSize = () => {
|
|||||||
|
|
||||||
// 页面加载时
|
// 页面加载时
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (getLocal('themeConfig')) {
|
const themeConfig = getThemeConfig();
|
||||||
|
if (themeConfig) {
|
||||||
initComponentSize();
|
initComponentSize();
|
||||||
|
isDark.value = themeConfig.isDark;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -39,9 +39,7 @@ export default {
|
|||||||
state.newsList = [];
|
state.newsList = [];
|
||||||
};
|
};
|
||||||
// 前往通知中心点击
|
// 前往通知中心点击
|
||||||
const toMsgCenter = () => {
|
const toMsgCenter = () => {};
|
||||||
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
onAllReadClick,
|
onAllReadClick,
|
||||||
toMsgCenter,
|
toMsgCenter,
|
||||||
@@ -62,7 +60,7 @@ export default {
|
|||||||
height: 35px;
|
height: 35px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
.head-box-btn {
|
.head-box-btn {
|
||||||
color: var(--color-primary);
|
color: var(--el-color-primary);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
@@ -90,7 +88,7 @@ export default {
|
|||||||
}
|
}
|
||||||
.foot-box {
|
.foot-box {
|
||||||
height: 35px;
|
height: 35px;
|
||||||
color: var(--color-primary);
|
color: var(--el-color-primary);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import BreadcrumbIndex from '@/views/layout/navBars/breadcrumb/index.vue';
|
import BreadcrumbIndex from '@/layout/navBars/breadcrumb/index.vue';
|
||||||
import TagsView from '@/views/layout/navBars/tagsView/tagsView.vue';
|
import TagsView from '@/layout/navBars/tagsView/tagsView.vue';
|
||||||
export default {
|
export default {
|
||||||
name: 'layoutNavBars',
|
name: 'layoutNavBars',
|
||||||
components: { BreadcrumbIndex, TagsView },
|
components: { BreadcrumbIndex, TagsView },
|
||||||
@@ -2,62 +2,109 @@
|
|||||||
<div class="layout-navbars-tagsview" :class="{ 'layout-navbars-tagsview-shadow': themeConfig.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 v-for="(v, k) in state.tagsViewList" :key="k" class="layout-navbars-tagsview-ul-li" :data-name="v.name"
|
<li
|
||||||
:class="{ 'is-active': isActive(v) }" @contextmenu.prevent="onContextmenu(v, $event)"
|
v-for="(v, k) in tagsViews"
|
||||||
@click="onTagsClick(v, k)" :ref="
|
:key="k"
|
||||||
|
class="layout-navbars-tagsview-ul-li"
|
||||||
|
:data-name="v.name"
|
||||||
|
:class="{ 'is-active': isActive(v) }"
|
||||||
|
@contextmenu.prevent="onContextmenu(v, $event)"
|
||||||
|
@click="onTagsClick(v, k)"
|
||||||
|
:ref="
|
||||||
(el) => {
|
(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"
|
>
|
||||||
v-if="isActive(v)" />
|
<SvgIcon name="iconfont icon-tag-view-active" class="layout-navbars-tagsview-ul-li-iconfont font14" v-if="isActive(v)" />
|
||||||
<SvgIcon :name="v.meta.icon" class="layout-navbars-tagsview-ul-li-iconfont"
|
<SvgIcon :name="v.icon" class="layout-navbars-tagsview-ul-li-iconfont" v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
|
||||||
v-if="!isActive(v) && themeConfig.isTagsviewIcon" />
|
<span>{{ v.title }}</span>
|
||||||
<span>{{ v.meta.title }}</span>
|
|
||||||
<template v-if="isActive(v)">
|
<template v-if="isActive(v)">
|
||||||
<SvgIcon name="RefreshRight" class="font14 ml5 layout-navbars-tagsview-ul-li-refresh"
|
<SvgIcon
|
||||||
@click.stop="refreshCurrentTagsView($route.fullPath)" />
|
name="RefreshRight"
|
||||||
<SvgIcon name="Close" class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-active"
|
class="font14 ml5 layout-navbars-tagsview-ul-li-refresh"
|
||||||
v-if="!v.meta.isAffix"
|
@click.stop="refreshCurrentTagsView($route.fullPath)"
|
||||||
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)" />
|
/>
|
||||||
|
<SvgIcon
|
||||||
|
name="Close"
|
||||||
|
class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-active"
|
||||||
|
v-if="!v.isAffix"
|
||||||
|
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<SvgIcon name="Close" class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-three"
|
<SvgIcon
|
||||||
v-if="!v.meta.isAffix"
|
name="Close"
|
||||||
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)" />
|
class="font14 layout-navbars-tagsview-ul-li-icon layout-icon-three"
|
||||||
|
v-if="!v.isAffix"
|
||||||
|
@click.stop="closeCurrentTagsView(themeConfig.isShareTagsView ? v.path : v.path)"
|
||||||
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<Contextmenu :dropdown="state.dropdown" ref="contextmenuRef" @currentContextmenuClick="onCurrentContextmenuClick" />
|
<Contextmenu :items="state.contextmenu.items" :dropdown="state.contextmenu.dropdown" ref="contextmenuRef" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="layoutTagsView">
|
<script lang="ts" setup name="layoutTagsView">
|
||||||
import { reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance, watch } from 'vue';
|
import { reactive, onMounted, computed, ref, nextTick, onBeforeUpdate, onBeforeMount, onUnmounted, getCurrentInstance } 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 { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { getSession, setSession, removeSession } from '@/common/utils/storage';
|
|
||||||
import mittBus from '@/common/utils/mitt';
|
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, ContextmenuItem } from '@/components/contextmenu';
|
||||||
|
import { getTagViews, setTagViews, removeTagViews } from '@/common/utils/storage';
|
||||||
|
import { useTagsViews } from '@/store/tagsViews';
|
||||||
|
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as any;
|
const { proxy } = getCurrentInstance() as any;
|
||||||
const tagsRefs = ref([]) as any;
|
const tagsRefs = ref([]) as any;
|
||||||
const scrollbarRef = ref();
|
const scrollbarRef = ref();
|
||||||
const contextmenuRef = ref();
|
const contextmenuRef = ref();
|
||||||
const tagsUlRef = ref();
|
const tagsUlRef = ref();
|
||||||
|
|
||||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
|
const { tagsViews } = storeToRefs(useTagsViews());
|
||||||
|
|
||||||
|
const keepAliveNamesStores = useKeepALiveNames();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const contextmenuItems = [
|
||||||
|
new ContextmenuItem(0, '刷新').withIcon('RefreshRight').withOnClick((data: any) => {
|
||||||
|
// path为fullPath
|
||||||
|
let { path } = data;
|
||||||
|
let currentTag = tagsViews.value.find((v: any) => v.path === path);
|
||||||
|
refreshCurrentTagsView(path);
|
||||||
|
router.push({ path, query: currentTag?.query });
|
||||||
|
}),
|
||||||
|
|
||||||
|
new ContextmenuItem(1, '关闭').withIcon('Close').withOnClick((data: any) => closeCurrentTagsView(data.path)),
|
||||||
|
|
||||||
|
new ContextmenuItem(2, '关闭其他').withIcon('CircleClose').withOnClick((data: any) => {
|
||||||
|
let { path } = data;
|
||||||
|
let currentTag = tagsViews.value.find((v: any) => v.path === path);
|
||||||
|
router.push({ path, query: currentTag?.query });
|
||||||
|
closeOtherTagsView(path);
|
||||||
|
}),
|
||||||
|
|
||||||
|
new ContextmenuItem(3, '关闭所有').withIcon('FolderDelete').withOnClick((data: any) => closeAllTagsView(data.path)),
|
||||||
|
|
||||||
|
new ContextmenuItem(4, '当前页全屏').withIcon('full-screen').withOnClick((data: any) => openCurrenFullscreen(data.path)),
|
||||||
|
];
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
routePath: route.fullPath,
|
routePath: route.fullPath,
|
||||||
dropdown: { x: '', y: '' },
|
// dropdown: { x: '', y: '' },
|
||||||
tagsRefsIndex: 0,
|
tagsRefsIndex: 0,
|
||||||
tagsViewList: [] as any,
|
|
||||||
sortable: '' as any,
|
sortable: '' as any,
|
||||||
|
contextmenu: {
|
||||||
|
items: contextmenuItems,
|
||||||
|
dropdown: { x: '', y: '' },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 动态设置 tagsView 风格样式
|
// 动态设置 tagsView 风格样式
|
||||||
@@ -67,163 +114,173 @@ const setTagsStyle = computed(() => {
|
|||||||
|
|
||||||
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
// 存储 tagsViewList 到浏览器临时缓存中,页面刷新时,保留记录
|
||||||
const addBrowserSetSession = (tagsViewList: Array<object>) => {
|
const addBrowserSetSession = (tagsViewList: Array<object>) => {
|
||||||
setSession('tagsViewList', tagsViewList);
|
setTagViews(tagsViewList);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取 vuex 中的 tagsViewRoutes 列表
|
// 获取 tagsViewRoutes 列表
|
||||||
const getTagsViewRoutes = () => {
|
const getTagsViewRoutes = () => {
|
||||||
state.routePath = route.fullPath;
|
state.routePath = route.fullPath;
|
||||||
state.tagsViewList = [];
|
tagsViews.value = [];
|
||||||
if (!themeConfig.value.isCacheTagsView) removeSession('tagsViewList');
|
if (!themeConfig.value.isCacheTagsView) {
|
||||||
|
removeTagViews();
|
||||||
|
}
|
||||||
initTagsView();
|
initTagsView();
|
||||||
};
|
};
|
||||||
// vuex 中获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
// 获取路由信息:如果是设置了固定的(isAffix),进行初始化显示
|
||||||
const initTagsView = () => {
|
const initTagsView = () => {
|
||||||
if (getSession('tagsViewList') && themeConfig.value.isCacheTagsView) {
|
const tagViews = getTagViews();
|
||||||
state.tagsViewList = getSession('tagsViewList');
|
if (tagViews && themeConfig.value.isCacheTagsView) {
|
||||||
|
tagsViews.value = tagViews;
|
||||||
} else {
|
} else {
|
||||||
state.tagsViewList?.map((v: any) => {
|
tagsViews.value?.map((v: any) => {
|
||||||
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
if (v.isAffix && !v.isHide) {
|
||||||
|
tagsViews.value.push({ ...v });
|
||||||
|
keepAliveNamesStores.setCacheKeepAlive(v);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
addTagsView(route.fullPath);
|
addTagsView(route.fullPath);
|
||||||
}
|
}
|
||||||
// 初始化当前元素(li)的下标
|
// 初始化当前元素(li)的下标
|
||||||
getTagsRefsIndex(route.fullPath);
|
setTagsRefsIndex(route.fullPath);
|
||||||
// 添加初始化横向滚动条移动到对应位置
|
// 添加初始化横向滚动条移动到对应位置
|
||||||
tagsViewmoveToCurrentTag();
|
tagsViewmoveToCurrentTag();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1、添加 tagsView:未设置隐藏(isHide)也添加到在 tagsView 中
|
// 1、添加 tagsView:未设置隐藏(isHide)也添加到在 tagsView 中
|
||||||
// path为fullPath
|
// path为fullPath
|
||||||
const addTagsView = (path: string, to: any = null) => {
|
const addTagsView = (path: string, to: any = null, tagViewIndex: number = -1) => {
|
||||||
|
nextTick(async () => {
|
||||||
if (!to) {
|
if (!to) {
|
||||||
to = route;
|
to = route;
|
||||||
}
|
}
|
||||||
|
|
||||||
path = decodeURI(path);
|
for (let tv of tagsViews.value) {
|
||||||
for (let tv of state.tagsViewList) {
|
if (tv.path === path) {
|
||||||
if (tv.fullPath === path) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagView = { ...to }
|
const tagView = {
|
||||||
// 防止Converting circular structure to JSON错误
|
path: path,
|
||||||
tagView.matched = null;
|
name: to.name,
|
||||||
tagView.redirectedFrom = null;
|
query: to.query,
|
||||||
state.tagsViewList.push(tagView);
|
title: to.meta.title,
|
||||||
addBrowserSetSession(state.tagsViewList);
|
icon: to.meta.icon,
|
||||||
|
isAffix: to.meta.isAffix,
|
||||||
|
isKeepAlive: to.meta.isKeepAlive,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tagViewIndex != -1) {
|
||||||
|
tagsViews.value.splice(tagViewIndex + 1, 0, tagView);
|
||||||
|
} else {
|
||||||
|
tagsViews.value.push(tagView);
|
||||||
|
}
|
||||||
|
await keepAliveNamesStores.addCachedView(tagView);
|
||||||
|
addBrowserSetSession(tagsViews.value);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2、刷新当前 tagsView:
|
// 2、刷新当前 tagsView:
|
||||||
// path为fullPath
|
// path为fullPath
|
||||||
const refreshCurrentTagsView = (path: string) => {
|
const refreshCurrentTagsView = async (path: string) => {
|
||||||
|
const item = getTagsView(path);
|
||||||
|
await keepAliveNamesStores.delCachedView(item);
|
||||||
|
keepAliveNamesStores.addCachedView(item);
|
||||||
mittBus.emit('onTagsViewRefreshRouterView', path);
|
mittBus.emit('onTagsViewRefreshRouterView', path);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTagsView = (path: string) => {
|
||||||
|
return tagsViews.value.find((v: any) => v.path === path);
|
||||||
|
};
|
||||||
|
|
||||||
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
|
// 3、关闭当前 tagsView:如果是设置了固定的(isAffix),不可以关闭
|
||||||
// path为fullPath
|
// path为fullPath
|
||||||
const closeCurrentTagsView = (path: string) => {
|
const closeCurrentTagsView = (path: string) => {
|
||||||
state.tagsViewList.map((v: any, k: number, arr: any) => {
|
tagsViews.value.map((v: TagsView, k: number, arr: any) => {
|
||||||
if (!v.meta.isAffix) {
|
if (!v.isAffix) {
|
||||||
if (v.fullPath === path) {
|
if (v.path === path) {
|
||||||
state.tagsViewList.splice(k, 1);
|
keepAliveNamesStores.delCachedView(v);
|
||||||
|
tagsViews.value.splice(k, 1);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (state.routePath !== path) {
|
if (state.routePath !== path) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let next;
|
let next: TagsView;
|
||||||
// 最后一个且高亮时
|
// 最后一个且高亮时
|
||||||
if (state.tagsViewList.length === k) {
|
if (tagsViews.value.length === k) {
|
||||||
next = k !== arr.length ? arr[k] : arr[arr.length - 1]
|
next = k !== arr.length ? arr[k] : arr[arr.length - 1];
|
||||||
} else {
|
} else {
|
||||||
next = arr[k];
|
next = arr[k];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next.meta.isDynamic) {
|
if (next) {
|
||||||
router.push({ name: next.name, params: next.params });
|
|
||||||
} else {
|
|
||||||
router.push({ path: next.path, query: next.query });
|
router.push({ path: next.path, query: next.query });
|
||||||
|
} else {
|
||||||
|
router.push({ path: '/' });
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addBrowserSetSession(state.tagsViewList);
|
addBrowserSetSession(tagsViews.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
// 4、关闭其它 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||||
const closeOtherTagsView = (path: string) => {
|
const closeOtherTagsView = (path: string) => {
|
||||||
const oldTagViews = state.tagsViewList;
|
const oldTagViews = tagsViews.value;
|
||||||
state.tagsViewList = [];
|
tagsViews.value = [];
|
||||||
oldTagViews.map((v: any) => {
|
oldTagViews.map((v: TagsView) => {
|
||||||
if (v.meta.isAffix && !v.meta.isHide) state.tagsViewList.push({ ...v });
|
if (v.isAffix && !v.isHide) {
|
||||||
|
keepAliveNamesStores.delOthersCachedViews(v);
|
||||||
|
tagsViews.value.push({ ...v });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
addTagsView(path);
|
addTagsView(path);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 5、关闭全部 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
// 5、关闭全部 tagsView:如果是设置了固定的(isAffix),不进行关闭
|
||||||
const closeAllTagsView = (path: string) => {
|
const closeAllTagsView = (path: string) => {
|
||||||
const oldTagViews = state.tagsViewList;
|
keepAliveNamesStores.delAllCachedViews();
|
||||||
state.tagsViewList = [];
|
const oldTagViews = tagsViews.value;
|
||||||
|
tagsViews.value = [];
|
||||||
oldTagViews.map((v: any) => {
|
oldTagViews.map((v: any) => {
|
||||||
if (v.meta.isAffix && !v.meta.isHide) {
|
if (v.isAffix && !v.isHide) {
|
||||||
state.tagsViewList.push({ ...v });
|
tagsViews.value.push({ ...v });
|
||||||
if (state.tagsViewList.some((v: any) => v.path === path)) router.push({ path, query: route.query });
|
if (tagsViews.value.some((v: any) => v.path === path)) {
|
||||||
else router.push({ path: v.path, query: route.query });
|
router.push({ path, query: route.query });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
addBrowserSetSession(state.tagsViewList);
|
if (tagsViews.value) {
|
||||||
|
router.push({ path: '/' });
|
||||||
|
}
|
||||||
|
addBrowserSetSession(tagsViews.value);
|
||||||
};
|
};
|
||||||
// 6、开启当前页面全屏
|
// 6、开启当前页面全屏
|
||||||
const openCurrenFullscreen = (path: string) => {
|
const openCurrenFullscreen = (path: string) => {
|
||||||
const item = state.tagsViewList.find((v: any) => v.fullPath === path);
|
const item = tagsViews.value.find((v: any) => v.path === path);
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
router.push({ path, query: item.query });
|
router.push({ path, query: item?.query });
|
||||||
const element = document.querySelector('.layout-main');
|
const element = document.querySelector('.layout-main');
|
||||||
const screenfulls: any = screenfull;
|
const screenfulls: any = screenfull;
|
||||||
screenfulls.request(element);
|
screenfulls.request(element);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 当前项右键菜单点击
|
|
||||||
const onCurrentContextmenuClick = (data: any) => {
|
|
||||||
// path为fullPath
|
|
||||||
let { id, path } = data;
|
|
||||||
let currentTag = state.tagsViewList.find((v: any) => v.fullPath === path);
|
|
||||||
switch (id) {
|
|
||||||
case 0:
|
|
||||||
refreshCurrentTagsView(path);
|
|
||||||
router.push({ path, query: currentTag.query });
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
closeCurrentTagsView(path);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
router.push({ path, query: currentTag.query });
|
|
||||||
closeOtherTagsView(path);
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
closeAllTagsView(path);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
openCurrenFullscreen(path);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// 判断页面高亮
|
// 判断页面高亮
|
||||||
const isActive = (route: any) => {
|
const isActive = (tagView: TagsView) => {
|
||||||
return route.fullPath === state.routePath;
|
return tagView.path === state.routePath;
|
||||||
};
|
};
|
||||||
// 右键点击时:传 x,y 坐标值到子组件中(props)
|
// 右键点击时:传 x,y 坐标值到子组件中(props)
|
||||||
const onContextmenu = (v: any, e: any) => {
|
const onContextmenu = (v: any, e: any) => {
|
||||||
const { clientX, clientY } = e;
|
const { clientX, clientY } = e;
|
||||||
state.dropdown.x = clientX;
|
state.contextmenu.dropdown.x = clientX;
|
||||||
state.dropdown.y = clientY;
|
state.contextmenu.dropdown.y = clientY;
|
||||||
contextmenuRef.value.openContextmenu(v);
|
contextmenuRef.value.openContextmenu(v);
|
||||||
};
|
};
|
||||||
// 当前的 tagsView 项点击时
|
// 当前的 tagsView 项点击时
|
||||||
const onTagsClick = (v: any, k: number) => {
|
const onTagsClick = (v: any, k: number) => {
|
||||||
state.routePath = decodeURI(v.fullPath);
|
state.routePath = decodeURI(v.path);
|
||||||
state.tagsRefsIndex = k;
|
state.tagsRefsIndex = k;
|
||||||
router.push(v);
|
router.push(v);
|
||||||
};
|
};
|
||||||
@@ -288,9 +345,9 @@ const tagsViewmoveToCurrentTag = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
|
// 获取 tagsView 的下标:用于处理 tagsView 点击时的横向滚动
|
||||||
const getTagsRefsIndex = (path: string) => {
|
const setTagsRefsIndex = (path: string) => {
|
||||||
if (state.tagsViewList.length > 0) {
|
if (tagsViews.value.length > 0) {
|
||||||
state.tagsRefsIndex = state.tagsViewList.findIndex((item: any) => item.fullPath === path);
|
state.tagsRefsIndex = tagsViews.value.findIndex((item: any) => item.path === path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 设置 tagsView 可以进行拖拽
|
// 设置 tagsView 可以进行拖拽
|
||||||
@@ -305,7 +362,7 @@ const initSortable = () => {
|
|||||||
onEnd: () => {
|
onEnd: () => {
|
||||||
const sortEndList: any = [];
|
const sortEndList: any = [];
|
||||||
state.sortable.toArray().map((val: any) => {
|
state.sortable.toArray().map((val: any) => {
|
||||||
state.tagsViewList.map((v: any) => {
|
tagsViews.value.map((v: any) => {
|
||||||
if (v.name === val) sortEndList.push({ ...v });
|
if (v.name === val) sortEndList.push({ ...v });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -315,24 +372,8 @@ const initSortable = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监听路由的变化,动态赋值给 tagsView
|
|
||||||
// watch(
|
|
||||||
// pinia.state,
|
|
||||||
// (val) => {
|
|
||||||
// if (val.tagsViewRoutes.tagsViewRoutes.length === state.tagsViewRoutesList.length) return false;
|
|
||||||
// getTagsViewRoutes();
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// deep: true,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
// 页面加载前
|
// 页面加载前
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
// 监听非本页面调用 0 刷新当前,1 关闭当前,2 关闭其它,3 关闭全部 4 当前页全屏
|
|
||||||
mittBus.on('onCurrentContextmenuClick', (data: object) => {
|
|
||||||
onCurrentContextmenuClick(data);
|
|
||||||
});
|
|
||||||
// 监听布局配置界面开启/关闭拖拽
|
// 监听布局配置界面开启/关闭拖拽
|
||||||
mittBus.on('openOrCloseSortable', () => {
|
mittBus.on('openOrCloseSortable', () => {
|
||||||
initSortable();
|
initSortable();
|
||||||
@@ -340,8 +381,6 @@ onBeforeMount(() => {
|
|||||||
});
|
});
|
||||||
// 页面卸载时
|
// 页面卸载时
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
// 取消非本页面调用监听
|
|
||||||
mittBus.off('onCurrentContextmenuClick');
|
|
||||||
// 取消监听布局配置界面开启/关闭拖拽
|
// 取消监听布局配置界面开启/关闭拖拽
|
||||||
mittBus.off('openOrCloseSortable');
|
mittBus.off('openOrCloseSortable');
|
||||||
});
|
});
|
||||||
@@ -357,17 +396,18 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
// 路由更新时
|
// 路由更新时
|
||||||
onBeforeRouteUpdate((to) => {
|
onBeforeRouteUpdate((to) => {
|
||||||
state.routePath = decodeURI(to.fullPath);
|
const path = decodeURI(to.fullPath);
|
||||||
addTagsView(to.fullPath, to);
|
state.routePath = path;
|
||||||
getTagsRefsIndex(to.fullPath);
|
addTagsView(path, to, state.tagsRefsIndex);
|
||||||
|
setTagsRefsIndex(path);
|
||||||
tagsViewmoveToCurrentTag();
|
tagsViewmoveToCurrentTag();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.layout-navbars-tagsview {
|
.layout-navbars-tagsview {
|
||||||
background-color: var(--el-color-white);
|
background-color: var(--bg-main-color);
|
||||||
border-bottom: 1px solid var(--next-border-color-light);
|
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 4;
|
z-index: 4;
|
||||||
|
|
||||||
@@ -480,8 +520,14 @@ onBeforeRouteUpdate((to) => {
|
|||||||
-webkit-mask-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzAiIGhlaWdodD0iNzAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0ibm9uZSI+CgogPGc+CiAgPHRpdGxlPkxheWVyIDE8L3RpdGxlPgogIDxwYXRoIHRyYW5zZm9ybT0icm90YXRlKC0wLjEzMzUwNiA1MC4xMTkyIDUwKSIgaWQ9InN2Z18xIiBkPSJtMTAwLjExOTE5LDEwMGMtNTUuMjI4LDAgLTEwMCwtNDQuNzcyIC0xMDAsLTEwMGwwLDEwMGwxMDAsMHoiIG9wYWNpdHk9InVuZGVmaW5lZCIgc3Ryb2tlPSJudWxsIiBmaWxsPSIjRjhFQUU3Ii8+CiAgPHBhdGggZD0ibS0wLjYzNzY2LDcuMzEyMjhjMC4xMTkxOSwwIDAuMjE3MzcsMC4wNTc5NiAwLjQ3Njc2LDAuMTE5MTljMC4yMzIsMC4wNTQ3NyAwLjI3MzI5LDAuMDM0OTEgMC4zNTc1NywwLjExOTE5YzAuMDg0MjgsMC4wODQyOCAwLjM1NzU3LDAgMC40NzY3NiwwbDAuMTE5MTksMGwwLjIzODM4LDAiIGlkPSJzdmdfMiIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHBhdGggZD0ibTI4LjkyMTM0LDY5LjA1MjQ0YzAsMC4xMTkxOSAwLDAuMjM4MzggMCwwLjM1NzU3bDAsMC4xMTkxOWwwLDAuMTE5MTkiIGlkPSJzdmdfMyIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z180IiBoZWlnaHQ9IjAiIHdpZHRoPSIxLjMxMTA4IiB5PSI2LjgzNTUyIiB4PSItMC4wNDE3MSIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z181IiBoZWlnaHQ9IjEuNzg3ODQiIHdpZHRoPSIwLjExOTE5IiB5PSI2OC40NTY1IiB4PSIyOC45MjEzNCIgc3Ryb2tlPSJudWxsIiBmaWxsPSJub25lIi8+CiAgPHJlY3QgaWQ9InN2Z182IiBoZWlnaHQ9IjQuODg2NzciIHdpZHRoPSIxOS4wNzAzMiIgeT0iNTEuMjkzMjEiIHg9IjM2LjY2ODY2IiBzdHJva2U9Im51bGwiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+'),
|
-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;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>");
|
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-size:
|
||||||
-webkit-mask-position: right bottom, left bottom, center top;
|
18px 30px,
|
||||||
|
20px 30px,
|
||||||
|
calc(100% - 30px) calc(100% + 17px);
|
||||||
|
-webkit-mask-position:
|
||||||
|
right bottom,
|
||||||
|
left bottom,
|
||||||
|
center top;
|
||||||
-webkit-mask-repeat: no-repeat;
|
-webkit-mask-repeat: no-repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,14 +548,14 @@ onBeforeRouteUpdate((to) => {
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@extend .tgs-style-three-svg;
|
@extend .tgs-style-three-svg;
|
||||||
background: var(--el-color-primary-light-9);
|
background: var(--tagsview3-active-background-color);
|
||||||
color: unset;
|
color: unset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-active {
|
.is-active {
|
||||||
@extend .tgs-style-three-svg;
|
@extend .tgs-style-three-svg;
|
||||||
background: var(--el-color-primary-light-9) !important;
|
background: var(--tagsview3-active-background-color) !important;
|
||||||
color: var(--el-color-primary) !important;
|
color: var(--el-color-primary) !important;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,23 @@
|
|||||||
<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="state.defaultActive" background-color="transparent" mode="horizontal"
|
<el-menu router :default-active="state.defaultActive" background-color="transparent" mode="horizontal" @select="onHorizontalSelect">
|
||||||
@select="onHorizontalSelect">
|
|
||||||
<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>
|
||||||
<SvgIcon :name="val.meta.icon"/>
|
<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-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 #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
|
<template #title v-if="!val.meta.link || (val.meta.link && val.meta.linkType == 1)">
|
||||||
<SvgIcon :name="val.meta.icon"/>
|
<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">
|
||||||
<SvgIcon :name="val.meta.icon"/>
|
<SvgIcon :name="val.meta.icon" />
|
||||||
{{ val.meta.title }}
|
{{ val.meta.title }}
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
@@ -32,7 +31,7 @@
|
|||||||
<script lang="ts" setup name="navMenuHorizontal">
|
<script lang="ts" setup name="navMenuHorizontal">
|
||||||
import { reactive, computed, 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 SubItem from '@/views/layout/navMenu/subItem.vue';
|
import SubItem from '@/layout/navMenu/subItem.vue';
|
||||||
import { useRoutesList } from '@/store/routesList';
|
import { useRoutesList } from '@/store/routesList';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
@@ -1,21 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-menu router :default-active="state.defaultActive" background-color="transparent" :collapse="setIsCollapse"
|
<el-menu
|
||||||
:unique-opened="themeConfig.isUniqueOpened" :collapse-transition="false">
|
router
|
||||||
|
:default-active="state.defaultActive"
|
||||||
|
background-color="transparent"
|
||||||
|
:collapse="setIsCollapse"
|
||||||
|
:unique-opened="themeConfig.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>
|
||||||
<SvgIcon :name="val.meta.icon"/>
|
<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-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.linkType == 1)">
|
<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>
|
||||||
@@ -26,7 +33,7 @@ import { reactive, computed } from 'vue';
|
|||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
import { useRoute, onBeforeRouteUpdate } from 'vue-router';
|
||||||
import SubItem from '@/views/layout/navMenu/subItem.vue';
|
import SubItem from '@/layout/navMenu/subItem.vue';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
|
|
||||||
// 定义父组件传过来的值
|
// 定义父组件传过来的值
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<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="state.keepAliveNameList">
|
<keep-alive :include="getKeepAliveNames">
|
||||||
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" />
|
<component :is="Component" :key="state.refreshRouterViewKey" class="w100" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -11,38 +11,62 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup name="layoutParentView">
|
<script lang="ts" setup name="layoutParentView">
|
||||||
import { computed, reactive, onBeforeMount, onUnmounted, nextTick } from 'vue';
|
import { computed, watch, reactive, onBeforeMount, onMounted, onUnmounted, nextTick } from 'vue';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
import { useKeepALiveNames } from '@/store/keepAliveNames';
|
||||||
import mittBus from '@/common/utils/mitt';
|
import mittBus from '@/common/utils/mitt';
|
||||||
|
import { getTagViews } from '@/common/utils/storage';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const { themeConfig } = storeToRefs(useThemeConfig());
|
const { themeConfig } = storeToRefs(useThemeConfig());
|
||||||
const { keepAliveNames } = storeToRefs(useKeepALiveNames());
|
const { keepAliveNames, cachedViews } = storeToRefs(useKeepALiveNames());
|
||||||
|
|
||||||
const state: any = reactive({
|
const state: any = reactive({
|
||||||
refreshRouterViewKey: null,
|
refreshRouterViewKey: null,
|
||||||
keepAliveNameList: [],
|
keepAliveNameList: [],
|
||||||
keepAliveNameNewList: [],
|
|
||||||
});
|
});
|
||||||
// 监听路由的变化,动态赋值给refreshRouterViewKey
|
|
||||||
// onBeforeRouteUpdate((to: any) => {
|
// 获取组件缓存列表(name值)
|
||||||
// state.refreshRouterViewKey = decodeURI(to.fullPath);
|
const getKeepAliveNames = computed(() => {
|
||||||
// });
|
return themeConfig.value.isTagsview ? cachedViews.value : state.keepAliveNameList;
|
||||||
|
});
|
||||||
|
|
||||||
// 页面加载前,处理缓存,页面刷新时路由缓存处理
|
// 页面加载前,处理缓存,页面刷新时路由缓存处理
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
state.keepAliveNameList = keepAliveNames.value;
|
state.keepAliveNameList = keepAliveNames.value;
|
||||||
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 = keepAliveNames.value.filter((name: string) => route.name !== name);
|
state.keepAliveNameList = keepAliveNames.value.filter((name: string) => route.name !== name);
|
||||||
state.refreshRouterViewKey = route.path;
|
state.refreshRouterViewKey = '';
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
state.refreshRouterViewKey = null;
|
state.refreshRouterViewKey = path;
|
||||||
state.keepAliveNameList = keepAliveNames.value;
|
state.keepAliveNameList = keepAliveNames.value;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// 页面加载时
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (themeConfig.value.isCacheTagsView) {
|
||||||
|
let tagsViewArr: any = getTagViews() || [];
|
||||||
|
cachedViews.value = tagsViewArr.filter((item: any) => item?.isKeepAlive).map((item: any) => item.name as string);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// 监听路由变化,防止 tagsView 多标签时,切换动画消失
|
||||||
|
watch(
|
||||||
|
() => route.fullPath,
|
||||||
|
() => {
|
||||||
|
state.refreshRouterViewKey = decodeURI(route.fullPath);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
// 设置主界面切换动画
|
// 设置主界面切换动画
|
||||||
const setTransitionName = computed(() => {
|
const setTransitionName = computed(() => {
|
||||||
return themeConfig.value.animation;
|
return themeConfig.value.animation;
|
||||||
@@ -9,9 +9,12 @@ 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 'element-plus/theme-chalk/dark/css-vars.css';
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
import 'splitpanes/dist/splitpanes.css';
|
||||||
|
|
||||||
import '@/theme/index.scss';
|
import '@/theme/index.scss';
|
||||||
import '@/assets/font/font.css';
|
import '@/assets/font/font.css';
|
||||||
import '@/assets/iconfont/iconfont.js';
|
import '@/assets/iconfont/iconfont.js';
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
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 { getSession, clearSession } from '@/common/utils/storage';
|
import { clearSession, getToken } from '@/common/utils/storage';
|
||||||
import { templateResolve } from '@/common/utils/string';
|
import { templateResolve } from '@/common/utils/string';
|
||||||
import { NextLoading } from '@/common/utils/loading';
|
import { NextLoading } from '@/common/utils/loading';
|
||||||
import { dynamicRoutes, staticRoutes, pathMatch } from './route';
|
import { dynamicRoutes, staticRoutes, pathMatch } from './route';
|
||||||
import openApi from '@/common/openApi';
|
import openApi from '@/common/openApi';
|
||||||
import sockets from '@/common/sockets';
|
import syssocket from '@/common/syssocket';
|
||||||
import pinia from '@/store/index';
|
import pinia from '@/store/index';
|
||||||
import { useThemeConfig } from '@/store/themeConfig';
|
import { useThemeConfig } from '@/store/themeConfig';
|
||||||
import { useUserInfo } from '@/store/userInfo';
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
@@ -18,7 +18,7 @@ import { useKeepALiveNames } from '@/store/keepAliveNames';
|
|||||||
* @method import.meta.glob
|
* @method import.meta.glob
|
||||||
* @link 参考:https://cn.vitejs.dev/guide/features.html#json
|
* @link 参考:https://cn.vitejs.dev/guide/features.html#json
|
||||||
*/
|
*/
|
||||||
const viewsModules: any = import.meta.glob(['../views/**/*.{vue,tsx}', '!../views/layout/**/*.{vue,tsx}']);
|
const viewsModules: any = import.meta.glob(['../views/**/*.{vue,tsx}']);
|
||||||
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...viewsModules });
|
const dynamicViewsModules: Record<string, Function> = Object.assign({}, { ...viewsModules });
|
||||||
|
|
||||||
// 添加静态路由
|
// 添加静态路由
|
||||||
@@ -29,8 +29,7 @@ const router = createRouter({
|
|||||||
|
|
||||||
// 前端控制路由:初始化方法,防止刷新时丢失
|
// 前端控制路由:初始化方法,防止刷新时丢失
|
||||||
export function initAllFun() {
|
export function initAllFun() {
|
||||||
NextLoading.start(); // 界面 loading 动画开始执行
|
const token = getToken(); // 获取浏览器缓存 token 值
|
||||||
const token = getSession('token'); // 获取浏览器缓存 token 值
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// 无 token 停止执行下一步
|
// 无 token 停止执行下一步
|
||||||
return false;
|
return false;
|
||||||
@@ -38,18 +37,15 @@ export function initAllFun() {
|
|||||||
useUserInfo().setUserInfo({});
|
useUserInfo().setUserInfo({});
|
||||||
router.addRoute(pathMatch); // 添加404界面
|
router.addRoute(pathMatch); // 添加404界面
|
||||||
resetRoute(); // 删除/重置路由
|
resetRoute(); // 删除/重置路由
|
||||||
// 添加动态路由
|
router.addRoute(dynamicRoutes[0]);
|
||||||
setFilterRouteEnd().forEach((route: any) => {
|
|
||||||
router.addRoute(route as unknown as RouteRecordRaw);
|
|
||||||
});
|
|
||||||
// 过滤权限菜单
|
// 过滤权限菜单
|
||||||
useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus));
|
useRoutesList().setRoutesList(dynamicRoutes[0].children);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 后端控制路由:执行路由数据初始化
|
// 后端控制路由:执行路由数据初始化
|
||||||
export async function initBackEndControlRoutesFun() {
|
export async function initBackEndControlRoutesFun() {
|
||||||
NextLoading.start(); // 界面 loading 动画开始执行
|
const token = getToken(); // 获取浏览器缓存 token 值
|
||||||
const token = getSession('token'); // 获取浏览器缓存 token 值
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// 无 token 停止执行下一步
|
// 无 token 停止执行下一步
|
||||||
return false;
|
return false;
|
||||||
@@ -57,14 +53,25 @@ export async function initBackEndControlRoutesFun() {
|
|||||||
useUserInfo().setUserInfo({});
|
useUserInfo().setUserInfo({});
|
||||||
// 获取路由
|
// 获取路由
|
||||||
let menuRoute = await getBackEndControlRoutes();
|
let menuRoute = await getBackEndControlRoutes();
|
||||||
dynamicRoutes[0].children = backEndRouterConverter(menuRoute); // 处理路由(component)
|
|
||||||
|
const cacheList: Array<string> = [];
|
||||||
|
// 处理路由(component)
|
||||||
|
dynamicRoutes[0].children = backEndRouterConverter(menuRoute, (router: any) => {
|
||||||
|
// 可能为false时不存在isKeepAlive属性
|
||||||
|
if (!router.meta.isKeepAlive) {
|
||||||
|
router.meta.isKeepAlive = false;
|
||||||
|
}
|
||||||
|
if (router.meta.isKeepAlive) {
|
||||||
|
cacheList.push(router.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
useKeepALiveNames().setCacheKeepAlive(cacheList);
|
||||||
|
|
||||||
// 添加404界面
|
// 添加404界面
|
||||||
router.addRoute(pathMatch);
|
router.addRoute(pathMatch);
|
||||||
resetRoute(); // 删除/重置路由
|
resetRoute(); // 删除/重置路由
|
||||||
// 添加动态路由
|
router.addRoute(dynamicRoutes[0] as unknown as RouteRecordRaw);
|
||||||
formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)).forEach((route: any) => {
|
|
||||||
router.addRoute(route as unknown as RouteRecordRaw);
|
|
||||||
});
|
|
||||||
useRoutesList().setRoutesList(dynamicRoutes[0].children);
|
useRoutesList().setRoutesList(dynamicRoutes[0].children);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +88,10 @@ export async function getBackEndControlRoutes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RouterConvCallbackFunc = (router: any) => void;
|
||||||
|
|
||||||
// 后端控制路由,后端返回路由 转换为vue route
|
// 后端控制路由,后端返回路由 转换为vue route
|
||||||
export function backEndRouterConverter(routes: any, parentPath: string = '/') {
|
export function backEndRouterConverter(routes: any, callbackFunc: RouterConvCallbackFunc = null as any, parentPath: string = '/') {
|
||||||
if (!routes) return;
|
if (!routes) return;
|
||||||
return routes.map((item: any) => {
|
return routes.map((item: any) => {
|
||||||
if (!item.meta) {
|
if (!item.meta) {
|
||||||
@@ -117,7 +126,9 @@ export function backEndRouterConverter(routes: any, parentPath: string = '/') {
|
|||||||
item.redirect = item.meta.redirect;
|
item.redirect = item.meta.redirect;
|
||||||
delete item.meta['redirect'];
|
delete item.meta['redirect'];
|
||||||
}
|
}
|
||||||
item.children && backEndRouterConverter(item.children, item.path);
|
// 存在回调,则执行回调
|
||||||
|
callbackFunc && callbackFunc(item);
|
||||||
|
item.children && backEndRouterConverter(item.children, callbackFunc, item.path);
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -143,86 +154,6 @@ export function dynamicImport(dynamicViewsModules: Record<string, Function>, com
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 多级嵌套数组处理成一维数组
|
|
||||||
export function formatFlatteningRoutes(arr: any) {
|
|
||||||
if (arr.length <= 0) return false;
|
|
||||||
for (let i = 0; i < arr.length; i++) {
|
|
||||||
if (arr[i].children) {
|
|
||||||
arr = arr.slice(0, i + 1).concat(arr[i].children, arr.slice(i + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 多级嵌套数组处理后的一维数组,再处理成 `定义动态路由` 的格式
|
|
||||||
// 只保留二级:也就是二级以上全部处理成只有二级,keep-alive 支持二级缓存
|
|
||||||
// isKeepAlive 处理 `name` 值,进行缓存。顶级关闭,全部不缓存
|
|
||||||
export function formatTwoStageRoutes(arr: any) {
|
|
||||||
if (arr.length <= 0) return false;
|
|
||||||
const newArr: any = [];
|
|
||||||
const cacheList: Array<string> = [];
|
|
||||||
arr.forEach((v: any) => {
|
|
||||||
if (v.path === '/') {
|
|
||||||
newArr.push({ component: v.component, name: v.name, path: v.path, redirect: v.redirect, meta: v.meta, children: [] });
|
|
||||||
} else {
|
|
||||||
newArr[0].children.push({ ...v });
|
|
||||||
if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive) {
|
|
||||||
cacheList.push(v.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
useKeepALiveNames().setCacheKeepAlive(cacheList);
|
|
||||||
return newArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断路由code 是否包含当前登录用户menus字段中,menus为字符串code数组
|
|
||||||
export function hasAnth(menus: any, route: any) {
|
|
||||||
if (route.meta && route.meta.code) {
|
|
||||||
return menus.includes(route.meta.code);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 递归过滤有权限的路由
|
|
||||||
export function setFilterMenuFun(routes: any, menus: any) {
|
|
||||||
const menu: any = [];
|
|
||||||
routes.forEach((route: any) => {
|
|
||||||
const item = { ...route };
|
|
||||||
if (hasAnth(menus, item)) {
|
|
||||||
if (item.children) {
|
|
||||||
item.children = setFilterMenuFun(item.children, menus);
|
|
||||||
}
|
|
||||||
menu.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前用户的权限去比对路由表,用于动态路由的添加
|
|
||||||
export function setFilterRoute(chil: any) {
|
|
||||||
let filterRoute: any = [];
|
|
||||||
chil.forEach((route: any) => {
|
|
||||||
// 如果路由需要拥有指定code才可访问,则校验该用户菜单是否存在该code
|
|
||||||
if (route.meta.code) {
|
|
||||||
useUserInfo().userInfo.menus.forEach((m: any) => {
|
|
||||||
if (route.meta.code == m) {
|
|
||||||
filterRoute.push({ ...route });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
filterRoute.push({ ...route });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return filterRoute;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 比对后的路由表,进行重新赋值
|
|
||||||
export function setFilterRouteEnd() {
|
|
||||||
let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes));
|
|
||||||
filterRouteEnd[0].children = setFilterRoute(filterRouteEnd[0].children);
|
|
||||||
return filterRouteEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除/重置路由
|
// 删除/重置路由
|
||||||
export function resetRoute() {
|
export function resetRoute() {
|
||||||
useRoutesList().routesList.forEach((route: any) => {
|
useRoutesList().routesList.forEach((route: any) => {
|
||||||
@@ -232,6 +163,8 @@ export function resetRoute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function initRouter() {
|
export async function initRouter() {
|
||||||
|
NextLoading.start(); // 界面 loading 动画开始执行
|
||||||
|
try {
|
||||||
// 初始化方法执行
|
// 初始化方法执行
|
||||||
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
|
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
|
||||||
if (!isRequestRoutes) {
|
if (!isRequestRoutes) {
|
||||||
@@ -241,9 +174,11 @@ export async function initRouter() {
|
|||||||
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
// 后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
|
||||||
await initBackEndControlRoutesFun();
|
await initBackEndControlRoutesFun();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
NextLoading.done();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let SysWs: any;
|
|
||||||
let loadRouter = false;
|
let loadRouter = false;
|
||||||
|
|
||||||
// 路由加载前
|
// 路由加载前
|
||||||
@@ -252,11 +187,11 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (to.meta.title) NProgress.start();
|
if (to.meta.title) NProgress.start();
|
||||||
|
|
||||||
// 如果有标题参数,则再原标题后加上参数来区别
|
// 如果有标题参数,则再原标题后加上参数来区别
|
||||||
if (to.meta.titleRename) {
|
if (to.meta.titleRename && to.meta.title) {
|
||||||
to.meta.title = templateResolve(to.meta.title as string, to.query);
|
to.meta.title = templateResolve(to.meta.title as string, to.query);
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = getSession('token');
|
const token = getToken();
|
||||||
if ((to.path === '/login' || to.path == '/oauth2/callback') && !token) {
|
if ((to.path === '/login' || to.path == '/oauth2/callback') && !token) {
|
||||||
next();
|
next();
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
@@ -268,10 +203,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
resetRoute();
|
resetRoute();
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
|
|
||||||
if (SysWs) {
|
syssocket.destory();
|
||||||
SysWs.close();
|
|
||||||
SysWs = null;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (token && to.path === '/login') {
|
if (token && to.path === '/login') {
|
||||||
@@ -281,9 +213,10 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 终端不需要连接系统websocket消息
|
// 终端不需要连接系统websocket消息
|
||||||
if (!SysWs && to.path != '/machine/terminal') {
|
if (to.path != '/machine/terminal') {
|
||||||
SysWs = sockets.sysMsgSocket();
|
syssocket.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 不存在路由(避免刷新页面找不到路由)并且未加载过(避免token过期,导致获取权限接口报权限不足,无限获取),则重新初始化路由
|
// 不存在路由(避免刷新页面找不到路由)并且未加载过(避免token过期,导致获取权限接口报权限不足,无限获取),则重新初始化路由
|
||||||
if (useRoutesList().routesList.length == 0 && !loadRouter) {
|
if (useRoutesList().routesList.length == 0 && !loadRouter) {
|
||||||
await initRouter();
|
await initRouter();
|
||||||
@@ -297,7 +230,6 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
// 路由加载后
|
// 路由加载后
|
||||||
router.afterEach(() => {
|
router.afterEach(() => {
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
NextLoading.done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 导出路由
|
// 导出路由
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import Layout from '@/views/layout/index.vue';
|
import Layout from '@/layout/index.vue';
|
||||||
|
|
||||||
// 定义动态路由
|
// 定义动态路由
|
||||||
export const dynamicRoutes = [
|
export const dynamicRoutes = [
|
||||||
|
|||||||
@@ -18,14 +18,16 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
|
|||||||
this.keepAliveNames = data;
|
this.keepAliveNames = data;
|
||||||
},
|
},
|
||||||
async addCachedView(view: any) {
|
async addCachedView(view: any) {
|
||||||
if (view.meta.isKeepAlive) this.cachedViews?.push(view.name);
|
if (view.isKeepAlive) {
|
||||||
|
this.cachedViews?.push(view.name);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async delCachedView(view: any) {
|
async delCachedView(view: any) {
|
||||||
const index = this.cachedViews.indexOf(view.name);
|
const index = this.cachedViews.indexOf(view.name);
|
||||||
index > -1 && this.cachedViews.splice(index, 1);
|
index > -1 && this.cachedViews.splice(index, 1);
|
||||||
},
|
},
|
||||||
async delOthersCachedViews(view: any) {
|
async delOthersCachedViews(view: any) {
|
||||||
if (view.meta.isKeepAlive) this.cachedViews = [view.name];
|
if (view.isKeepAlive) this.cachedViews = [view.name];
|
||||||
else this.cachedViews = [];
|
else this.cachedViews = [];
|
||||||
},
|
},
|
||||||
async delAllCachedViews() {
|
async delAllCachedViews() {
|
||||||
|
|||||||
25
mayfly_go_web/src/store/tagsViews.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { getNowUrl } from '@/common/utils/url';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tags view
|
||||||
|
*/
|
||||||
|
export const useTagsViews = defineStore('tagsViews', {
|
||||||
|
state: (): TagsViewsState => ({
|
||||||
|
tagsViews: [],
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
setTagsViews(data: Array<TagsView>) {
|
||||||
|
this.tagsViews = data;
|
||||||
|
},
|
||||||
|
// 设置当前页面的tags view title
|
||||||
|
setNowTitle(title: string) {
|
||||||
|
this.tagsViews.forEach((item) => {
|
||||||
|
// console.log(getNowUrl(), item.path);
|
||||||
|
if (item.path == getNowUrl()) {
|
||||||
|
item.title = title;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
import { dateFormat2 } from '@/common/utils/date';
|
||||||
|
import { useUserInfo } from '@/store/userInfo';
|
||||||
|
|
||||||
export const useThemeConfig = defineStore('themeConfig', {
|
export const useThemeConfig = defineStore('themeConfig', {
|
||||||
state: (): ThemeConfigState => ({
|
state: (): ThemeConfigState => ({
|
||||||
@@ -24,13 +26,13 @@ export const useThemeConfig = defineStore('themeConfig', {
|
|||||||
// 默认顶栏导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
// 默认顶栏导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
||||||
topBar: '#ffffff',
|
topBar: '#ffffff',
|
||||||
// 默认菜单导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
// 默认菜单导航背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
||||||
menuBar: '#545c64',
|
menuBar: '#FFFFFF',
|
||||||
// 默认分栏菜单背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
// 默认分栏菜单背景颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
||||||
columnsMenuBar: '#545c64',
|
columnsMenuBar: '#545c64',
|
||||||
// 默认顶栏导航字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
// 默认顶栏导航字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
||||||
topBarColor: '#606266',
|
topBarColor: '#606266',
|
||||||
// 默认菜单导航字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
// 默认菜单导航字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
||||||
menuBarColor: '#eaeaea',
|
menuBarColor: '#606266',
|
||||||
// 默认分栏菜单字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
// 默认分栏菜单字体颜色,请注意:需要同时修改 `/@/theme/common/var.scss` 对应的值
|
||||||
columnsMenuBarColor: '#e6e6e6',
|
columnsMenuBarColor: '#e6e6e6',
|
||||||
// 是否开启顶栏背景颜色渐变
|
// 是否开启顶栏背景颜色渐变
|
||||||
@@ -81,14 +83,16 @@ export const useThemeConfig = defineStore('themeConfig', {
|
|||||||
isSortableTagsView: true,
|
isSortableTagsView: true,
|
||||||
// 是否开启 Footer 底部版权信息
|
// 是否开启 Footer 底部版权信息
|
||||||
isFooter: false,
|
isFooter: false,
|
||||||
|
// 是否暗模式
|
||||||
|
isDark: false,
|
||||||
// 是否开启灰色模式
|
// 是否开启灰色模式
|
||||||
isGrayscale: false,
|
isGrayscale: false,
|
||||||
// 是否开启色弱模式
|
// 是否开启色弱模式
|
||||||
isInvert: false,
|
isInvert: false,
|
||||||
// 是否开启水印
|
// 是否开启水印
|
||||||
isWartermark: false,
|
isWatermark: false,
|
||||||
// 水印文案
|
// 水印文案数组,0->用户信息 1->当前时间 2->额外信息
|
||||||
wartermarkText: 'mayfly',
|
watermarkText: ['', '', ''],
|
||||||
|
|
||||||
/* 其它设置
|
/* 其它设置
|
||||||
------------------------------- */
|
------------------------------- */
|
||||||
@@ -131,6 +135,10 @@ export const useThemeConfig = defineStore('themeConfig', {
|
|||||||
globalI18n: 'zh-cn',
|
globalI18n: 'zh-cn',
|
||||||
// 默认全局组件大小,可选值"<|large|default|small>",默认 ''
|
// 默认全局组件大小,可选值"<|large|default|small>",默认 ''
|
||||||
globalComponentSize: '',
|
globalComponentSize: '',
|
||||||
|
|
||||||
|
/** 全局设置 */
|
||||||
|
// 默认列表页的分页大小
|
||||||
|
defaultListPageSize: 10,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
@@ -138,5 +146,28 @@ export const useThemeConfig = defineStore('themeConfig', {
|
|||||||
setThemeConfig(data: ThemeConfigState) {
|
setThemeConfig(data: ThemeConfigState) {
|
||||||
this.themeConfig = data.themeConfig;
|
this.themeConfig = data.themeConfig;
|
||||||
},
|
},
|
||||||
|
// 设置水印配置信息
|
||||||
|
setWatermarkConfig(useWatermarkConfig: any) {
|
||||||
|
this.themeConfig.watermarkText = [];
|
||||||
|
this.themeConfig.isWatermark = useWatermarkConfig.isUse;
|
||||||
|
if (!useWatermarkConfig.isUse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 索引2为用户自定义水印信息
|
||||||
|
this.themeConfig.watermarkText[2] = useWatermarkConfig.content;
|
||||||
|
},
|
||||||
|
// 设置水印用户信息
|
||||||
|
setWatermarkUser(del: boolean = false) {
|
||||||
|
const userinfo = useUserInfo().userInfo;
|
||||||
|
let desc = '';
|
||||||
|
if (!del && userinfo && userinfo.username) {
|
||||||
|
desc = `${userinfo.username}(${userinfo.name})`;
|
||||||
|
}
|
||||||
|
this.themeConfig.watermarkText[0] = desc;
|
||||||
|
},
|
||||||
|
// 设置水印时间为当前时间
|
||||||
|
setWatermarkNowTime() {
|
||||||
|
this.themeConfig.watermarkText[1] = dateFormat2('yyyy-MM-dd HH:mm:ss', new Date());
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { getSession } from '@/common/utils/storage';
|
import { getUser } from '@/common/utils/storage';
|
||||||
|
|
||||||
export const useUserInfo = defineStore('userInfo', {
|
export const useUserInfo = defineStore('userInfo', {
|
||||||
state: (): UserInfoState => ({
|
state: (): UserInfoState => ({
|
||||||
@@ -8,7 +8,7 @@ export const useUserInfo = defineStore('userInfo', {
|
|||||||
actions: {
|
actions: {
|
||||||
// 设置用户信息
|
// 设置用户信息
|
||||||
async setUserInfo(data: object) {
|
async setUserInfo(data: object) {
|
||||||
const ui = getSession('userInfo');
|
const ui = getUser();
|
||||||
if (ui) {
|
if (ui) {
|
||||||
this.userInfo = ui;
|
this.userInfo = ui;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,6 +7,26 @@
|
|||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-white: #ffffff;
|
||||||
|
--bg-main-color: #f8f8f8;
|
||||||
|
--bg-color: #f5f5ff;
|
||||||
|
--bg-menuBarActiveColor: #0000000a; // 菜单栏激活时的背景色
|
||||||
|
--border-color-light: #f1f2f3;
|
||||||
|
--el-color-primary-lighter: #ecf5ff;
|
||||||
|
--color-success-lighter: #f0f9eb;
|
||||||
|
--color-warning-lighter: #fdf6ec;
|
||||||
|
--color-danger-lighter: #fef0f0;
|
||||||
|
--color-dark-hover: #0000001a;
|
||||||
|
--color-menu-hover: rgba(0, 0, 0, 0.2);
|
||||||
|
--color-user-hover: rgba(0, 0, 0, 0.04);
|
||||||
|
--color-seting-main: #e9eef3;
|
||||||
|
--color-seting-aside: #d3dce6;
|
||||||
|
--color-seting-header: #b3c0d1;
|
||||||
|
|
||||||
|
--tagsview3-active-background-color: var(--el-color-primary-light-9);
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#app {
|
#app {
|
||||||
@@ -18,7 +38,7 @@ body,
|
|||||||
font-weight: 450;
|
font-weight: 450;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
background-color: #f8f8f8;
|
background-color: var(--bg-main-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -53,7 +73,7 @@ body,
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #f8f8f8;
|
background-color: var(--bg-main-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-scrollbar {
|
.el-scrollbar {
|
||||||
@@ -65,11 +85,11 @@ body,
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #ebeef5;
|
border: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-el-aside-br-color {
|
.layout-el-aside-br-color {
|
||||||
border-right: 1px solid rgb(238, 238, 238);
|
border-right: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.layout-aside-width-default {
|
.layout-aside-width-default {
|
||||||
@@ -116,7 +136,7 @@ body,
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
border-bottom: 1px solid rgb(230, 230, 230);
|
border-bottom: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-divider {
|
.el-divider {
|
||||||
@@ -128,7 +148,7 @@ body,
|
|||||||
------------------------------- */
|
------------------------------- */
|
||||||
#nprogress {
|
#nprogress {
|
||||||
.bar {
|
.bar {
|
||||||
background: var(--color-primary) !important;
|
background: var(--el-color-primary) !important;
|
||||||
z-index: 9999999 !important;
|
z-index: 9999999 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,23 +215,23 @@ body,
|
|||||||
/* 颜色值
|
/* 颜色值
|
||||||
------------------------------- */
|
------------------------------- */
|
||||||
.color-primary {
|
.color-primary {
|
||||||
color: var(--color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-success {
|
.color-success {
|
||||||
color: var(--color-success);
|
color: var(--el-color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-warning {
|
.color-warning {
|
||||||
color: var(--color-warning);
|
color: var(--el-color-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-danger {
|
.color-danger {
|
||||||
color: var(--color-danger);
|
color: var(--el-color-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-info {
|
.color-info {
|
||||||
color: var(--color-info);
|
color: var(--el-color-info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 字体大小全局样式
|
/* 字体大小全局样式
|
||||||
@@ -262,17 +282,17 @@ body,
|
|||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background-color: #F5F5F5;
|
background-color: var(--el-border-color-light, #ebeef5);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
|
||||||
background-color: #F5F5F5;
|
background-color: var(--el-border-color-light, #ebeef5);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
|
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
|
||||||
background-color: #F5F5F5;
|
background-color: var(--el-border-color-light, #ebeef5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu .fa {
|
.el-menu .fa {
|
||||||
@@ -317,11 +337,10 @@ body,
|
|||||||
|
|
||||||
.toolbar {
|
.toolbar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 6px;
|
padding: 4px;
|
||||||
background-color: #ffffff;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
line-height: 32px;
|
line-height: 24px;
|
||||||
border: 1px solid #e6ebf5;
|
border: 1px solid var(--el-border-color-light, #ebeef5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fl {
|
.fl {
|
||||||
@@ -345,3 +364,54 @@ body,
|
|||||||
.f12 {
|
.f12 {
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer-icon:hover {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
/* 鼠标移动到图标时的颜色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** splitpanes **/
|
||||||
|
.splitpanes.default-theme .splitpanes {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitpanes.default-theme .splitpanes__pane {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-theme.splitpanes--vertical>.splitpanes__splitter {
|
||||||
|
border-left: 1px solid var(--bg-main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-theme.splitpanes--horizontal>.splitpanes__splitter {
|
||||||
|
border-top: 1px solid var(--bg-main-color);
|
||||||
|
}
|
||||||
|
// 竖线样式
|
||||||
|
.splitpanes.default-theme .splitpanes__splitter::before,
|
||||||
|
.splitpanes.default-theme .splitpanes__splitter::after {
|
||||||
|
background-color: var(--el-color-info-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitpanes.default-theme .splitpanes__splitter:hover::before,
|
||||||
|
.splitpanes.default-theme .splitpanes__splitter:hover::after {
|
||||||
|
background-color: var(--el-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitpanes.default-theme .splitpanes__splitter {
|
||||||
|
min-width: 6px;
|
||||||
|
background: var(--el-color-info-light-8) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splitpanes.default-theme .splitpanes__splitter:hover {
|
||||||
|
background: var(--el-color-success-light-8) !important;
|
||||||
|
}
|
||||||
@@ -1,2 +1 @@
|
|||||||
@import 'common/transition.scss';
|
@import 'common/transition.scss';
|
||||||
@import 'common/var.scss';
|
|
||||||
|
|||||||
@@ -83,3 +83,42 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 登录页动画
|
||||||
|
------------------------------- */
|
||||||
|
@keyframes loginLeft {
|
||||||
|
0% {
|
||||||
|
left: -100%;
|
||||||
|
}
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes loginTop {
|
||||||
|
0% {
|
||||||
|
top: -100%;
|
||||||
|
}
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes loginRight {
|
||||||
|
0% {
|
||||||
|
right: -100%;
|
||||||
|
}
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
right: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes loginBottom {
|
||||||
|
0% {
|
||||||
|
bottom: -100%;
|
||||||
|
}
|
||||||
|
50%,
|
||||||
|
100% {
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
/**
|
|
||||||
* scss 怎么动态创建变量
|
|
||||||
* 本来想用 @function,@for 好像不可以动态创建
|
|
||||||
* 2020.12.19 lyt 记录
|
|
||||||
**/
|
|
||||||
|
|
||||||
/* 定义初始颜色
|
|
||||||
------------------------------- */
|
|
||||||
$--color-primary: #409eff !default;
|
|
||||||
$--color-whites: #ffffff !default;
|
|
||||||
$--color-blacks: #000000 !default;
|
|
||||||
$--color-primary-light-1: mix($--color-whites, $--color-primary, 10%) !default;
|
|
||||||
$--color-primary-light-2: mix($--color-whites, $--color-primary, 20%) !default;
|
|
||||||
$--color-primary-light-3: mix($--color-whites, $--color-primary, 30%) !default;
|
|
||||||
$--color-primary-light-4: mix($--color-whites, $--color-primary, 40%) !default;
|
|
||||||
$--color-primary-light-5: mix($--color-whites, $--color-primary, 50%) !default;
|
|
||||||
$--color-primary-light-6: mix($--color-whites, $--color-primary, 60%) !default;
|
|
||||||
$--color-primary-light-7: mix($--color-whites, $--color-primary, 70%) !default;
|
|
||||||
$--color-primary-light-8: mix($--color-whites, $--color-primary, 80%) !default;
|
|
||||||
$--color-primary-light-9: mix($--color-whites, $--color-primary, 90%) !default;
|
|
||||||
$--color-success: #67c23a !default;
|
|
||||||
$--color-success-light-1: mix($--color-whites, $--color-success, 10%) !default;
|
|
||||||
$--color-success-light-2: mix($--color-whites, $--color-success, 20%) !default;
|
|
||||||
$--color-success-light-3: mix($--color-whites, $--color-success, 30%) !default;
|
|
||||||
$--color-success-light-4: mix($--color-whites, $--color-success, 40%) !default;
|
|
||||||
$--color-success-light-5: mix($--color-whites, $--color-success, 50%) !default;
|
|
||||||
$--color-success-light-6: mix($--color-whites, $--color-success, 60%) !default;
|
|
||||||
$--color-success-light-7: mix($--color-whites, $--color-success, 70%) !default;
|
|
||||||
$--color-success-light-8: mix($--color-whites, $--color-success, 80%) !default;
|
|
||||||
$--color-success-light-9: mix($--color-whites, $--color-success, 90%) !default;
|
|
||||||
$--color-info: #909399 !default;
|
|
||||||
$--color-info-light-1: mix($--color-whites, $--color-info, 10%) !default;
|
|
||||||
$--color-info-light-2: mix($--color-whites, $--color-info, 20%) !default;
|
|
||||||
$--color-info-light-3: mix($--color-whites, $--color-info, 30%) !default;
|
|
||||||
$--color-info-light-4: mix($--color-whites, $--color-info, 40%) !default;
|
|
||||||
$--color-info-light-5: mix($--color-whites, $--color-info, 50%) !default;
|
|
||||||
$--color-info-light-6: mix($--color-whites, $--color-info, 60%) !default;
|
|
||||||
$--color-info-light-7: mix($--color-whites, $--color-info, 70%) !default;
|
|
||||||
$--color-info-light-8: mix($--color-whites, $--color-info, 80%) !default;
|
|
||||||
$--color-info-light-9: mix($--color-whites, $--color-info, 90%) !default;
|
|
||||||
$--color-warning: #e6a23c !default;
|
|
||||||
$--color-warning-light-1: mix($--color-whites, $--color-warning, 10%) !default;
|
|
||||||
$--color-warning-light-2: mix($--color-whites, $--color-warning, 20%) !default;
|
|
||||||
$--color-warning-light-3: mix($--color-whites, $--color-warning, 30%) !default;
|
|
||||||
$--color-warning-light-4: mix($--color-whites, $--color-warning, 40%) !default;
|
|
||||||
$--color-warning-light-5: mix($--color-whites, $--color-warning, 50%) !default;
|
|
||||||
$--color-warning-light-6: mix($--color-whites, $--color-warning, 60%) !default;
|
|
||||||
$--color-warning-light-7: mix($--color-whites, $--color-warning, 70%) !default;
|
|
||||||
$--color-warning-light-8: mix($--color-whites, $--color-warning, 80%) !default;
|
|
||||||
$--color-warning-light-9: mix($--color-whites, $--color-warning, 90%) !default;
|
|
||||||
$--color-danger: #f56c6c !default;
|
|
||||||
$--color-danger-light-1: mix($--color-whites, $--color-danger, 10%) !default;
|
|
||||||
$--color-danger-light-2: mix($--color-whites, $--color-danger, 20%) !default;
|
|
||||||
$--color-danger-light-3: mix($--color-whites, $--color-danger, 30%) !default;
|
|
||||||
$--color-danger-light-4: mix($--color-whites, $--color-danger, 40%) !default;
|
|
||||||
$--color-danger-light-5: mix($--color-whites, $--color-danger, 50%) !default;
|
|
||||||
$--color-danger-light-6: mix($--color-whites, $--color-danger, 60%) !default;
|
|
||||||
$--color-danger-light-7: mix($--color-whites, $--color-danger, 70%) !default;
|
|
||||||
$--color-danger-light-8: mix($--color-whites, $--color-danger, 80%) !default;
|
|
||||||
$--color-danger-light-9: mix($--color-whites, $--color-danger, 90%) !default;
|
|
||||||
$--bg-topBar: #ffffff;
|
|
||||||
$--bg-menuBar: #545c64;
|
|
||||||
$--bg-columnsMenuBar: #545c64;
|
|
||||||
$--bg-topBarColor: #606266;
|
|
||||||
$--bg-menuBarColor: #eaeaea;
|
|
||||||
$--bg-columnsMenuBarColor: #e6e6e6;
|
|
||||||
|
|
||||||
/* 赋值给:root
|
|
||||||
------------------------------- */
|
|
||||||
:root {
|
|
||||||
--color-primary: #{$--color-primary};
|
|
||||||
--color-whites: #{$--color-whites};
|
|
||||||
--color-blacks: #{$--color-blacks};
|
|
||||||
--color-primary-light-1: #{$--color-primary-light-1};
|
|
||||||
--color-primary-light-2: #{$--color-primary-light-2};
|
|
||||||
--color-primary-light-3: #{$--color-primary-light-3};
|
|
||||||
--color-primary-light-4: #{$--color-primary-light-4};
|
|
||||||
--color-primary-light-5: #{$--color-primary-light-5};
|
|
||||||
--color-primary-light-6: #{$--color-primary-light-6};
|
|
||||||
--color-primary-light-7: #{$--color-primary-light-7};
|
|
||||||
--color-primary-light-8: #{$--color-primary-light-8};
|
|
||||||
--color-primary-light-9: #{$--color-primary-light-9};
|
|
||||||
--color-success: #{$--color-success};
|
|
||||||
--color-success-light-1: #{$--color-success-light-1};
|
|
||||||
--color-success-light-2: #{$--color-success-light-2};
|
|
||||||
--color-success-light-3: #{$--color-success-light-3};
|
|
||||||
--color-success-light-4: #{$--color-success-light-4};
|
|
||||||
--color-success-light-5: #{$--color-success-light-5};
|
|
||||||
--color-success-light-6: #{$--color-success-light-6};
|
|
||||||
--color-success-light-7: #{$--color-success-light-7};
|
|
||||||
--color-success-light-8: #{$--color-success-light-8};
|
|
||||||
--color-success-light-9: #{$--color-success-light-9};
|
|
||||||
--color-info: #{$--color-info};
|
|
||||||
--color-info-light-1: #{$--color-info-light-1};
|
|
||||||
--color-info-light-2: #{$--color-info-light-2};
|
|
||||||
--color-info-light-3: #{$--color-info-light-3};
|
|
||||||
--color-info-light-4: #{$--color-info-light-4};
|
|
||||||
--color-info-light-5: #{$--color-info-light-5};
|
|
||||||
--color-info-light-6: #{$--color-info-light-6};
|
|
||||||
--color-info-light-7: #{$--color-info-light-7};
|
|
||||||
--color-info-light-8: #{$--color-info-light-8};
|
|
||||||
--color-info-light-9: #{$--color-info-light-9};
|
|
||||||
--color-warning: #{$--color-warning};
|
|
||||||
--color-warning-light-1: #{$--color-warning-light-1};
|
|
||||||
--color-warning-light-2: #{$--color-warning-light-2};
|
|
||||||
--color-warning-light-3: #{$--color-warning-light-3};
|
|
||||||
--color-warning-light-4: #{$--color-warning-light-4};
|
|
||||||
--color-warning-light-5: #{$--color-warning-light-5};
|
|
||||||
--color-warning-light-6: #{$--color-warning-light-6};
|
|
||||||
--color-warning-light-7: #{$--color-warning-light-7};
|
|
||||||
--color-warning-light-8: #{$--color-warning-light-8};
|
|
||||||
--color-warning-light-9: #{$--color-warning-light-9};
|
|
||||||
--color-danger: #{$--color-danger};
|
|
||||||
--color-danger-light-1: #{$--color-danger-light-1};
|
|
||||||
--color-danger-light-2: #{$--color-danger-light-2};
|
|
||||||
--color-danger-light-3: #{$--color-danger-light-3};
|
|
||||||
--color-danger-light-4: #{$--color-danger-light-4};
|
|
||||||
--color-danger-light-5: #{$--color-danger-light-5};
|
|
||||||
--color-danger-light-6: #{$--color-danger-light-6};
|
|
||||||
--color-danger-light-7: #{$--color-danger-light-7};
|
|
||||||
--color-danger-light-8: #{$--color-danger-light-8};
|
|
||||||
--color-danger-light-9: #{$--color-danger-light-9};
|
|
||||||
--bg-topBar: #{$--bg-topBar};
|
|
||||||
--bg-menuBar: #{$--bg-menuBar};
|
|
||||||
--bg-columnsMenuBar: #{$--bg-columnsMenuBar};
|
|
||||||
--bg-topBarColor: #{$--bg-topBarColor};
|
|
||||||
--bg-menuBarColor: #{$--bg-menuBarColor};
|
|
||||||
--bg-columnsMenuBarColor: #{$--bg-columnsMenuBarColor};
|
|
||||||
}
|
|
||||||
29
mayfly_go_web/src/theme/dark.scss
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
html.dark {
|
||||||
|
// 变量(自定义时,只需修改这里的值)
|
||||||
|
--next-bg-main: #1f1f1f;
|
||||||
|
--next-color-white: #ffffff;
|
||||||
|
--next-color-disabled: #191919;
|
||||||
|
--next-color-bar: #dadada;
|
||||||
|
--next-color-primary: #303030;
|
||||||
|
--next-border-color: #424242;
|
||||||
|
--next-border-black: #333333;
|
||||||
|
--next-border-columns: #2a2a2a;
|
||||||
|
--next-color-seting: #505050;
|
||||||
|
--next-text-color-regular: #9b9da1;
|
||||||
|
--next-text-color-placeholder: #7a7a7a;
|
||||||
|
--next-color-hover: #3c3c3c;
|
||||||
|
--next-color-hover-rgba: rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
/* 自定义深色背景颜色 */
|
||||||
|
// root
|
||||||
|
--bg-main-color: var(--next-bg-main) !important;
|
||||||
|
--bg-topBar: var(--next-color-disabled) !important;
|
||||||
|
--bg-topBarColor: var(--next-color-bar) !important;
|
||||||
|
--bg-menuBar: var(--next-color-disabled) !important;
|
||||||
|
--bg-menuBarColor: var(--next-color-bar) !important;
|
||||||
|
--bg-menuBarActiveColor: var(--next-color-hover-rgba) !important;
|
||||||
|
--bg-columnsMenuBar: var(--next-color-disabled) !important;
|
||||||
|
--bg-columnsMenuBarColor: var(--next-color-bar) !important;
|
||||||
|
|
||||||
|
--tagsview3-active-background-color: var(--next-color-hover);
|
||||||
|
}
|
||||||
@@ -4,4 +4,5 @@
|
|||||||
@import './element.scss';
|
@import './element.scss';
|
||||||
@import './media/media.scss';
|
@import './media/media.scss';
|
||||||
@import './waves.scss';
|
@import './waves.scss';
|
||||||
|
@import './dark.scss';
|
||||||
@import './iconSelector.scss';
|
@import './iconSelector.scss';
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
.loading-next .loading-next-box-warp .loading-next-box-item {
|
.loading-next .loading-next-box-warp .loading-next-box-item {
|
||||||
width: 33.333333%;
|
width: 33.333333%;
|
||||||
height: 33.333333%;
|
height: 33.333333%;
|
||||||
background: var(--color-primary);
|
background: var(--el-color-primary);
|
||||||
float: left;
|
float: left;
|
||||||
animation: loading-next-animation 1.2s infinite ease;
|
animation: loading-next-animation 1.2s infinite ease;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
height: 3px !important;
|
height: 3px !important;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-track-piece {
|
::-webkit-scrollbar-track-piece {
|
||||||
background-color: #f8f8f8;
|
background-color: var(--bg-main-color);
|
||||||
}
|
}
|
||||||
// 滚动条的设置
|
// 滚动条的设置
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
height: 7px;
|
height: 7px;
|
||||||
}
|
}
|
||||||
::-webkit-scrollbar-track-piece {
|
::-webkit-scrollbar-track-piece {
|
||||||
background-color: #f8f8f8;
|
background-color: var(--bg-main-color);
|
||||||
}
|
}
|
||||||
// 滚动条的设置
|
// 滚动条的设置
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
/* Button 按钮
|
|
||||||
------------------------------- */
|
|
||||||
@mixin Button($main, $c1, $c2) {
|
|
||||||
color: set-color($main);
|
|
||||||
background: set-color($c1);
|
|
||||||
border-color: set-color($c2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Radio 单选框、Checkbox 多选框
|
|
||||||
------------------------------- */
|
|
||||||
@mixin RadioCheckbox($name) {
|
|
||||||
background-color: set-color($name);
|
|
||||||
border-color: set-color($name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tag 标签
|
|
||||||
------------------------------- */
|
|
||||||
@mixin Tag($main, $c1, $c2) {
|
|
||||||
color: set-color($main);
|
|
||||||
background-color: set-color($c1);
|
|
||||||
border-color: set-color($c2);
|
|
||||||
}
|
|
||||||
@mixin TagDark($main, $c1) {
|
|
||||||
color: set-color($main);
|
|
||||||
background-color: set-color($c1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Alert 警告
|
|
||||||
------------------------------- */
|
|
||||||
@mixin Alert($main, $c1, $c2) {
|
|
||||||
color: set-color($main);
|
|
||||||
background: set-color($c1);
|
|
||||||
border: 1px solid set-color($c2);
|
|
||||||
}
|
|
||||||