mirror of
https://github.com/pure-admin/pure-admin-thin.git
synced 2025-12-14 22:30:31 +08:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e95672cb4 | ||
|
|
9c0872fa6a | ||
|
|
7678aa64e2 | ||
|
|
1e30b31be2 | ||
|
|
2c887ee1d9 | ||
|
|
635da1dc52 | ||
|
|
6f65e67872 | ||
|
|
c79862a6c8 | ||
|
|
799643a283 | ||
|
|
707200c71d | ||
|
|
bc548d500c | ||
|
|
4d57c9e3d7 | ||
|
|
1bcf391513 | ||
|
|
30af2b78fe | ||
|
|
0b1bd19179 | ||
|
|
4ccf200059 | ||
|
|
27056e7560 | ||
|
|
132fbbade3 | ||
|
|
c6e25d6933 | ||
|
|
cabf1f85ef | ||
|
|
cb3d7cd552 | ||
|
|
80453ec4b1 | ||
|
|
8ca8bbcee0 | ||
|
|
2b67efe771 | ||
|
|
bbe23ce0a2 | ||
|
|
68492ec362 | ||
|
|
bc3b199860 | ||
|
|
4407dd5d10 |
3
.env
3
.env
@@ -1,2 +1,5 @@
|
|||||||
# 平台本地运行端口号
|
# 平台本地运行端口号
|
||||||
VITE_PORT = 8848
|
VITE_PORT = 8848
|
||||||
|
|
||||||
|
# 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置)
|
||||||
|
VITE_HIDE_HOME = false
|
||||||
|
|||||||
@@ -13,4 +13,4 @@ VITE_CDN = true
|
|||||||
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
|
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
|
||||||
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
||||||
# 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
# 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
|
||||||
VITE_COMPRESSION = "both-clear"
|
VITE_COMPRESSION = "none"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
/dist/*
|
/dist/*
|
||||||
/public/*
|
/public/*
|
||||||
public/*
|
public/*
|
||||||
|
src/style/reset.scss
|
||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2022 啝裳
|
Copyright (c) 2020-present, pure-admin
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
<h1>vue-pure-admin Lite Edition(no i18n version)</h1>
|
<h1>vue-pure-admin Lite Edition(no i18n version)</h1>
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
**English** | [中文](./README.md)
|
**English** | [中文](./README.md)
|
||||||
|
|
||||||
## introduce
|
## Introduce
|
||||||
|
|
||||||
The Lite version is based on the shelf extracted from [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin), including the main functions, which is more suitable for actual project development, and the packaged size is lower than ` 3MB`, and will permanently sync the full version of the code. After enabling `brotli` compression and `cdn` to replace the native library mode, the package size is less than `500kb`
|
The simplified version is based on the shelf extracted from [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin), which contains main functions and is more suitable for actual project development. The packaged size is introduced globally [element-plus](https://element-plus.org) is still below `2.3MB`, and the full version of the code will be permanently synchronized. After enabling `brotli` compression and `cdn` to replace the local library mode, the package size is less than `350kb`
|
||||||
|
|
||||||
## Supporting Video
|
## Supporting Video
|
||||||
|
|
||||||
- [Click Watch Tutorial](https://www.bilibili.com/video/BV1534y1S7HV)
|
- [Click Watch Tutorial](https://www.bilibili.com/video/BV1kg411v7QT)
|
||||||
- [Click Watch UI Design](https://www.bilibili.com/video/BV17g411T7rq)
|
- [Click Watch UI Design](https://www.bilibili.com/video/BV17g411T7rq)
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
- [Click me to view the domestic documentation site](https://yiming_chang.gitee.io/pure-admin-doc)
|
- [Click me to view the domestic documentation site](https://yiming_chang.gitee.io/pure-admin-doc)
|
||||||
- [Click me to view foreign document site](https://xiaoxian521.github.io/pure-admin-doc)
|
- [Click me to view foreign document site](https://pure-admin.github.io/pure-admin-doc)
|
||||||
|
|
||||||
|
## Preview
|
||||||
|
|
||||||
|
- [Click me to view the preview station](https://pure-admin-thin.netlify.app/#/login)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -40,8 +44,10 @@ bilibili: https://www.bilibili.com/video/BV1534y1S7HV/
|
|||||||
|
|
||||||
## ⚠️ Attention
|
## ⚠️ Attention
|
||||||
|
|
||||||
- The Lite version does not accept any issues and prs. If you have any questions, please go to the full version https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose to mention, thank you! ! !
|
- The Lite version does not accept any issues and prs. If you have any questions, please go to the full version https://github.com/pure-admin/vue-pure-admin/issues/new/choose to mention, thank you! ! !
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
In principle, no fees and copyrights are charged, and you can use it with confidence, but if you need secondary open source, please contact the author for permission!
|
In principle, no fees and copyrights are charged, and you can use it with confidence, but if you need secondary open source, please contact the author for permission!
|
||||||
|
|
||||||
|
[MIT © 2020-present, pure-admin](./LICENSE)
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -1,42 +1,44 @@
|
|||||||
<h1>vue-pure-admin精简版(非国际化版本)</h1>
|
<h1>vue-pure-admin精简版(非国际化版本)</h1>
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
**中文** | [English](./README.en-US.md)
|
**中文** | [English](./README.en-US.md)
|
||||||
|
|
||||||
## 介绍
|
## 介绍
|
||||||
|
|
||||||
精简版是基于 [vue-pure-admin](https://github.com/xiaoxian521/vue-pure-admin) 提炼出的架子,包含主体功能,更适合实际项目开发,打包后的大小低于 `3MB`,并且会永久同步完整版的代码。开启 `brotli` 压缩和 `cdn` 替换本地库模式后,打包大小低于 `500kb`
|
精简版是基于 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin) 提炼出的架子,包含主体功能,更适合实际项目开发,打包后的大小在全局引入 [element-plus](https://element-plus.org) 的情况下仍然低于 `2.3MB`,并且会永久同步完整版的代码。开启 `brotli` 压缩和 `cdn` 替换本地库模式后,打包大小低于 `350kb`
|
||||||
|
|
||||||
## 版本选择
|
## 版本选择
|
||||||
|
|
||||||
当前是非国际化版本哦,如果您需要国际化版本 [请点击](https://github.com/xiaoxian521/pure-admin-thin/tree/i18n)
|
当前是非国际化版本哦,如果您需要国际化版本 [请点击](https://github.com/pure-admin/pure-admin-thin/tree/i18n)
|
||||||
|
|
||||||
## 配套视频
|
## 配套视频
|
||||||
|
|
||||||
- [点我查看教程](https://www.bilibili.com/video/BV1534y1S7HV)
|
- [点我查看教程](https://www.bilibili.com/video/BV1kg411v7QT)
|
||||||
- [点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
|
- [点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
|
||||||
|
|
||||||
## 配套文档
|
## 配套文档
|
||||||
|
|
||||||
- [点我查看国内文档站](https://yiming_chang.gitee.io/pure-admin-doc)
|
- [点我查看国内文档站](https://yiming_chang.gitee.io/pure-admin-doc)
|
||||||
- [点我查看国外文档站](https://xiaoxian521.github.io/pure-admin-doc)
|
- [点我查看国外文档站](https://pure-admin.github.io/pure-admin-doc)
|
||||||
|
|
||||||
|
## 预览
|
||||||
|
|
||||||
|
- [点我查看预览站](https://pure-admin-thin.netlify.app/#/login)
|
||||||
|
|
||||||
## 维护者
|
## 维护者
|
||||||
|
|
||||||
[xiaoxian521](https://github.com/xiaoxian521)
|
[xiaoxian521](https://github.com/xiaoxian521)
|
||||||
|
|
||||||
## 捐赠
|
## 支持
|
||||||
|
|
||||||
如果你觉得这个项目对您有帮助,可以帮作者买一杯果汁 🍹 表示支持
|
如果你觉得这个项目对您有帮助,可以帮作者买一杯果汁 🍹 表示支持
|
||||||
|
|
||||||
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f69bf13c5b854ed5b699807cafa0e3ce~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?" width="150px" height="150px" />
|
<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f69bf13c5b854ed5b699807cafa0e3ce~tplv-k3u1fbpfcp-zoom-in-crop-mark:1304:0:0:0.awebp?" width="150px" height="150px" />
|
||||||
|
|
||||||
## QQ 交流群
|
## `QQ` 交流群
|
||||||
|
|
||||||
一群已满,下面是二群,群里严禁 `黄` 、 `赌` 、 `毒` 、 `vpn` 等违法行为!
|
[点击去加入](https://yiming_chang.gitee.io/pure-admin-doc/pages/support/#qq-%E4%BA%A4%E6%B5%81%E7%BE%A4)
|
||||||
|
|
||||||
<img src="http://yiming_chang.gitee.io/pure-admin-doc/img/support/qq.png" width="150px" height="225px" />
|
|
||||||
|
|
||||||
## 用法
|
## 用法
|
||||||
|
|
||||||
@@ -56,10 +58,10 @@ pnpm remove 包名
|
|||||||
|
|
||||||
## ⚠️ 注意
|
## ⚠️ 注意
|
||||||
|
|
||||||
- 精简版不接受任何 `issues` 和 `pr`,如果有问题请到完整版 [issues](https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose) 去提,谢谢!!!
|
- 精简版不接受任何 `issues` 和 `pr`,如果有问题请到完整版 [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) 去提,谢谢!!!
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
|
原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
|
||||||
|
|
||||||
[MIT © xiaoxian521-2020](./LICENSE)
|
[MIT © 2020-present, pure-admin](./LICENSE)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Plugin as importToCDN } from "vite-plugin-cdn-import";
|
|||||||
* 注意:上面提到的仅限外网使用也不是完全肯定的,如果你们公司内网部署的有相关js、css文件,也可以将下面配置对应改一下,整一套内网版cdn
|
* 注意:上面提到的仅限外网使用也不是完全肯定的,如果你们公司内网部署的有相关js、css文件,也可以将下面配置对应改一下,整一套内网版cdn
|
||||||
*/
|
*/
|
||||||
export const cdn = importToCDN({
|
export const cdn = importToCDN({
|
||||||
//(prodUrl解释: name: 对应下面modules的name,version: 自动读取本地package.json中dependencies依赖中对应包的版本号,path: 对应下面modules的path)
|
//(prodUrl解释: name: 对应下面modules的name,version: 自动读取本地package.json中dependencies依赖中对应包的版本号,path: 对应下面modules的path,当然也可写完整路径,会替换prodUrl)
|
||||||
prodUrl: "https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}",
|
prodUrl: "https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}",
|
||||||
modules: [
|
modules: [
|
||||||
{
|
{
|
||||||
@@ -51,12 +51,6 @@ export const cdn = importToCDN({
|
|||||||
name: "echarts",
|
name: "echarts",
|
||||||
var: "echarts",
|
var: "echarts",
|
||||||
path: "echarts.min.js"
|
path: "echarts.min.js"
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "lodash",
|
|
||||||
var: "lodash",
|
|
||||||
// 可写`完整路径`,会替换`prodUrl`
|
|
||||||
path: "https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.min.js"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const warpperEnv = (envConf: Recordable): ViteEnv => {
|
|||||||
VITE_PUBLIC_PATH: "",
|
VITE_PUBLIC_PATH: "",
|
||||||
VITE_ROUTER_HISTORY: "",
|
VITE_ROUTER_HISTORY: "",
|
||||||
VITE_CDN: false,
|
VITE_CDN: false,
|
||||||
|
VITE_HIDE_HOME: "false",
|
||||||
VITE_COMPRESSION: "none"
|
VITE_COMPRESSION: "none"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,9 +28,4 @@ const warpperEnv = (envConf: Recordable): ViteEnv => {
|
|||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 获取环境变量 */
|
export { warpperEnv };
|
||||||
const loadEnv = (): ViteEnv => {
|
|
||||||
return import.meta.env;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { warpperEnv, loadEnv };
|
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import type { Plugin } from "vite";
|
import type { Plugin } from "vite";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
|
import utils from "@pureadmin/utils";
|
||||||
import duration from "dayjs/plugin/duration";
|
import duration from "dayjs/plugin/duration";
|
||||||
import { green, blue, bold } from "picocolors";
|
import { green, blue, bold } from "picocolors";
|
||||||
import { getPackageSize } from "@pureadmin/utils";
|
|
||||||
dayjs.extend(duration);
|
dayjs.extend(duration);
|
||||||
|
|
||||||
export function viteBuildInfo(): Plugin {
|
export function viteBuildInfo(): Plugin {
|
||||||
let config: { command: string };
|
let config: { command: string };
|
||||||
let startTime: Dayjs;
|
let startTime: Dayjs;
|
||||||
let endTime: Dayjs;
|
let endTime: Dayjs;
|
||||||
|
let outDir: string;
|
||||||
return {
|
return {
|
||||||
name: "vite:buildInfo",
|
name: "vite:buildInfo",
|
||||||
configResolved(resolvedConfig: { command: string }) {
|
configResolved(resolvedConfig) {
|
||||||
config = resolvedConfig;
|
config = resolvedConfig;
|
||||||
|
outDir = resolvedConfig.build?.outDir ?? "dist";
|
||||||
},
|
},
|
||||||
buildStart() {
|
buildStart() {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -20,7 +22,7 @@ export function viteBuildInfo(): Plugin {
|
|||||||
green(
|
green(
|
||||||
`👏欢迎使用${blue(
|
`👏欢迎使用${blue(
|
||||||
"[vue-pure-admin]"
|
"[vue-pure-admin]"
|
||||||
)},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/xiaoxian521/vue-pure-admin`
|
)},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/pure-admin/vue-pure-admin`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -31,7 +33,8 @@ export function viteBuildInfo(): Plugin {
|
|||||||
closeBundle() {
|
closeBundle() {
|
||||||
if (config.command === "build") {
|
if (config.command === "build") {
|
||||||
endTime = dayjs(new Date());
|
endTime = dayjs(new Date());
|
||||||
getPackageSize({
|
utils.getPackageSize({
|
||||||
|
folder: outDir,
|
||||||
callback: (size: string) => {
|
callback: (size: string) => {
|
||||||
console.log(
|
console.log(
|
||||||
bold(
|
bold(
|
||||||
|
|||||||
@@ -4,22 +4,28 @@
|
|||||||
* 尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include里,否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存
|
* 尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include里,否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存
|
||||||
* 温馨提示:如果您使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite
|
* 温馨提示:如果您使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite
|
||||||
*/
|
*/
|
||||||
export const include = [
|
const include = [
|
||||||
"qs",
|
"qs",
|
||||||
"mitt",
|
"mitt",
|
||||||
"dayjs",
|
"dayjs",
|
||||||
"axios",
|
"axios",
|
||||||
"pinia",
|
"pinia",
|
||||||
"lodash",
|
|
||||||
"echarts",
|
|
||||||
"xe-utils",
|
|
||||||
"vxe-table",
|
|
||||||
"js-cookie",
|
"js-cookie",
|
||||||
"lodash-es",
|
"sortablejs",
|
||||||
"@vueuse/core",
|
"@vueuse/core",
|
||||||
"lodash-unified",
|
|
||||||
"@ctrl/tinycolor",
|
|
||||||
"@pureadmin/utils",
|
"@pureadmin/utils",
|
||||||
"responsive-storage",
|
"responsive-storage",
|
||||||
"element-resize-detector"
|
"element-resize-detector"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在预构建中强制排除的依赖项
|
||||||
|
* 温馨提示:所有以 `@iconify-icons/` 开头引入的的本地图标模块,都应该加入到下面的 `exclude` 里,因为平台推荐的使用方式是哪里需要哪里引入而且都是单个的引入,不需要预构建,直接让浏览器加载就好
|
||||||
|
*/
|
||||||
|
const exclude = [
|
||||||
|
"@iconify-icons/ep",
|
||||||
|
"@iconify-icons/ri",
|
||||||
|
"@pureadmin/theme/dist/browser-utils"
|
||||||
|
];
|
||||||
|
|
||||||
|
export { include, exclude };
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import vue from "@vitejs/plugin-vue";
|
|||||||
import { viteBuildInfo } from "./info";
|
import { viteBuildInfo } from "./info";
|
||||||
import svgLoader from "vite-svg-loader";
|
import svgLoader from "vite-svg-loader";
|
||||||
import vueJsx from "@vitejs/plugin-vue-jsx";
|
import vueJsx from "@vitejs/plugin-vue-jsx";
|
||||||
import VueMacros from "unplugin-vue-macros/vite";
|
|
||||||
import { viteMockServe } from "vite-plugin-mock";
|
import { viteMockServe } from "vite-plugin-mock";
|
||||||
import { configCompressPlugin } from "./compress";
|
import { configCompressPlugin } from "./compress";
|
||||||
// import ElementPlus from "unplugin-element-plus/vite";
|
// import ElementPlus from "unplugin-element-plus/vite";
|
||||||
@@ -25,7 +24,6 @@ export function getPluginsList(
|
|||||||
vueJsx(),
|
vueJsx(),
|
||||||
VITE_CDN ? cdn : null,
|
VITE_CDN ? cdn : null,
|
||||||
configCompressPlugin(VITE_COMPRESSION),
|
configCompressPlugin(VITE_COMPRESSION),
|
||||||
VueMacros(),
|
|
||||||
// 线上环境删除console
|
// 线上环境删除console
|
||||||
removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
|
removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
|
||||||
viteBuildInfo(),
|
viteBuildInfo(),
|
||||||
@@ -33,14 +31,7 @@ export function getPluginsList(
|
|||||||
themePreprocessorPlugin({
|
themePreprocessorPlugin({
|
||||||
scss: {
|
scss: {
|
||||||
multipleScopeVars: genScssMultipleScopeVars(),
|
multipleScopeVars: genScssMultipleScopeVars(),
|
||||||
// 在生产模式是否抽取独立的主题css文件,extract为true以下属性有效
|
extract: true
|
||||||
extract: true,
|
|
||||||
// 会选取defaultScopeName对应的主题css文件在html添加link
|
|
||||||
themeLinkTagId: "head",
|
|
||||||
// "head"||"head-prepend" || "body" ||"body-prepend"
|
|
||||||
themeLinkTagInjectTo: "head",
|
|
||||||
// 是否对抽取的css文件内对应scopeName的权重类名移除
|
|
||||||
removeCssScopeName: false
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
// svg组件化支持
|
// svg组件化支持
|
||||||
|
|||||||
38
index.html
38
index.html
@@ -21,54 +21,54 @@
|
|||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#app {
|
#app {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader,
|
.loader,
|
||||||
.loader:before,
|
.loader::before,
|
||||||
.loader:after {
|
.loader::after {
|
||||||
border-radius: 50%;
|
|
||||||
width: 2.5em;
|
width: 2.5em;
|
||||||
height: 2.5em;
|
height: 2.5em;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: load-animation 1.8s infinite ease-in-out;
|
||||||
animation-fill-mode: both;
|
animation-fill-mode: both;
|
||||||
animation: loadAnimation 1.8s infinite ease-in-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
color: #406eeb;
|
|
||||||
font-size: 10px;
|
|
||||||
margin: 80px auto;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
margin: 80px auto;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #406eeb;
|
||||||
text-indent: -9999em;
|
text-indent: -9999em;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
animation-delay: -0.16s;
|
|
||||||
top: 0;
|
|
||||||
transform: translate(-50%, 0);
|
transform: translate(-50%, 0);
|
||||||
|
animation-delay: -0.16s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader:before,
|
.loader::before,
|
||||||
.loader:after {
|
.loader::after {
|
||||||
content: "";
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
content: "";
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader:before {
|
.loader::before {
|
||||||
left: -3.5em;
|
left: -3.5em;
|
||||||
animation-delay: -0.32s;
|
animation-delay: -0.32s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader:after {
|
.loader::after {
|
||||||
left: 3.5em;
|
left: 3.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes loadAnimation {
|
@keyframes load-animation {
|
||||||
0%,
|
0%,
|
||||||
80%,
|
80%,
|
||||||
100% {
|
100% {
|
||||||
|
|||||||
150
package.json
150
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pure-admin-thin",
|
"name": "pure-admin-thin",
|
||||||
"version": "3.8.5",
|
"version": "4.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
|
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
"cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML",
|
"cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML",
|
||||||
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
|
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
|
||||||
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
|
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
|
||||||
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
|
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
|
||||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/",
|
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||||
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
|
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
|
||||||
"lint:pretty": "pretty-quick --staged",
|
"lint:pretty": "pretty-quick --staged",
|
||||||
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
|
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
|
||||||
@@ -29,107 +29,107 @@
|
|||||||
"not op_mini all"
|
"not op_mini all"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ctrl/tinycolor": "^3.4.1",
|
"@pureadmin/descriptions": "^1.1.1",
|
||||||
"@pureadmin/descriptions": "^1.1.0",
|
"@pureadmin/table": "^2.1.0",
|
||||||
"@pureadmin/table": "^1.8.0",
|
"@pureadmin/utils": "^1.8.9",
|
||||||
"@pureadmin/utils": "^1.6.7",
|
"@vueuse/core": "^10.1.2",
|
||||||
"@vueuse/core": "^9.6.0",
|
|
||||||
"@vueuse/motion": "2.0.0-beta.12",
|
"@vueuse/motion": "2.0.0-beta.12",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.2.0",
|
"axios": "^1.4.0",
|
||||||
"dayjs": "^1.11.6",
|
"dayjs": "^1.11.7",
|
||||||
"echarts": "^5.4.0",
|
"echarts": "^5.4.2",
|
||||||
"element-plus": "^2.2.25",
|
"element-plus": "^2.3.4",
|
||||||
"element-resize-detector": "^1.2.4",
|
"element-resize-detector": "^1.2.4",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.5",
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"lodash-es": "^4.17.21",
|
|
||||||
"lodash-unified": "^1.0.2",
|
|
||||||
"mitt": "^3.0.0",
|
"mitt": "^3.0.0",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pinia": "^2.0.26",
|
"pinia": "^2.0.36",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.1",
|
||||||
"responsive-storage": "^2.1.0",
|
"responsive-storage": "^2.2.0",
|
||||||
"vue": "^3.2.45",
|
"sortablejs": "^1.15.0",
|
||||||
|
"vue": "^3.3.1",
|
||||||
"vue-router": "^4.1.6",
|
"vue-router": "^4.1.6",
|
||||||
"vue-types": "^4.2.1",
|
"vue-types": "^5.0.2"
|
||||||
"vxe-table": "^4.3.6",
|
|
||||||
"xe-utils": "^3.5.7"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "13.1.0",
|
"@commitlint/cli": "^17.6.3",
|
||||||
"@commitlint/config-conventional": "13.1.0",
|
"@commitlint/config-conventional": "^17.6.3",
|
||||||
"@iconify-icons/ep": "^1.2.7",
|
"@iconify-icons/ep": "^1.2.11",
|
||||||
"@iconify-icons/ri": "^1.2.3",
|
"@iconify-icons/ri": "^1.2.7",
|
||||||
"@iconify/vue": "^4.0.0",
|
"@iconify/vue": "^4.1.1",
|
||||||
"@pureadmin/theme": "^2.4.0",
|
"@pureadmin/theme": "^3.0.0",
|
||||||
"@types/element-resize-detector": "1.1.3",
|
"@types/element-resize-detector": "1.1.3",
|
||||||
"@types/js-cookie": "^3.0.1",
|
"@types/js-cookie": "^3.0.3",
|
||||||
"@types/lodash": "^4.14.180",
|
|
||||||
"@types/lodash-es": "^4.17.6",
|
|
||||||
"@types/mockjs": "^1.0.7",
|
"@types/mockjs": "^1.0.7",
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.15.12",
|
||||||
"@types/nprogress": "0.2.0",
|
"@types/nprogress": "0.2.0",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/qs": "^6.9.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
"@types/sortablejs": "^1.15.1",
|
||||||
"@typescript-eslint/parser": "^5.42.1",
|
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
||||||
"@vitejs/plugin-legacy": "^2.3.1",
|
"@typescript-eslint/parser": "^5.59.5",
|
||||||
"@vitejs/plugin-vue": "^3.2.0",
|
"@vitejs/plugin-vue": "^4.2.2",
|
||||||
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||||
"@vue/eslint-config-prettier": "^7.0.0",
|
"@vue/eslint-config-prettier": "^7.1.0",
|
||||||
"@vue/eslint-config-typescript": "^11.0.2",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"@vue/runtime-core": "^3.2.45",
|
"autoprefixer": "^10.4.14",
|
||||||
"autoprefixer": "^10.4.13",
|
"cloc": "^2.11.0",
|
||||||
"cloc": "^2.10.0",
|
"cssnano": "^6.0.1",
|
||||||
"cssnano": "^5.1.14",
|
"eslint": "^8.40.0",
|
||||||
"eslint": "^8.8.0",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-vue": "^9.12.0",
|
||||||
"eslint-plugin-vue": "^9.7.0",
|
"husky": "^8.0.3",
|
||||||
"font-awesome": "^4.7.0",
|
"lint-staged": "^13.2.2",
|
||||||
"husky": "^7.0.4",
|
|
||||||
"lint-staged": "11.1.2",
|
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
"postcss": "^8.4.18",
|
"postcss": "^8.4.23",
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-html": "^1.5.0",
|
||||||
"postcss-import": "^15.0.0",
|
"postcss-import": "^15.1.0",
|
||||||
"postcss-scss": "^4.0.5",
|
"postcss-scss": "^4.0.6",
|
||||||
"prettier": "^2.5.1",
|
"prettier": "^2.8.7",
|
||||||
"pretty-quick": "3.1.1",
|
"pretty-quick": "3.1.1",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "^5.0.0",
|
||||||
"rollup-plugin-visualizer": "^5.8.3",
|
"rollup-plugin-visualizer": "^5.9.0",
|
||||||
"sass": "^1.56.1",
|
"sass": "^1.62.1",
|
||||||
"sass-loader": "^13.2.0",
|
"sass-loader": "^13.2.2",
|
||||||
"stylelint": "^14.3.0",
|
"stylelint": "^15.6.1",
|
||||||
"stylelint-config-html": "^1.0.0",
|
"stylelint-config-html": "^1.1.0",
|
||||||
"stylelint-config-prettier": "^9.0.3",
|
"stylelint-config-recess-order": "^4.0.0",
|
||||||
"stylelint-config-recommended": "^9.0.0",
|
"stylelint-config-recommended": "^12.0.0",
|
||||||
"stylelint-config-standard": "^29.0.0",
|
"stylelint-config-recommended-scss": "^11.0.0",
|
||||||
"stylelint-order": "^5.0.0",
|
"stylelint-config-recommended-vue": "^1.4.0",
|
||||||
|
"stylelint-config-standard": "^33.0.0",
|
||||||
|
"stylelint-config-standard-scss": "^9.0.0",
|
||||||
|
"stylelint-order": "^6.0.3",
|
||||||
|
"stylelint-prettier": "^3.0.0",
|
||||||
|
"stylelint-scss": "^5.0.0",
|
||||||
"svgo": "^3.0.2",
|
"svgo": "^3.0.2",
|
||||||
"tailwindcss": "^3.2.4",
|
"tailwindcss": "^3.3.2",
|
||||||
"terser": "^5.15.1",
|
"terser": "^5.17.1",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^5.0.4",
|
||||||
"unplugin-vue-macros": "^1.0.3",
|
"vite": "^4.3.5",
|
||||||
"vite": "3.1.8",
|
|
||||||
"vite-plugin-cdn-import": "^0.3.5",
|
"vite-plugin-cdn-import": "^0.3.5",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-mock": "^2.9.6",
|
"vite-plugin-mock": "^2.9.6",
|
||||||
"vite-plugin-remove-console": "^1.3.0",
|
"vite-plugin-remove-console": "^2.1.1",
|
||||||
"vite-svg-loader": "^3.6.0",
|
"vite-svg-loader": "^4.0.0",
|
||||||
"vue-eslint-parser": "^9.1.0",
|
"vue-eslint-parser": "^9.2.1",
|
||||||
"vue-tsc": "^1.0.9"
|
"vue-tsc": "^1.6.4"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"ignoreMissing": [
|
"ignoreMissing": [
|
||||||
"rollup",
|
"rollup",
|
||||||
"webpack"
|
"webpack",
|
||||||
|
"core-js"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"allowedDeprecatedVersions": {
|
||||||
|
"sourcemap-codec": "*",
|
||||||
|
"stable": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"repository": "git@github.com:xiaoxian521/pure-admin-thin.git",
|
"repository": "git@github.com:pure-admin/pure-admin-thin.git",
|
||||||
"author": "xiaoxian521",
|
"author": "xiaoxian521",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
4791
pnpm-lock.yaml
generated
4791
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
"postcss-import": {},
|
"postcss-import": {},
|
||||||
|
"tailwindcss/nesting": {},
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {})
|
...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {})
|
||||||
|
|||||||
1
public/logo.svg
Normal file
1
public/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109z"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665z"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.114 323.114 0 0 1-107.769-242.852z"/></svg>
|
||||||
|
After Width: | Height: | Size: 712 B |
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Version": "3.8.5",
|
"Version": "4.1.0",
|
||||||
"Title": "PureAdmin",
|
"Title": "PureAdmin",
|
||||||
"FixedHeader": true,
|
"FixedHeader": true,
|
||||||
"HiddenSideBar": false,
|
"HiddenSideBar": false,
|
||||||
@@ -15,5 +15,8 @@
|
|||||||
"EpThemeColor": "#409EFF",
|
"EpThemeColor": "#409EFF",
|
||||||
"ShowLogo": true,
|
"ShowLogo": true,
|
||||||
"ShowModel": "smart",
|
"ShowModel": "smart",
|
||||||
"MenuArrowIconNoTransition": true
|
"MenuArrowIconNoTransition": true,
|
||||||
|
"CachingAsyncRoutes": false,
|
||||||
|
"TooltipEffect": "light",
|
||||||
|
"ResponsiveStorageNameSpace": "responsive-"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-config-provider :locale="currentLocale">
|
<el-config-provider :locale="currentLocale">
|
||||||
<router-view />
|
<router-view />
|
||||||
|
<ReDialog />
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -8,10 +9,12 @@
|
|||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
import { ElConfigProvider } from "element-plus";
|
import { ElConfigProvider } from "element-plus";
|
||||||
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
||||||
|
import { ReDialog } from "@/components/ReDialog";
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "app",
|
name: "app",
|
||||||
components: {
|
components: {
|
||||||
[ElConfigProvider.name]: ElConfigProvider
|
[ElConfigProvider.name]: ElConfigProvider,
|
||||||
|
ReDialog
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentLocale() {
|
currentLocale() {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 2208059 */
|
font-family: "iconfont"; /* Project id 2208059 */
|
||||||
src: url("iconfont.woff2?t=1638023560828") format("woff2"),
|
src: url("iconfont.woff2?t=1671895108120") format("woff2"),
|
||||||
url("iconfont.woff?t=1638023560828") format("woff"),
|
url("iconfont.woff?t=1671895108120") format("woff"),
|
||||||
url("iconfont.ttf?t=1638023560828") format("truetype");
|
url("iconfont.ttf?t=1671895108120") format("truetype");
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,26 +13,14 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
.team-icontabs::before {
|
.pure-iconfont-tabs:before {
|
||||||
content: "\e63e";
|
content: "\e63e";
|
||||||
}
|
}
|
||||||
|
|
||||||
.team-iconlogo::before {
|
.pure-iconfont-logo:before {
|
||||||
content: "\e620";
|
content: "\e620";
|
||||||
}
|
}
|
||||||
|
|
||||||
.team-iconxinpin::before {
|
.pure-iconfont-new:before {
|
||||||
content: "\e614";
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-iconxinpinrenqiwang::before {
|
|
||||||
content: "\e615";
|
content: "\e615";
|
||||||
}
|
}
|
||||||
|
|
||||||
.team-iconexit-fullscreen::before {
|
|
||||||
content: "\e62a";
|
|
||||||
}
|
|
||||||
|
|
||||||
.team-iconfullscreen::before {
|
|
||||||
content: "\e62b";
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -2,50 +2,29 @@
|
|||||||
"id": "2208059",
|
"id": "2208059",
|
||||||
"name": "pure-admin",
|
"name": "pure-admin",
|
||||||
"font_family": "iconfont",
|
"font_family": "iconfont",
|
||||||
"css_prefix_text": "team-icon",
|
"css_prefix_text": "pure-iconfont-",
|
||||||
"description": "pure-admin",
|
"description": "pure-admin-iconfont",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
{
|
{
|
||||||
"icon_id": "20594647",
|
"icon_id": "20594647",
|
||||||
"name": "标签页",
|
"name": "Tabs",
|
||||||
"font_class": "tabs",
|
"font_class": "tabs",
|
||||||
"unicode": "e63e",
|
"unicode": "e63e",
|
||||||
"unicode_decimal": 58942
|
"unicode_decimal": 58942
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "22129506",
|
"icon_id": "22129506",
|
||||||
"name": "水能",
|
"name": "PureLogo",
|
||||||
"font_class": "logo",
|
"font_class": "logo",
|
||||||
"unicode": "e620",
|
"unicode": "e620",
|
||||||
"unicode_decimal": 58912
|
"unicode_decimal": 58912
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"icon_id": "7795613",
|
|
||||||
"name": "新品",
|
|
||||||
"font_class": "xinpin",
|
|
||||||
"unicode": "e614",
|
|
||||||
"unicode_decimal": 58900
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"icon_id": "7795615",
|
"icon_id": "7795615",
|
||||||
"name": "新品人气王",
|
"name": "New",
|
||||||
"font_class": "xinpinrenqiwang",
|
"font_class": "new",
|
||||||
"unicode": "e615",
|
"unicode": "e615",
|
||||||
"unicode_decimal": 58901
|
"unicode_decimal": 58901
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon_id": "5698509",
|
|
||||||
"name": "全屏缩小",
|
|
||||||
"font_class": "exit-fullscreen",
|
|
||||||
"unicode": "e62a",
|
|
||||||
"unicode_decimal": 58922
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"icon_id": "5698510",
|
|
||||||
"name": "全屏显示",
|
|
||||||
"font_class": "fullscreen",
|
|
||||||
"unicode": "e62b",
|
|
||||||
"unicode_decimal": 58923
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
29
src/components/ReCol/index.ts
Normal file
29
src/components/ReCol/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { ElCol } from "element-plus";
|
||||||
|
import { h, defineComponent } from "vue";
|
||||||
|
|
||||||
|
// 封装element-plus的el-col组件
|
||||||
|
export default defineComponent({
|
||||||
|
name: "ReCol",
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Number,
|
||||||
|
default: 24
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const attrs = this.$attrs;
|
||||||
|
const val = this.value;
|
||||||
|
return h(
|
||||||
|
ElCol,
|
||||||
|
{
|
||||||
|
xs: val,
|
||||||
|
sm: val,
|
||||||
|
md: val,
|
||||||
|
lg: val,
|
||||||
|
xl: val,
|
||||||
|
...attrs
|
||||||
|
},
|
||||||
|
{ default: () => this.$slots.default() }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
39
src/components/ReDialog/index.ts
Normal file
39
src/components/ReDialog/index.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
import reDialog from "./index.vue";
|
||||||
|
import { useTimeoutFn } from "@vueuse/core";
|
||||||
|
import { withInstall } from "@pureadmin/utils";
|
||||||
|
import type {
|
||||||
|
EventType,
|
||||||
|
ArgsType,
|
||||||
|
DialogProps,
|
||||||
|
ButtonProps,
|
||||||
|
DialogOptions
|
||||||
|
} from "./type";
|
||||||
|
|
||||||
|
const dialogStore = ref<Array<DialogOptions>>([]);
|
||||||
|
|
||||||
|
const addDialog = (options: DialogOptions) => {
|
||||||
|
const open = () =>
|
||||||
|
dialogStore.value.push(Object.assign(options, { visible: true }));
|
||||||
|
if (options?.openDelay) {
|
||||||
|
useTimeoutFn(() => {
|
||||||
|
open();
|
||||||
|
}, options.openDelay);
|
||||||
|
} else {
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = (options: DialogOptions, index: number, args?: any) => {
|
||||||
|
dialogStore.value.splice(index, 1);
|
||||||
|
options.closeCallBack && options.closeCallBack({ options, index, args });
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAllDialog = () => {
|
||||||
|
dialogStore.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const ReDialog = withInstall(reDialog);
|
||||||
|
|
||||||
|
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
|
||||||
|
export { ReDialog, dialogStore, addDialog, closeDialog, closeAllDialog };
|
||||||
118
src/components/ReDialog/index.vue
Normal file
118
src/components/ReDialog/index.vue
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { isFunction } from "@pureadmin/utils";
|
||||||
|
import {
|
||||||
|
type DialogOptions,
|
||||||
|
type ButtonProps,
|
||||||
|
type EventType,
|
||||||
|
dialogStore,
|
||||||
|
closeDialog
|
||||||
|
} from "./index";
|
||||||
|
|
||||||
|
const footerButtons = computed(() => {
|
||||||
|
return (options: DialogOptions) => {
|
||||||
|
return options?.footerButtons?.length > 0
|
||||||
|
? options.footerButtons
|
||||||
|
: ([
|
||||||
|
{
|
||||||
|
label: "取消",
|
||||||
|
text: true,
|
||||||
|
bg: true,
|
||||||
|
btnClick: ({ dialog: { options, index } }) => {
|
||||||
|
const done = () =>
|
||||||
|
closeDialog(options, index, { command: "cancel" });
|
||||||
|
if (options?.beforeCancel && isFunction(options?.beforeCancel)) {
|
||||||
|
options.beforeCancel(done, { options, index });
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "确定",
|
||||||
|
type: "primary",
|
||||||
|
text: true,
|
||||||
|
bg: true,
|
||||||
|
btnClick: ({ dialog: { options, index } }) => {
|
||||||
|
const done = () =>
|
||||||
|
closeDialog(options, index, { command: "sure" });
|
||||||
|
if (options?.beforeSure && isFunction(options?.beforeSure)) {
|
||||||
|
options.beforeSure(done, { options, index });
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
] as Array<ButtonProps>);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function eventsCallBack(
|
||||||
|
event: EventType,
|
||||||
|
options: DialogOptions,
|
||||||
|
index: number
|
||||||
|
) {
|
||||||
|
if (options?.[event] && isFunction(options?.[event])) {
|
||||||
|
return options?.[event]({ options, index });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose(
|
||||||
|
options: DialogOptions,
|
||||||
|
index: number,
|
||||||
|
args = { command: "close" }
|
||||||
|
) {
|
||||||
|
closeDialog(options, index, args);
|
||||||
|
eventsCallBack("close", options, index);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-for="(options, index) in dialogStore"
|
||||||
|
:key="index"
|
||||||
|
v-bind="options"
|
||||||
|
v-model="options.visible"
|
||||||
|
@opened="eventsCallBack('open', options, index)"
|
||||||
|
@close="handleClose(options, index)"
|
||||||
|
@openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
|
||||||
|
@closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
|
||||||
|
>
|
||||||
|
<!-- header -->
|
||||||
|
<template
|
||||||
|
v-if="options?.headerRenderer"
|
||||||
|
#header="{ close, titleId, titleClass }"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="options?.headerRenderer({ close, titleId, titleClass })"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<!-- default -->
|
||||||
|
<component
|
||||||
|
v-bind="options?.props"
|
||||||
|
:is="options.contentRenderer({ options, index })"
|
||||||
|
@close="args => handleClose(options, index, args)"
|
||||||
|
/>
|
||||||
|
<!-- footer -->
|
||||||
|
<template v-if="!options?.hideFooter" #footer>
|
||||||
|
<template v-if="options?.footerRenderer">
|
||||||
|
<component :is="options?.footerRenderer({ options, index })" />
|
||||||
|
</template>
|
||||||
|
<span v-else>
|
||||||
|
<el-button
|
||||||
|
v-for="(btn, key) in footerButtons(options)"
|
||||||
|
:key="key"
|
||||||
|
v-bind="btn"
|
||||||
|
@click="
|
||||||
|
btn.btnClick({
|
||||||
|
dialog: { options, index },
|
||||||
|
button: { btn, index: key }
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ btn?.label }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
216
src/components/ReDialog/type.ts
Normal file
216
src/components/ReDialog/type.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import type { CSSProperties, VNode, Component } from "vue";
|
||||||
|
|
||||||
|
type DoneFn = (cancel?: boolean) => void;
|
||||||
|
type EventType = "open" | "close" | "openAutoFocus" | "closeAutoFocus";
|
||||||
|
type ArgsType = {
|
||||||
|
/** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */
|
||||||
|
command: "cancel" | "sure" | "close";
|
||||||
|
};
|
||||||
|
|
||||||
|
/** https://element-plus.org/zh-CN/component/dialog.html#attributes */
|
||||||
|
type DialogProps = {
|
||||||
|
/** `Dialog` 的显示与隐藏 */
|
||||||
|
visible?: boolean;
|
||||||
|
/** `Dialog` 的标题 */
|
||||||
|
title?: string;
|
||||||
|
/** `Dialog` 的宽度,默认 `50%` */
|
||||||
|
width?: string | number;
|
||||||
|
/** 是否为全屏 `Dialog`,默认 `false` */
|
||||||
|
fullscreen?: boolean;
|
||||||
|
/** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */
|
||||||
|
top?: string;
|
||||||
|
/** 是否需要遮罩层,默认 `true` */
|
||||||
|
modal?: boolean;
|
||||||
|
/** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */
|
||||||
|
appendToBody?: boolean;
|
||||||
|
/** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */
|
||||||
|
lockScroll?: boolean;
|
||||||
|
/** `Dialog` 的自定义类名 */
|
||||||
|
class?: string;
|
||||||
|
/** `Dialog` 的自定义样式 */
|
||||||
|
style?: CSSProperties;
|
||||||
|
/** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */
|
||||||
|
openDelay?: number;
|
||||||
|
/** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */
|
||||||
|
closeDelay?: number;
|
||||||
|
/** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */
|
||||||
|
closeOnClickModal?: boolean;
|
||||||
|
/** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */
|
||||||
|
closeOnPressEscape?: boolean;
|
||||||
|
/** 是否显示关闭按钮,默认 `true` */
|
||||||
|
showClose?: boolean;
|
||||||
|
/** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||||
|
beforeClose?: (done: DoneFn) => void;
|
||||||
|
/** 为 `Dialog` 启用可拖拽功能,默认 `false` */
|
||||||
|
draggable?: boolean;
|
||||||
|
/** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */
|
||||||
|
center?: boolean;
|
||||||
|
/** 是否水平垂直对齐对话框,默认 `false` */
|
||||||
|
alignCenter?: boolean;
|
||||||
|
/** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */
|
||||||
|
destroyOnClose?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BtnClickDialog = {
|
||||||
|
options?: DialogOptions;
|
||||||
|
index?: number;
|
||||||
|
};
|
||||||
|
type BtnClickButton = {
|
||||||
|
btn?: ButtonProps;
|
||||||
|
index?: number;
|
||||||
|
};
|
||||||
|
/** https://element-plus.org/zh-CN/component/button.html#button-attributes */
|
||||||
|
type ButtonProps = {
|
||||||
|
/** 按钮文字 */
|
||||||
|
label: string;
|
||||||
|
/** 按钮尺寸 */
|
||||||
|
size?: "large" | "default" | "small";
|
||||||
|
/** 按钮类型 */
|
||||||
|
type?: "primary" | "success" | "warning" | "danger" | "info";
|
||||||
|
/** 是否为朴素按钮,默认 `false` */
|
||||||
|
plain?: boolean;
|
||||||
|
/** 是否为文字按钮,默认 `false` */
|
||||||
|
text?: boolean;
|
||||||
|
/** 是否显示文字按钮背景颜色,默认 `false` */
|
||||||
|
bg?: boolean;
|
||||||
|
/** 是否为链接按钮,默认 `false` */
|
||||||
|
link?: boolean;
|
||||||
|
/** 是否为圆角按钮,默认 `false` */
|
||||||
|
round?: boolean;
|
||||||
|
/** 是否为圆形按钮,默认 `false` */
|
||||||
|
circle?: boolean;
|
||||||
|
/** 是否为加载中状态,默认 `false` */
|
||||||
|
loading?: boolean;
|
||||||
|
/** 自定义加载中状态图标组件 */
|
||||||
|
loadingIcon?: string | Component;
|
||||||
|
/** 按钮是否为禁用状态,默认 `false` */
|
||||||
|
disabled?: boolean;
|
||||||
|
/** 图标组件 */
|
||||||
|
icon?: string | Component;
|
||||||
|
/** 是否开启原生 `autofocus` 属性,默认 `false` */
|
||||||
|
autofocus?: boolean;
|
||||||
|
/** 原生 `type` 属性,默认 `button` */
|
||||||
|
nativeType?: "button" | "submit" | "reset";
|
||||||
|
/** 自动在两个中文字符之间插入空格 */
|
||||||
|
autoInsertSpace?: boolean;
|
||||||
|
/** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */
|
||||||
|
color?: string;
|
||||||
|
/** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */
|
||||||
|
dark?: boolean;
|
||||||
|
/** 自定义元素标签 */
|
||||||
|
tag?: string | Component;
|
||||||
|
/** 点击按钮后触发的回调 */
|
||||||
|
btnClick?: ({
|
||||||
|
dialog,
|
||||||
|
button
|
||||||
|
}: {
|
||||||
|
/** 当前 `Dialog` 信息 */
|
||||||
|
dialog: BtnClickDialog;
|
||||||
|
/** 当前 `button` 信息 */
|
||||||
|
button: BtnClickButton;
|
||||||
|
}) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DialogOptions extends DialogProps {
|
||||||
|
/** 内容区组件的 `props`,可通过 `defineProps` 接收 */
|
||||||
|
props?: any;
|
||||||
|
/** 是否隐藏 `Dialog` 按钮操作区的内容 */
|
||||||
|
hideFooter?: boolean;
|
||||||
|
/**
|
||||||
|
* @description 自定义对话框标题的内容渲染器
|
||||||
|
* @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8}
|
||||||
|
*/
|
||||||
|
headerRenderer?: ({
|
||||||
|
close,
|
||||||
|
titleId,
|
||||||
|
titleClass
|
||||||
|
}: {
|
||||||
|
close: Function;
|
||||||
|
titleId: string;
|
||||||
|
titleClass: string;
|
||||||
|
}) => VNode | Component;
|
||||||
|
/** 自定义内容渲染器 */
|
||||||
|
contentRenderer?: ({
|
||||||
|
options,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
}) => VNode | Component;
|
||||||
|
/** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */
|
||||||
|
footerRenderer?: ({
|
||||||
|
options,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
}) => VNode | Component;
|
||||||
|
/** 自定义底部按钮操作 */
|
||||||
|
footerButtons?: Array<ButtonProps>;
|
||||||
|
/** `Dialog` 打开后的回调 */
|
||||||
|
open?: ({
|
||||||
|
options,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
}) => void;
|
||||||
|
/** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或者空白页关闭页面时才会触发) */
|
||||||
|
close?: ({
|
||||||
|
options,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
}) => void;
|
||||||
|
/** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */
|
||||||
|
closeCallBack?: ({
|
||||||
|
options,
|
||||||
|
index,
|
||||||
|
args
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
args: any;
|
||||||
|
}) => void;
|
||||||
|
/** 输入焦点聚焦在 `Dialog` 内容时的回调 */
|
||||||
|
openAutoFocus?: ({
|
||||||
|
options,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
}) => void;
|
||||||
|
/** 输入焦点从 `Dialog` 内容失焦时的回调 */
|
||||||
|
closeAutoFocus?: ({
|
||||||
|
options,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
}) => void;
|
||||||
|
/** 点击底部取消按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||||
|
beforeCancel?: (
|
||||||
|
done: Function,
|
||||||
|
{
|
||||||
|
options,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
/** 点击底部确定按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
|
||||||
|
beforeSure?: (
|
||||||
|
done: Function,
|
||||||
|
{
|
||||||
|
options,
|
||||||
|
index
|
||||||
|
}: {
|
||||||
|
options: DialogOptions;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
|
||||||
@@ -34,6 +34,16 @@ export function useRenderIcon(icon: any, attrs?: iconType): Component {
|
|||||||
} else if (typeof icon === "function" || typeof icon?.render === "function") {
|
} else if (typeof icon === "function" || typeof icon?.render === "function") {
|
||||||
// svg
|
// svg
|
||||||
return icon;
|
return icon;
|
||||||
|
} else if (typeof icon === "object") {
|
||||||
|
return defineComponent({
|
||||||
|
name: "OfflineIcon",
|
||||||
|
render() {
|
||||||
|
return h(IconifyIconOffline, {
|
||||||
|
icon: icon,
|
||||||
|
...attrs
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之
|
// 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之
|
||||||
return defineComponent({
|
return defineComponent({
|
||||||
|
|||||||
@@ -1,88 +1,22 @@
|
|||||||
import { h, defineComponent } from "vue";
|
import { h, defineComponent } from "vue";
|
||||||
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline";
|
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline";
|
||||||
|
|
||||||
// element-plus icon
|
|
||||||
import Check from "@iconify-icons/ep/check";
|
|
||||||
import HomeFilled from "@iconify-icons/ep/home-filled";
|
|
||||||
import Lollipop from "@iconify-icons/ep/lollipop";
|
|
||||||
import RefreshRight from "@iconify-icons/ep/refresh-right";
|
|
||||||
import Close from "@iconify-icons/ep/close";
|
|
||||||
import CloseBold from "@iconify-icons/ep/close-bold";
|
|
||||||
import Bell from "@iconify-icons/ep/bell";
|
|
||||||
import Search from "@iconify-icons/ep/search";
|
|
||||||
import EpArrowDown from "@iconify-icons/ep/arrow-down";
|
|
||||||
import ArrowUp from "@iconify-icons/ep/arrow-up";
|
|
||||||
import ArrowRight from "@iconify-icons/ep/arrow-right";
|
|
||||||
import ArrowLeft from "@iconify-icons/ep/arrow-left";
|
|
||||||
addIcon("check", Check);
|
|
||||||
addIcon("home-filled", HomeFilled);
|
|
||||||
addIcon("lollipop", Lollipop);
|
|
||||||
addIcon("refresh-right", RefreshRight);
|
|
||||||
addIcon("close", Close);
|
|
||||||
addIcon("close-bold", CloseBold);
|
|
||||||
addIcon("bell", Bell);
|
|
||||||
addIcon("search", Search);
|
|
||||||
addIcon("ep-arrow-down", EpArrowDown);
|
|
||||||
addIcon("ep-arrow-up", ArrowUp);
|
|
||||||
addIcon("ep-arrow-right", ArrowRight);
|
|
||||||
addIcon("ep-arrow-left", ArrowLeft);
|
|
||||||
|
|
||||||
// remixicon
|
|
||||||
import ArrowRightSLine from "@iconify-icons/ri/arrow-right-s-line";
|
|
||||||
import ArrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line";
|
|
||||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
|
||||||
import InformationLine from "@iconify-icons/ri/information-line";
|
|
||||||
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
|
|
||||||
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
|
|
||||||
import Bookmark2Line from "@iconify-icons/ri/bookmark-2-line";
|
|
||||||
import User from "@iconify-icons/ri/user-3-fill";
|
|
||||||
import Lock from "@iconify-icons/ri/lock-fill";
|
|
||||||
import MenuUnfold from "@iconify-icons/ri/menu-unfold-fill";
|
|
||||||
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
|
|
||||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
|
||||||
import ArrowDown from "@iconify-icons/ri/arrow-down-s-line";
|
|
||||||
import CloseLeftTags from "@iconify-icons/ri/text-direction-r";
|
|
||||||
import CloseRightTags from "@iconify-icons/ri/text-direction-l";
|
|
||||||
import CloseOtherTags from "@iconify-icons/ri/text-spacing";
|
|
||||||
import CloseAllTags from "@iconify-icons/ri/subtract-line";
|
|
||||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
|
||||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
|
|
||||||
addIcon("arrow-right-s-line", ArrowRightSLine);
|
|
||||||
addIcon("arrow-left-s-line", ArrowLeftSLine);
|
|
||||||
addIcon("logout-circle-r-line", LogoutCircleRLine);
|
|
||||||
addIcon("information-line", InformationLine);
|
|
||||||
addIcon("arrow-up-line", ArrowUpLine);
|
|
||||||
addIcon("arrow-down-line", ArrowDownLine);
|
|
||||||
addIcon("bookmark-2-line", Bookmark2Line);
|
|
||||||
addIcon("user", User);
|
|
||||||
addIcon("lock", Lock);
|
|
||||||
addIcon("menu-unfold", MenuUnfold);
|
|
||||||
addIcon("menu-fold", MenuFold);
|
|
||||||
addIcon("setting", Setting);
|
|
||||||
addIcon("arrow-down", ArrowDown);
|
|
||||||
addIcon("close-left-tags", CloseLeftTags);
|
|
||||||
addIcon("close-right-tags", CloseRightTags);
|
|
||||||
addIcon("close-other-tags", CloseOtherTags);
|
|
||||||
addIcon("close-all-tags", CloseAllTags);
|
|
||||||
addIcon("fullscreen", Fullscreen);
|
|
||||||
addIcon("exit-fullscreen", ExitFullscreen);
|
|
||||||
|
|
||||||
// Iconify Icon在Vue里本地使用(用于内网环境)https://docs.iconify.design/icon-components/vue/offline.html
|
// Iconify Icon在Vue里本地使用(用于内网环境)https://docs.iconify.design/icon-components/vue/offline.html
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "IconifyIconOffline",
|
name: "IconifyIconOffline",
|
||||||
components: { IconifyIcon },
|
components: { IconifyIcon },
|
||||||
props: {
|
props: {
|
||||||
icon: {
|
icon: {
|
||||||
type: String,
|
default: null
|
||||||
default: ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
|
if (typeof this.icon === "object") addIcon(this.icon, this.icon);
|
||||||
const attrs = this.$attrs;
|
const attrs = this.$attrs;
|
||||||
return h(
|
return h(
|
||||||
IconifyIcon,
|
IconifyIcon,
|
||||||
{
|
{
|
||||||
icon: `${this.icon}`,
|
icon: this.icon,
|
||||||
style: attrs?.style
|
style: attrs?.style
|
||||||
? Object.assign(attrs.style, { outline: "none" })
|
? Object.assign(attrs.style, { outline: "none" })
|
||||||
: { outline: "none" },
|
: { outline: "none" },
|
||||||
|
|||||||
14
src/components/ReIcon/src/offlineIcon.ts
Normal file
14
src/components/ReIcon/src/offlineIcon.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { addIcon } from "@iconify/vue/dist/offline";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 本地菜单图标,后端在路由的icon中返回对应的图标字符串并且前端在此处使用addIcon添加即可渲染菜单图标
|
||||||
|
import HomeFilled from "@iconify-icons/ep/home-filled";
|
||||||
|
import InformationLine from "@iconify-icons/ri/information-line";
|
||||||
|
import Lollipop from "@iconify-icons/ep/lollipop";
|
||||||
|
|
||||||
|
addIcon("homeFilled", HomeFilled);
|
||||||
|
addIcon("informationLine", InformationLine);
|
||||||
|
addIcon("lollipop", Lollipop);
|
||||||
5
src/components/RePureTableBar/index.ts
Normal file
5
src/components/RePureTableBar/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import pureTableBar from "./src/bar";
|
||||||
|
import { withInstall } from "@pureadmin/utils";
|
||||||
|
|
||||||
|
/** 配合 `@pureadmin/table` 实现快速便捷的表格操作 https://github.com/pure-admin/pure-admin-table */
|
||||||
|
export const PureTableBar = withInstall(pureTableBar);
|
||||||
339
src/components/RePureTableBar/src/bar.tsx
Normal file
339
src/components/RePureTableBar/src/bar.tsx
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||||
|
import { delay, getKeyList, cloneDeep } from "@pureadmin/utils";
|
||||||
|
import { defineComponent, ref, computed, type PropType, nextTick } from "vue";
|
||||||
|
|
||||||
|
import Sortable from "sortablejs";
|
||||||
|
import DragIcon from "./svg/drag.svg?component";
|
||||||
|
import ExpandIcon from "./svg/expand.svg?component";
|
||||||
|
import RefreshIcon from "./svg/refresh.svg?component";
|
||||||
|
import SettingIcon from "./svg/settings.svg?component";
|
||||||
|
import CollapseIcon from "./svg/collapse.svg?component";
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
/** 头部最左边的标题 */
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: "列表"
|
||||||
|
},
|
||||||
|
/** 对于树形表格,如果想启用展开和折叠功能,传入当前表格的ref即可 */
|
||||||
|
tableRef: {
|
||||||
|
type: Object as PropType<any>
|
||||||
|
},
|
||||||
|
/** 需要展示的列 */
|
||||||
|
columns: {
|
||||||
|
type: Array as PropType<TableColumnList>,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "PureTableBar",
|
||||||
|
props,
|
||||||
|
emits: ["refresh"],
|
||||||
|
setup(props, { emit, slots, attrs }) {
|
||||||
|
const buttonRef = ref();
|
||||||
|
const size = ref("default");
|
||||||
|
const isExpandAll = ref(true);
|
||||||
|
const loading = ref(false);
|
||||||
|
const checkAll = ref(true);
|
||||||
|
const isIndeterminate = ref(false);
|
||||||
|
let checkColumnList = getKeyList(cloneDeep(props?.columns), "label");
|
||||||
|
const checkedColumns = ref(checkColumnList);
|
||||||
|
const dynamicColumns = ref(cloneDeep(props?.columns));
|
||||||
|
|
||||||
|
const getDropdownItemStyle = computed(() => {
|
||||||
|
return s => {
|
||||||
|
return {
|
||||||
|
background:
|
||||||
|
s === size.value ? useEpThemeStoreHook().epThemeColor : "",
|
||||||
|
color: s === size.value ? "#fff" : "var(--el-text-color-primary)"
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const iconClass = computed(() => {
|
||||||
|
return [
|
||||||
|
"text-black",
|
||||||
|
"dark:text-white",
|
||||||
|
"duration-100",
|
||||||
|
"hover:!text-primary",
|
||||||
|
"cursor-pointer",
|
||||||
|
"outline-none"
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const topClass = computed(() => {
|
||||||
|
return [
|
||||||
|
"flex",
|
||||||
|
"justify-between",
|
||||||
|
"pt-[3px]",
|
||||||
|
"px-[11px]",
|
||||||
|
"border-b-[1px]",
|
||||||
|
"border-solid",
|
||||||
|
"border-[#dcdfe6]",
|
||||||
|
"dark:border-[#303030]"
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
function onReFresh() {
|
||||||
|
loading.value = true;
|
||||||
|
emit("refresh");
|
||||||
|
delay(500).then(() => (loading.value = false));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onExpand() {
|
||||||
|
isExpandAll.value = !isExpandAll.value;
|
||||||
|
toggleRowExpansionAll(props.tableRef.data, isExpandAll.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRowExpansionAll(data, isExpansion) {
|
||||||
|
data.forEach(item => {
|
||||||
|
props.tableRef.toggleRowExpansion(item, isExpansion);
|
||||||
|
if (item.children !== undefined && item.children !== null) {
|
||||||
|
toggleRowExpansionAll(item.children, isExpansion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckAllChange(val: boolean) {
|
||||||
|
checkedColumns.value = val ? checkColumnList : [];
|
||||||
|
isIndeterminate.value = false;
|
||||||
|
dynamicColumns.value.map(column =>
|
||||||
|
val ? (column.hide = false) : (column.hide = true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckedColumnsChange(value: string[]) {
|
||||||
|
const checkedCount = value.length;
|
||||||
|
checkAll.value = checkedCount === checkColumnList.length;
|
||||||
|
isIndeterminate.value =
|
||||||
|
checkedCount > 0 && checkedCount < checkColumnList.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCheckColumnListChange(val: boolean, label: string) {
|
||||||
|
dynamicColumns.value.filter(item => item.label === label)[0].hide = !val;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onReset() {
|
||||||
|
checkAll.value = true;
|
||||||
|
isIndeterminate.value = false;
|
||||||
|
dynamicColumns.value = cloneDeep(props?.columns);
|
||||||
|
checkColumnList = [];
|
||||||
|
checkColumnList = await getKeyList(cloneDeep(props?.columns), "label");
|
||||||
|
checkedColumns.value = checkColumnList;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropdown = {
|
||||||
|
dropdown: () => (
|
||||||
|
<el-dropdown-menu class="translation">
|
||||||
|
<el-dropdown-item
|
||||||
|
style={getDropdownItemStyle.value("large")}
|
||||||
|
onClick={() => (size.value = "large")}
|
||||||
|
>
|
||||||
|
宽松
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
style={getDropdownItemStyle.value("default")}
|
||||||
|
onClick={() => (size.value = "default")}
|
||||||
|
>
|
||||||
|
默认
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
style={getDropdownItemStyle.value("small")}
|
||||||
|
onClick={() => (size.value = "small")}
|
||||||
|
>
|
||||||
|
紧凑
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 列展示拖拽排序 */
|
||||||
|
const rowDrop = (event: { preventDefault: () => void }) => {
|
||||||
|
event.preventDefault();
|
||||||
|
nextTick(() => {
|
||||||
|
const wrapper: HTMLElement = document.querySelector(
|
||||||
|
".el-checkbox-group>div"
|
||||||
|
);
|
||||||
|
Sortable.create(wrapper, {
|
||||||
|
animation: 300,
|
||||||
|
handle: ".drag-btn",
|
||||||
|
onEnd: ({ newIndex, oldIndex, item }) => {
|
||||||
|
const targetThElem = item;
|
||||||
|
const wrapperElem = targetThElem.parentNode as HTMLElement;
|
||||||
|
const oldColumn = dynamicColumns.value[oldIndex];
|
||||||
|
const newColumn = dynamicColumns.value[newIndex];
|
||||||
|
if (oldColumn?.fixed || newColumn?.fixed) {
|
||||||
|
// 当前列存在fixed属性 则不可拖拽
|
||||||
|
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
|
||||||
|
if (newIndex > oldIndex) {
|
||||||
|
wrapperElem.insertBefore(targetThElem, oldThElem);
|
||||||
|
} else {
|
||||||
|
wrapperElem.insertBefore(
|
||||||
|
targetThElem,
|
||||||
|
oldThElem ? oldThElem.nextElementSibling : oldThElem
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
|
||||||
|
dynamicColumns.value.splice(newIndex, 0, currentRow);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFixedColumn = (label: string) => {
|
||||||
|
return dynamicColumns.value.filter(item => item.label === label)[0].fixed
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reference = {
|
||||||
|
reference: () => (
|
||||||
|
<SettingIcon
|
||||||
|
class={["w-[16px]", iconClass.value]}
|
||||||
|
onMouseover={e => (buttonRef.value = e.currentTarget)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<>
|
||||||
|
<div {...attrs} class="w-[99/100] mt-6 p-2 bg-bg_color">
|
||||||
|
<div class="flex justify-between w-full h-[60px] p-4">
|
||||||
|
<p class="font-bold truncate">{props.title}</p>
|
||||||
|
<div class="flex items-center justify-around">
|
||||||
|
{slots?.buttons ? (
|
||||||
|
<div class="flex mr-4">{slots.buttons()}</div>
|
||||||
|
) : null}
|
||||||
|
{props.tableRef?.size ? (
|
||||||
|
<>
|
||||||
|
<el-tooltip
|
||||||
|
effect="dark"
|
||||||
|
content={isExpandAll.value ? "折叠" : "展开"}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<ExpandIcon
|
||||||
|
class={["w-[16px]", iconClass.value]}
|
||||||
|
style={{
|
||||||
|
transform: isExpandAll.value ? "none" : "rotate(-90deg)"
|
||||||
|
}}
|
||||||
|
onClick={() => onExpand()}
|
||||||
|
/>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<el-tooltip effect="dark" content="刷新" placement="top">
|
||||||
|
<RefreshIcon
|
||||||
|
class={[
|
||||||
|
"w-[16px]",
|
||||||
|
iconClass.value,
|
||||||
|
loading.value ? "animate-spin" : ""
|
||||||
|
]}
|
||||||
|
onClick={() => onReFresh()}
|
||||||
|
/>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
<el-tooltip effect="dark" content="密度" placement="top">
|
||||||
|
<el-dropdown v-slots={dropdown} trigger="click">
|
||||||
|
<CollapseIcon class={["w-[16px]", iconClass.value]} />
|
||||||
|
</el-dropdown>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-divider direction="vertical" />
|
||||||
|
|
||||||
|
<el-popover
|
||||||
|
v-slots={reference}
|
||||||
|
popper-style={{ padding: 0 }}
|
||||||
|
width="160"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<div class={[topClass.value]}>
|
||||||
|
<el-checkbox
|
||||||
|
class="!-mr-1"
|
||||||
|
label="列展示"
|
||||||
|
v-model={checkAll.value}
|
||||||
|
indeterminate={isIndeterminate.value}
|
||||||
|
onChange={value => handleCheckAllChange(value)}
|
||||||
|
/>
|
||||||
|
<el-button type="primary" link onClick={() => onReset()}>
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pt-[6px] pl-[11px]">
|
||||||
|
<el-checkbox-group
|
||||||
|
v-model={checkedColumns.value}
|
||||||
|
onChange={value => handleCheckedColumnsChange(value)}
|
||||||
|
>
|
||||||
|
<el-space
|
||||||
|
direction="vertical"
|
||||||
|
alignment="flex-start"
|
||||||
|
size={0}
|
||||||
|
>
|
||||||
|
{checkColumnList.map(item => {
|
||||||
|
return (
|
||||||
|
<div class="flex items-center">
|
||||||
|
<DragIcon
|
||||||
|
class={[
|
||||||
|
"drag-btn w-[16px] mr-2",
|
||||||
|
isFixedColumn(item)
|
||||||
|
? "!cursor-no-drop"
|
||||||
|
: "!cursor-grab"
|
||||||
|
]}
|
||||||
|
onMouseenter={(event: {
|
||||||
|
preventDefault: () => void;
|
||||||
|
}) => rowDrop(event)}
|
||||||
|
/>
|
||||||
|
<el-checkbox
|
||||||
|
key={item}
|
||||||
|
label={item}
|
||||||
|
onChange={value =>
|
||||||
|
handleCheckColumnListChange(value, item)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
title={item}
|
||||||
|
class="inline-block w-[120px] truncate hover:text-text_color_primary"
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</el-space>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-tooltip
|
||||||
|
popper-options={{
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: "computeStyles",
|
||||||
|
options: {
|
||||||
|
adaptive: false,
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}}
|
||||||
|
placement="top"
|
||||||
|
virtual-ref={buttonRef.value}
|
||||||
|
virtual-triggering
|
||||||
|
trigger="hover"
|
||||||
|
content="列设置"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{slots.default({
|
||||||
|
size: size.value,
|
||||||
|
dynamicColumns: dynamicColumns.value
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
1
src/components/RePureTableBar/src/svg/collapse.svg
Normal file
1
src/components/RePureTableBar/src/svg/collapse.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.79 10.21a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42l-2.5-2.5a1 1 0 0 0-.33-.21 1 1 0 0 0-.76 0 1 1 0 0 0-.33.21l-2.5 2.5a1 1 0 0 0 1.42 1.42l.79-.8v5.18l-.79-.8a1 1 0 0 0-1.42 1.42l2.5 2.5a1 1 0 0 0 .33.21.94.94 0 0 0 .76 0 1 1 0 0 0 .33-.21l2.5-2.5a1 1 0 0 0-1.42-1.42l-.79.8V9.41ZM7 4h10a1 1 0 0 0 0-2H7a1 1 0 0 0 0 2Zm10 16H7a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 441 B |
1
src/components/RePureTableBar/src/svg/drag.svg
Normal file
1
src/components/RePureTableBar/src/svg/drag.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zm0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0zM300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0z"/></svg>
|
||||||
|
After Width: | Height: | Size: 398 B |
1
src/components/RePureTableBar/src/svg/expand.svg
Normal file
1
src/components/RePureTableBar/src/svg/expand.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4h9Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 163 B |
1
src/components/RePureTableBar/src/svg/refresh.svg
Normal file
1
src/components/RePureTableBar/src/svg/refresh.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"/></svg>
|
||||||
|
After Width: | Height: | Size: 235 B |
1
src/components/RePureTableBar/src/svg/settings.svg
Normal file
1
src/components/RePureTableBar/src/svg/settings.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M3.34 17a10.018 10.018 0 0 1-.978-2.326 3 3 0 0 0 .002-5.347A9.99 9.99 0 0 1 4.865 4.99a3 3 0 0 0 4.631-2.674 9.99 9.99 0 0 1 5.007.002 3 3 0 0 0 4.632 2.672A9.99 9.99 0 0 1 20.66 7c.433.749.757 1.53.978 2.326a3 3 0 0 0-.002 5.347 9.99 9.99 0 0 1-2.501 4.337 3 3 0 0 0-4.631 2.674 9.99 9.99 0 0 1-5.007-.002 3 3 0 0 0-4.632-2.672A10.018 10.018 0 0 1 3.34 17zm5.66.196a4.993 4.993 0 0 1 2.25 2.77c.499.047 1 .048 1.499.001A4.993 4.993 0 0 1 15 17.197a4.993 4.993 0 0 1 3.525-.565c.29-.408.54-.843.748-1.298A4.993 4.993 0 0 1 18 12c0-1.26.47-2.437 1.273-3.334a8.126 8.126 0 0 0-.75-1.298A4.993 4.993 0 0 1 15 6.804a4.993 4.993 0 0 1-2.25-2.77c-.499-.047-1-.048-1.499-.001A4.993 4.993 0 0 1 9 6.803a4.993 4.993 0 0 1-3.525.565 7.99 7.99 0 0 0-.748 1.298A4.993 4.993 0 0 1 6 12a4.99 4.99 0 0 1-1.273 3.334 8.126 8.126 0 0 0 .75 1.298A4.993 4.993 0 0 1 9 17.196zM12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1011 B |
@@ -1,9 +1,8 @@
|
|||||||
import { App } from "vue";
|
import { App } from "vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { loadEnv } from "@build/index";
|
|
||||||
|
|
||||||
let config: object = {};
|
let config: object = {};
|
||||||
const { VITE_PUBLIC_PATH } = loadEnv();
|
const { VITE_PUBLIC_PATH } = import.meta.env;
|
||||||
|
|
||||||
const setConfig = (cfg?: unknown) => {
|
const setConfig = (cfg?: unknown) => {
|
||||||
config = Object.assign(config, cfg);
|
config = Object.assign(config, cfg);
|
||||||
@@ -50,4 +49,7 @@ export const getServerConfig = async (app: App): Promise<undefined> => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getConfig, setConfig };
|
/** 本地响应式存储的命名空间 */
|
||||||
|
const responsiveStorageNameSpace = () => getConfig().ResponsiveStorageNameSpace;
|
||||||
|
|
||||||
|
export { getConfig, setConfig, responsiveStorageNameSpace };
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const auth: Directive = {
|
|||||||
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
const { value } = binding;
|
const { value } = binding;
|
||||||
if (value) {
|
if (value) {
|
||||||
!hasAuth(value) && el.parentNode.removeChild(el);
|
!hasAuth(value) && el.parentNode?.removeChild(el);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("need auths! Like v-auth=\"['btn.add','btn.edit']\"");
|
throw new Error("need auths! Like v-auth=\"['btn.add','btn.edit']\"");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,16 +130,16 @@ const transitionMain = defineComponent({
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.app-main {
|
.app-main {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: relative;
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-main-nofixed-header {
|
.app-main-nofixed-header {
|
||||||
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import mixNav from "./sidebar/mixNav.vue";
|
|||||||
import { useNav } from "@/layout/hooks/useNav";
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
import Breadcrumb from "./sidebar/breadCrumb.vue";
|
import Breadcrumb from "./sidebar/breadCrumb.vue";
|
||||||
import topCollapse from "./sidebar/topCollapse.vue";
|
import topCollapse from "./sidebar/topCollapse.vue";
|
||||||
|
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||||
|
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
layout,
|
layout,
|
||||||
@@ -54,7 +56,7 @@ const {
|
|||||||
<el-dropdown-menu class="logout">
|
<el-dropdown-menu class="logout">
|
||||||
<el-dropdown-item @click="logout">
|
<el-dropdown-item @click="logout">
|
||||||
<IconifyIconOffline
|
<IconifyIconOffline
|
||||||
icon="logout-circle-r-line"
|
:icon="LogoutCircleRLine"
|
||||||
style="margin: 5px"
|
style="margin: 5px"
|
||||||
/>
|
/>
|
||||||
退出系统
|
退出系统
|
||||||
@@ -67,7 +69,7 @@ const {
|
|||||||
title="打开项目配置"
|
title="打开项目配置"
|
||||||
@click="onPanel"
|
@click="onPanel"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline icon="setting" />
|
<IconifyIconOffline :icon="Setting" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,28 +82,28 @@ const {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.hamburger-container {
|
.hamburger-container {
|
||||||
line-height: 48px;
|
|
||||||
height: 100%;
|
|
||||||
float: left;
|
float: left;
|
||||||
|
height: 100%;
|
||||||
|
line-height: 48px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vertical-header-right {
|
.vertical-header-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
min-width: 280px;
|
min-width: 280px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
align-items: center;
|
|
||||||
color: #000000d9;
|
color: #000000d9;
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
.el-dropdown-link {
|
.el-dropdown-link {
|
||||||
height: 48px;
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
cursor: pointer;
|
height: 48px;
|
||||||
|
padding: 10px;
|
||||||
color: #000000d9;
|
color: #000000d9;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -125,9 +127,9 @@ const {
|
|||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
|
|
||||||
::v-deep(.el-dropdown-menu__item) {
|
::v-deep(.el-dropdown-menu__item) {
|
||||||
min-width: 100%;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { noticesData } from "./data";
|
import { noticesData } from "./data";
|
||||||
import NoticeList from "./noticeList.vue";
|
import NoticeList from "./noticeList.vue";
|
||||||
|
import Bell from "@iconify-icons/ep/bell";
|
||||||
|
|
||||||
const noticesNum = ref(0);
|
const noticesNum = ref(0);
|
||||||
const notices = ref(noticesData);
|
const notices = ref(noticesData);
|
||||||
@@ -15,7 +16,7 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
|||||||
<span class="dropdown-badge navbar-bg-hover select-none">
|
<span class="dropdown-badge navbar-bg-hover select-none">
|
||||||
<el-badge :value="noticesNum" :max="99">
|
<el-badge :value="noticesNum" :max="99">
|
||||||
<span class="header-notice-icon">
|
<span class="header-notice-icon">
|
||||||
<IconifyIconOffline icon="bell" />
|
<IconifyIconOffline :icon="Bell" />
|
||||||
</span>
|
</span>
|
||||||
</el-badge>
|
</el-badge>
|
||||||
</span>
|
</span>
|
||||||
@@ -45,8 +46,8 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 48px;
|
|
||||||
width: 60px;
|
width: 60px;
|
||||||
|
height: 48px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
.header-notice-icon {
|
.header-notice-icon {
|
||||||
@@ -58,7 +59,7 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
|||||||
width: 330px;
|
width: 330px;
|
||||||
|
|
||||||
.noticeList-container {
|
.noticeList-container {
|
||||||
padding: 15px 24px 0 24px;
|
padding: 15px 24px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-tabs__header) {
|
:deep(.el-tabs__header) {
|
||||||
@@ -69,14 +70,8 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
|||||||
height: 1px;
|
height: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果上面的 notices 长度大于 3 请注释掉下面代码
|
|
||||||
:deep(.el-tabs__nav-wrap) {
|
:deep(.el-tabs__nav-wrap) {
|
||||||
padding: 0 36px 0 36px;
|
padding: 0 36px;
|
||||||
}
|
|
||||||
|
|
||||||
// 如果上面的 notices 长度大于 3 请注释掉下面代码
|
|
||||||
:deep(.el-tabs__active-bar) {
|
|
||||||
margin: 0 36px 0 36px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ListItem } from "./data";
|
import { ListItem } from "./data";
|
||||||
import { ref, PropType, nextTick } from "vue";
|
import { ref, PropType, nextTick } from "vue";
|
||||||
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
|
import { deviceDetection } from "@pureadmin/utils";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
noticeItem: {
|
noticeItem: {
|
||||||
@@ -13,6 +15,8 @@ const titleRef = ref(null);
|
|||||||
const titleTooltip = ref(false);
|
const titleTooltip = ref(false);
|
||||||
const descriptionRef = ref(null);
|
const descriptionRef = ref(null);
|
||||||
const descriptionTooltip = ref(false);
|
const descriptionTooltip = ref(false);
|
||||||
|
const { tooltipEffect } = useNav();
|
||||||
|
const isMobile = deviceDetection();
|
||||||
|
|
||||||
function hoverTitle() {
|
function hoverTitle() {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -57,9 +61,11 @@ function hoverDescription(event, description) {
|
|||||||
<div class="notice-text-title text-[#000000d9] dark:text-white">
|
<div class="notice-text-title text-[#000000d9] dark:text-white">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
popper-class="notice-title-popper"
|
popper-class="notice-title-popper"
|
||||||
|
:effect="tooltipEffect"
|
||||||
:disabled="!titleTooltip"
|
:disabled="!titleTooltip"
|
||||||
:content="props.noticeItem.title"
|
:content="props.noticeItem.title"
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
|
:enterable="!isMobile"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref="titleRef"
|
ref="titleRef"
|
||||||
@@ -81,6 +87,7 @@ function hoverDescription(event, description) {
|
|||||||
|
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
popper-class="notice-title-popper"
|
popper-class="notice-title-popper"
|
||||||
|
:effect="tooltipEffect"
|
||||||
:disabled="!descriptionTooltip"
|
:disabled="!descriptionTooltip"
|
||||||
:content="props.noticeItem.description"
|
:content="props.noticeItem.description"
|
||||||
placement="top-start"
|
placement="top-start"
|
||||||
@@ -111,6 +118,7 @@ function hoverDescription(event, description) {
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
|
|
||||||
// border-bottom: 1px solid #f0f0f0;
|
// border-bottom: 1px solid #f0f0f0;
|
||||||
|
|
||||||
.notice-container-avatar {
|
.notice-container-avatar {
|
||||||
@@ -120,15 +128,15 @@ function hoverDescription(event, description) {
|
|||||||
|
|
||||||
.notice-container-text {
|
.notice-container-text {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.notice-text-title {
|
.notice-text-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-weight: 400;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
line-height: 1.5715;
|
line-height: 1.5715;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
@@ -136,8 +144,8 @@ function hoverDescription(event, description) {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notice-title-extra {
|
.notice-title-extra {
|
||||||
@@ -155,8 +163,8 @@ function hoverDescription(event, description) {
|
|||||||
|
|
||||||
.notice-text-description {
|
.notice-text-description {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { onClickOutside } from "@vueuse/core";
|
|
||||||
import { emitter } from "@/utils/mitt";
|
import { emitter } from "@/utils/mitt";
|
||||||
|
import { onClickOutside } from "@vueuse/core";
|
||||||
|
import Close from "@iconify-icons/ep/close";
|
||||||
|
|
||||||
const show = ref<Boolean>(false);
|
|
||||||
const target = ref(null);
|
const target = ref(null);
|
||||||
|
const show = ref<Boolean>(false);
|
||||||
|
|
||||||
|
const iconClass = computed(() => {
|
||||||
|
return [
|
||||||
|
"mr-[20px]",
|
||||||
|
"outline-none",
|
||||||
|
"width-[20px]",
|
||||||
|
"height-[20px]",
|
||||||
|
"rounded-[4px]",
|
||||||
|
"cursor-pointer",
|
||||||
|
"transition-colors",
|
||||||
|
"hover:bg-[#0000000f]",
|
||||||
|
"dark:hover:bg-[#ffffff1f]",
|
||||||
|
"dark:hover:text-[#ffffffd9]"
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
onClickOutside(target, (event: any) => {
|
onClickOutside(target, (event: any) => {
|
||||||
if (event.clientX > target.value.offsetLeft) return;
|
if (event.clientX > target.value.offsetLeft) return;
|
||||||
show.value = false;
|
show.value = false;
|
||||||
@@ -22,10 +39,12 @@ emitter.on("openPanel", () => {
|
|||||||
<div class="right-panel-items">
|
<div class="right-panel-items">
|
||||||
<div class="project-configuration">
|
<div class="project-configuration">
|
||||||
<h4 class="dark:text-white">项目配置</h4>
|
<h4 class="dark:text-white">项目配置</h4>
|
||||||
<span title="关闭配置">
|
<span title="关闭配置" :class="iconClass">
|
||||||
<IconifyIconOffline
|
<IconifyIconOffline
|
||||||
class="dark:text-white"
|
class="dark:text-white"
|
||||||
icon="close"
|
width="20px"
|
||||||
|
height="20px"
|
||||||
|
:icon="Close"
|
||||||
@click="show = !show"
|
@click="show = !show"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -41,9 +60,9 @@ emitter.on("openPanel", () => {
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.showright-panel {
|
.showright-panel {
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: calc(100% - 15px);
|
width: calc(100% - 15px);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -52,24 +71,23 @@ emitter.on("openPanel", () => {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
background: rgb(0 0 0 / 20%);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
z-index: -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-panel {
|
.right-panel {
|
||||||
width: 100%;
|
|
||||||
max-width: 315px;
|
|
||||||
height: 100vh;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.05);
|
z-index: 40000;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 315px;
|
||||||
|
height: 100vh;
|
||||||
|
box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%);
|
||||||
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
|
||||||
transform: translate(100%);
|
transform: translate(100%);
|
||||||
// background: #fff;
|
|
||||||
z-index: 40000;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.show {
|
.show {
|
||||||
@@ -77,9 +95,9 @@ emitter.on("openPanel", () => {
|
|||||||
|
|
||||||
.right-panel-background {
|
.right-panel-background {
|
||||||
z-index: 20000;
|
z-index: 20000;
|
||||||
opacity: 1;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-panel {
|
.right-panel {
|
||||||
@@ -88,20 +106,20 @@ emitter.on("openPanel", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.handle-button {
|
.handle-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 45%;
|
||||||
|
left: -48px;
|
||||||
|
z-index: 0;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
position: absolute;
|
|
||||||
left: -48px;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
border-radius: 6px 0 0 6px !important;
|
line-height: 48px;
|
||||||
z-index: 0;
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #fff;
|
background: rgb(24 144 255);
|
||||||
line-height: 48px;
|
border-radius: 6px 0 0 6px !important;
|
||||||
top: 45%;
|
|
||||||
background: rgb(24, 144, 255);
|
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@@ -110,34 +128,24 @@ emitter.on("openPanel", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.right-panel-items {
|
.right-panel-items {
|
||||||
margin-top: 60px;
|
|
||||||
height: calc(100vh - 60px);
|
height: calc(100vh - 60px);
|
||||||
|
margin-top: 60px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-configuration {
|
.project-configuration {
|
||||||
|
position: fixed;
|
||||||
|
top: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
position: fixed;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
top: 15px;
|
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
|
||||||
svg {
|
|
||||||
font-size: 20px;
|
|
||||||
margin-right: 20px;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--el-color-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-divider--horizontal) {
|
:deep(.el-divider--horizontal) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
margin: 20px auto 0 auto;
|
margin: 20px auto 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useFullscreen } from "@vueuse/core";
|
|
||||||
|
|
||||||
const { isFullscreen, toggle } = useFullscreen();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
class="screen-full w-[36px] h-[48px] flex-ac cursor-pointer navbar-bg-hover"
|
|
||||||
@click="toggle"
|
|
||||||
>
|
|
||||||
<FontIcon
|
|
||||||
:title="isFullscreen ? '退出全屏' : '全屏'"
|
|
||||||
:icon="isFullscreen ? 'team-iconexit-fullscreen' : 'team-iconfullscreen'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -5,8 +5,8 @@
|
|||||||
确认
|
确认
|
||||||
</span>
|
</span>
|
||||||
<span class="search-footer-item">
|
<span class="search-footer-item">
|
||||||
<IconifyIconOffline icon="arrow-up-line" class="icon" />
|
<IconifyIconOffline :icon="ArrowUpLine" class="icon" />
|
||||||
<IconifyIconOffline icon="arrow-down-line" class="icon" />
|
<IconifyIconOffline :icon="ArrowDownLine" class="icon" />
|
||||||
切换
|
切换
|
||||||
</span>
|
</span>
|
||||||
<span class="search-footer-item">
|
<span class="search-footer-item">
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
|
||||||
|
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
|
||||||
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
|
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
|
||||||
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { cloneDeep } from "lodash-unified";
|
import { cloneDeep } from "@pureadmin/utils";
|
||||||
import SearchResult from "./SearchResult.vue";
|
import SearchResult from "./SearchResult.vue";
|
||||||
import SearchFooter from "./SearchFooter.vue";
|
import SearchFooter from "./SearchFooter.vue";
|
||||||
import { deleteChildren } from "@/utils/tree";
|
|
||||||
import { useNav } from "@/layout/hooks/useNav";
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
|
import { ref, computed, shallowRef } from "vue";
|
||||||
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
|
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
|
||||||
import { ref, watch, computed, nextTick, shallowRef } from "vue";
|
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||||
|
import Search from "@iconify-icons/ep/search";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** 弹窗显隐 */
|
/** 弹窗显隐 */
|
||||||
@@ -31,7 +31,7 @@ const handleSearch = useDebounceFn(search, 300);
|
|||||||
|
|
||||||
/** 菜单树形结构 */
|
/** 菜单树形结构 */
|
||||||
const menusData = computed(() => {
|
const menusData = computed(() => {
|
||||||
return deleteChildren(cloneDeep(usePermissionStoreHook().wholeMenus));
|
return cloneDeep(usePermissionStoreHook().wholeMenus);
|
||||||
});
|
});
|
||||||
|
|
||||||
const show = computed({
|
const show = computed({
|
||||||
@@ -43,14 +43,6 @@ const show = computed({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(show, async val => {
|
|
||||||
if (val) {
|
|
||||||
/** 自动聚焦 */
|
|
||||||
await nextTick();
|
|
||||||
inputRef.value?.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
|
/** 将菜单树形结构扁平化为一维数组,用于菜单查询 */
|
||||||
function flatTree(arr) {
|
function flatTree(arr) {
|
||||||
const res = [];
|
const res = [];
|
||||||
@@ -134,9 +126,11 @@ onKeyStroke("ArrowDown", handleDown);
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
top="5vh"
|
top="5vh"
|
||||||
:width="device === 'mobile' ? '80vw' : '50vw'"
|
|
||||||
v-model="show"
|
v-model="show"
|
||||||
|
:width="device === 'mobile' ? '80vw' : '50vw'"
|
||||||
:before-close="handleClose"
|
:before-close="handleClose"
|
||||||
|
@opened="inputRef.focus()"
|
||||||
|
@closed="inputRef.blur()"
|
||||||
>
|
>
|
||||||
<el-input
|
<el-input
|
||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
@@ -147,7 +141,7 @@ onKeyStroke("ArrowDown", handleDown);
|
|||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<span class="el-input__icon">
|
<span class="el-input__icon">
|
||||||
<IconifyIconOffline icon="search" />
|
<IconifyIconOffline :icon="Search" />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { computed } from "vue";
|
|||||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||||
|
import Bookmark2Line from "@iconify-icons/ri/bookmark-2-line";
|
||||||
|
|
||||||
interface optionsItem {
|
interface optionsItem {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -64,7 +65,7 @@ function handleTo() {
|
|||||||
@click="handleTo"
|
@click="handleTo"
|
||||||
@mouseenter="handleMouse(item)"
|
@mouseenter="handleMouse(item)"
|
||||||
>
|
>
|
||||||
<component :is="useRenderIcon(item.meta?.icon ?? 'bookmark-2-line')" />
|
<component :is="useRenderIcon(item.meta?.icon ?? Bookmark2Line)" />
|
||||||
<span class="result-item-title">{{ item.meta?.title }}</span>
|
<span class="result-item-title">{{ item.meta?.title }}</span>
|
||||||
<enterOutlined />
|
<enterOutlined />
|
||||||
</div>
|
</div>
|
||||||
@@ -80,11 +81,11 @@ function handleTo() {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
margin-top: 8px;
|
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
border-radius: 4px;
|
margin-top: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 0.1px solid #ccc;
|
border: 0.1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SearchModal } from "./components";
|
import { SearchModal } from "./components";
|
||||||
import { useBoolean } from "../../hooks/useBoolean";
|
import { useBoolean } from "../../hooks/useBoolean";
|
||||||
|
import Search from "@iconify-icons/ep/search";
|
||||||
|
|
||||||
const { bool: show, toggle } = useBoolean();
|
const { bool: show, toggle } = useBoolean();
|
||||||
function handleSearch() {
|
function handleSearch() {
|
||||||
toggle();
|
toggle();
|
||||||
@@ -12,7 +14,7 @@ function handleSearch() {
|
|||||||
class="search-container w-[40px] h-[48px] flex-c cursor-pointer navbar-bg-hover"
|
class="search-container w-[40px] h-[48px] flex-c cursor-pointer navbar-bg-hover"
|
||||||
@click="handleSearch"
|
@click="handleSearch"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline icon="search" />
|
<IconifyIconOffline :icon="Search" />
|
||||||
</div>
|
</div>
|
||||||
<SearchModal v-model:value="show" />
|
<SearchModal v-model:value="show" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,8 +6,15 @@ import {
|
|||||||
reactive,
|
reactive,
|
||||||
computed,
|
computed,
|
||||||
nextTick,
|
nextTick,
|
||||||
useCssModule
|
onBeforeMount
|
||||||
} from "vue";
|
} from "vue";
|
||||||
|
import {
|
||||||
|
useDark,
|
||||||
|
debounce,
|
||||||
|
useGlobal,
|
||||||
|
storageLocal,
|
||||||
|
storageSession
|
||||||
|
} from "@pureadmin/utils";
|
||||||
import { getConfig } from "@/config";
|
import { getConfig } from "@/config";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import panel from "../panel/index.vue";
|
import panel from "../panel/index.vue";
|
||||||
@@ -17,24 +24,18 @@ import { removeToken } from "@/utils/auth";
|
|||||||
import { routerArrays } from "@/layout/types";
|
import { routerArrays } from "@/layout/types";
|
||||||
import { useNav } from "@/layout/hooks/useNav";
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
import { useAppStoreHook } from "@/store/modules/app";
|
import { useAppStoreHook } from "@/store/modules/app";
|
||||||
|
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
|
||||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||||
import {
|
|
||||||
useDark,
|
|
||||||
debounce,
|
|
||||||
useGlobal,
|
|
||||||
storageLocal,
|
|
||||||
storageSession
|
|
||||||
} from "@pureadmin/utils";
|
|
||||||
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
|
|
||||||
|
|
||||||
import dayIcon from "@/assets/svg/day.svg?component";
|
import dayIcon from "@/assets/svg/day.svg?component";
|
||||||
import darkIcon from "@/assets/svg/dark.svg?component";
|
import darkIcon from "@/assets/svg/dark.svg?component";
|
||||||
|
import Check from "@iconify-icons/ep/check";
|
||||||
|
import Logout from "@iconify-icons/ri/logout-circle-r-line";
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { device } = useNav();
|
|
||||||
const { isDark } = useDark();
|
const { isDark } = useDark();
|
||||||
const { isSelect } = useCssModule();
|
const { device, tooltipEffect } = useNav();
|
||||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
const { $storage } = useGlobal<GlobalPropertiesApi>();
|
||||||
|
|
||||||
const mixRef = ref();
|
const mixRef = ref();
|
||||||
@@ -42,7 +43,6 @@ const verticalRef = ref();
|
|||||||
const horizontalRef = ref();
|
const horizontalRef = ref();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
body,
|
|
||||||
dataTheme,
|
dataTheme,
|
||||||
layoutTheme,
|
layoutTheme,
|
||||||
themeColors,
|
themeColors,
|
||||||
@@ -132,8 +132,8 @@ const multiTagsCacheChange = () => {
|
|||||||
/** 清空缓存并返回登录页 */
|
/** 清空缓存并返回登录页 */
|
||||||
function onReset() {
|
function onReset() {
|
||||||
removeToken();
|
removeToken();
|
||||||
storageLocal.clear();
|
storageLocal().clear();
|
||||||
storageSession.clear();
|
storageSession().clear();
|
||||||
const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
|
const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
|
||||||
useAppStoreHook().setLayout(Layout);
|
useAppStoreHook().setLayout(Layout);
|
||||||
setEpThemeColor(EpThemeColor);
|
setEpThemeColor(EpThemeColor);
|
||||||
@@ -160,32 +160,10 @@ function logoChange() {
|
|||||||
|
|
||||||
function setFalse(Doms): any {
|
function setFalse(Doms): any {
|
||||||
Doms.forEach(v => {
|
Doms.forEach(v => {
|
||||||
toggleClass(false, isSelect, unref(v));
|
toggleClass(false, "is-select", unref(v));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
watch($storage, ({ layout }) => {
|
|
||||||
/* 设置wangeditorV5主题色 */
|
|
||||||
body.style.setProperty("--w-e-toolbar-active-color", layout["epThemeColor"]);
|
|
||||||
switch (layout["layout"]) {
|
|
||||||
case "vertical":
|
|
||||||
toggleClass(true, isSelect, unref(verticalRef));
|
|
||||||
debounce(setFalse([horizontalRef]), 50);
|
|
||||||
debounce(setFalse([mixRef]), 50);
|
|
||||||
break;
|
|
||||||
case "horizontal":
|
|
||||||
toggleClass(true, isSelect, unref(horizontalRef));
|
|
||||||
debounce(setFalse([verticalRef]), 50);
|
|
||||||
debounce(setFalse([mixRef]), 50);
|
|
||||||
break;
|
|
||||||
case "mix":
|
|
||||||
toggleClass(true, isSelect, unref(mixRef));
|
|
||||||
debounce(setFalse([verticalRef]), 50);
|
|
||||||
debounce(setFalse([horizontalRef]), 50);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 主题色 激活选择项 */
|
/** 主题色 激活选择项 */
|
||||||
const getThemeColor = computed(() => {
|
const getThemeColor = computed(() => {
|
||||||
return current => {
|
return current => {
|
||||||
@@ -219,14 +197,36 @@ function setLayoutModel(layout: string) {
|
|||||||
useAppStoreHook().setLayout(layout);
|
useAppStoreHook().setLayout(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 初始化项目配置 */
|
watch($storage, ({ layout }) => {
|
||||||
nextTick(() => {
|
switch (layout["layout"]) {
|
||||||
settings.greyVal &&
|
case "vertical":
|
||||||
document.querySelector("html")?.setAttribute("class", "html-grey");
|
toggleClass(true, "is-select", unref(verticalRef));
|
||||||
settings.weakVal &&
|
debounce(setFalse([horizontalRef]), 50);
|
||||||
document.querySelector("html")?.setAttribute("class", "html-weakness");
|
debounce(setFalse([mixRef]), 50);
|
||||||
settings.tabsVal && tagsChange();
|
break;
|
||||||
|
case "horizontal":
|
||||||
|
toggleClass(true, "is-select", unref(horizontalRef));
|
||||||
|
debounce(setFalse([verticalRef]), 50);
|
||||||
|
debounce(setFalse([mixRef]), 50);
|
||||||
|
break;
|
||||||
|
case "mix":
|
||||||
|
toggleClass(true, "is-select", unref(mixRef));
|
||||||
|
debounce(setFalse([verticalRef]), 50);
|
||||||
|
debounce(setFalse([horizontalRef]), 50);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
dataThemeChange();
|
dataThemeChange();
|
||||||
|
/* 初始化项目配置 */
|
||||||
|
nextTick(() => {
|
||||||
|
settings.greyVal &&
|
||||||
|
document.querySelector("html")?.setAttribute("class", "html-grey");
|
||||||
|
settings.weakVal &&
|
||||||
|
document.querySelector("html")?.setAttribute("class", "html-weakness");
|
||||||
|
settings.tabsVal && tagsChange();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -244,9 +244,15 @@ nextTick(() => {
|
|||||||
|
|
||||||
<el-divider>导航栏模式</el-divider>
|
<el-divider>导航栏模式</el-divider>
|
||||||
<ul class="pure-theme">
|
<ul class="pure-theme">
|
||||||
<el-tooltip class="item" content="左侧模式" placement="bottom">
|
<el-tooltip
|
||||||
|
:effect="tooltipEffect"
|
||||||
|
class="item"
|
||||||
|
content="左侧模式"
|
||||||
|
placement="bottom"
|
||||||
|
popper-class="pure-tooltip"
|
||||||
|
>
|
||||||
<li
|
<li
|
||||||
:class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''"
|
:class="layoutTheme.layout === 'vertical' ? 'is-select' : ''"
|
||||||
ref="verticalRef"
|
ref="verticalRef"
|
||||||
@click="setLayoutModel('vertical')"
|
@click="setLayoutModel('vertical')"
|
||||||
>
|
>
|
||||||
@@ -257,12 +263,14 @@ nextTick(() => {
|
|||||||
|
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="device !== 'mobile'"
|
v-if="device !== 'mobile'"
|
||||||
|
:effect="tooltipEffect"
|
||||||
class="item"
|
class="item"
|
||||||
content="顶部模式"
|
content="顶部模式"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
|
popper-class="pure-tooltip"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
:class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''"
|
:class="layoutTheme.layout === 'horizontal' ? 'is-select' : ''"
|
||||||
ref="horizontalRef"
|
ref="horizontalRef"
|
||||||
@click="setLayoutModel('horizontal')"
|
@click="setLayoutModel('horizontal')"
|
||||||
>
|
>
|
||||||
@@ -273,12 +281,14 @@ nextTick(() => {
|
|||||||
|
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
v-if="device !== 'mobile'"
|
v-if="device !== 'mobile'"
|
||||||
|
:effect="tooltipEffect"
|
||||||
class="item"
|
class="item"
|
||||||
content="混合模式"
|
content="混合模式"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
|
popper-class="pure-tooltip"
|
||||||
>
|
>
|
||||||
<li
|
<li
|
||||||
:class="layoutTheme.layout === 'mix' ? $style.isSelect : ''"
|
:class="layoutTheme.layout === 'mix' ? 'is-select' : ''"
|
||||||
ref="mixRef"
|
ref="mixRef"
|
||||||
@click="setLayoutModel('mix')"
|
@click="setLayoutModel('mix')"
|
||||||
>
|
>
|
||||||
@@ -302,7 +312,7 @@ nextTick(() => {
|
|||||||
:size="17"
|
:size="17"
|
||||||
:color="getThemeColor(item.themeColor)"
|
:color="getThemeColor(item.themeColor)"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline icon="check" />
|
<IconifyIconOffline :icon="Check" />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -383,7 +393,7 @@ nextTick(() => {
|
|||||||
@click="onReset"
|
@click="onReset"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline
|
<IconifyIconOffline
|
||||||
icon="fa-sign-out"
|
:icon="Logout"
|
||||||
width="15"
|
width="15"
|
||||||
height="15"
|
height="15"
|
||||||
style="margin-right: 4px"
|
style="margin-right: 4px"
|
||||||
@@ -393,52 +403,50 @@ nextTick(() => {
|
|||||||
</panel>
|
</panel>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped module>
|
|
||||||
.isSelect {
|
|
||||||
border: 2px solid var(--el-color-primary);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.setting {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin: 25px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-divider__text) {
|
:deep(.el-divider__text) {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-select {
|
||||||
|
border: 2px solid var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.setting {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.pure-datatheme {
|
.pure-datatheme {
|
||||||
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
text-align: center;
|
|
||||||
display: block;
|
|
||||||
padding-top: 25px;
|
padding-top: 25px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pure-theme {
|
.pure-theme {
|
||||||
margin-top: 25px;
|
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
margin-top: 25px;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
position: relative;
|
||||||
width: 18%;
|
width: 18%;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
background: #f0f2f5;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
background: #f0f2f5;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
|
box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
|
||||||
|
|
||||||
@@ -451,13 +459,13 @@ nextTick(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
width: 70%;
|
position: absolute;
|
||||||
height: 30%;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
width: 70%;
|
||||||
|
height: 30%;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 0 1px #888;
|
box-shadow: 0 0 1px #888;
|
||||||
position: absolute;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,13 +491,13 @@ nextTick(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
width: 30%;
|
position: absolute;
|
||||||
height: 70%;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
width: 30%;
|
||||||
|
height: 70%;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 0 1px #888;
|
box-shadow: 0 0 1px #888;
|
||||||
position: absolute;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -497,11 +505,11 @@ nextTick(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theme-color {
|
.theme-color {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
li {
|
li {
|
||||||
float: left;
|
float: left;
|
||||||
@@ -511,8 +519,8 @@ nextTick(() => {
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 2px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
&:nth-child(2) {
|
&:nth-child(2) {
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
|
|||||||
@@ -11,12 +11,6 @@ const router = useRouter();
|
|||||||
const routes: any = router.options.routes;
|
const routes: any = router.options.routes;
|
||||||
const multiTags: any = useMultiTagsStoreHook().multiTags;
|
const multiTags: any = useMultiTagsStoreHook().multiTags;
|
||||||
|
|
||||||
const isDashboard = (route: RouteLocationMatched): boolean | string => {
|
|
||||||
const name = route && (route.name as string);
|
|
||||||
if (!name) return false;
|
|
||||||
return name.trim().toLocaleLowerCase() === "Welcome".toLocaleLowerCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
const getBreadcrumb = (): void => {
|
const getBreadcrumb = (): void => {
|
||||||
// 当前路由信息
|
// 当前路由信息
|
||||||
let currentRoute;
|
let currentRoute;
|
||||||
@@ -34,28 +28,24 @@ const getBreadcrumb = (): void => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
currentRoute = findRouteByPath(router.currentRoute.value.path, multiTags);
|
currentRoute = findRouteByPath(router.currentRoute.value.path, routes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前路由的父级路径组成的数组
|
// 当前路由的父级路径组成的数组
|
||||||
const parentRoutes = getParentPaths(router.currentRoute.value.path, routes);
|
const parentRoutes = getParentPaths(
|
||||||
|
router.currentRoute.value.name as string,
|
||||||
|
routes,
|
||||||
|
"name"
|
||||||
|
);
|
||||||
// 存放组成面包屑的数组
|
// 存放组成面包屑的数组
|
||||||
let matched = [];
|
const matched = [];
|
||||||
|
|
||||||
// 获取每个父级路径对应的路由信息
|
// 获取每个父级路径对应的路由信息
|
||||||
parentRoutes.forEach(path => {
|
parentRoutes.forEach(path => {
|
||||||
if (path !== "/") matched.push(findRouteByPath(path, routes));
|
if (path !== "/") matched.push(findRouteByPath(path, routes));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentRoute?.path !== "/welcome") matched.push(currentRoute);
|
matched.push(currentRoute);
|
||||||
|
|
||||||
if (!isDashboard(matched[0])) {
|
|
||||||
matched = [
|
|
||||||
{
|
|
||||||
path: "/welcome",
|
|
||||||
parentPath: "/",
|
|
||||||
meta: { title: "首页" }
|
|
||||||
} as unknown as RouteLocationMatched
|
|
||||||
].concat(matched);
|
|
||||||
}
|
|
||||||
|
|
||||||
matched.forEach((item, index) => {
|
matched.forEach((item, index) => {
|
||||||
if (currentRoute?.query || currentRoute?.params) return;
|
if (currentRoute?.query || currentRoute?.params) return;
|
||||||
@@ -90,14 +80,21 @@ watch(
|
|||||||
() => route.path,
|
() => route.path,
|
||||||
() => {
|
() => {
|
||||||
getBreadcrumb();
|
getBreadcrumb();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-breadcrumb class="!leading-[50px] select-none" separator="/">
|
<el-breadcrumb class="!leading-[50px] select-none" separator="/">
|
||||||
<transition-group appear name="breadcrumb">
|
<transition-group name="breadcrumb">
|
||||||
<el-breadcrumb-item v-for="item in levelList" :key="item.path">
|
<el-breadcrumb-item
|
||||||
|
class="!inline !items-stretch"
|
||||||
|
v-for="item in levelList"
|
||||||
|
:key="item.path"
|
||||||
|
>
|
||||||
<a @click.prevent="handleLink(item)">
|
<a @click.prevent="handleLink(item)">
|
||||||
{{ item.meta.title }}
|
{{ item.meta.title }}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
20
src/layout/components/sidebar/extraIcon.vue
Normal file
20
src/layout/components/sidebar/extraIcon.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { toRaw } from "vue";
|
||||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
extraIcon: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="props.extraIcon" class="flex justify-center items-center">
|
||||||
|
<component
|
||||||
|
:is="useRenderIcon(toRaw(props.extraIcon))"
|
||||||
|
class="w-[30px] h-[30px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -5,6 +5,8 @@ import { ref, watch, nextTick } from "vue";
|
|||||||
import SidebarItem from "./sidebarItem.vue";
|
import SidebarItem from "./sidebarItem.vue";
|
||||||
import { useNav } from "@/layout/hooks/useNav";
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||||
|
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||||
|
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||||
|
|
||||||
const menuRef = ref();
|
const menuRef = ref();
|
||||||
|
|
||||||
@@ -13,7 +15,7 @@ const {
|
|||||||
title,
|
title,
|
||||||
routers,
|
routers,
|
||||||
logout,
|
logout,
|
||||||
backHome,
|
backTopMenu,
|
||||||
onPanel,
|
onPanel,
|
||||||
menuSelect,
|
menuSelect,
|
||||||
username,
|
username,
|
||||||
@@ -33,10 +35,13 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="horizontal-header">
|
<div
|
||||||
<div class="horizontal-header-left" @click="backHome">
|
v-loading="usePermissionStoreHook().wholeMenus.length === 0"
|
||||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
class="horizontal-header"
|
||||||
<h4>{{ title }}</h4>
|
>
|
||||||
|
<div class="horizontal-header-left" @click="backTopMenu">
|
||||||
|
<img src="/logo.svg" alt="logo" />
|
||||||
|
<span>{{ title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<el-menu
|
<el-menu
|
||||||
router
|
router
|
||||||
@@ -71,7 +76,7 @@ watch(
|
|||||||
<el-dropdown-menu class="logout">
|
<el-dropdown-menu class="logout">
|
||||||
<el-dropdown-item @click="logout">
|
<el-dropdown-item @click="logout">
|
||||||
<IconifyIconOffline
|
<IconifyIconOffline
|
||||||
icon="logout-circle-r-line"
|
:icon="LogoutCircleRLine"
|
||||||
style="margin: 5px"
|
style="margin: 5px"
|
||||||
/>
|
/>
|
||||||
退出系统
|
退出系统
|
||||||
@@ -84,20 +89,24 @@ watch(
|
|||||||
title="打开项目配置"
|
title="打开项目配置"
|
||||||
@click="onPanel"
|
@click="onPanel"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline icon="setting" />
|
<IconifyIconOffline :icon="Setting" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-loading-mask) {
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
.logout {
|
.logout {
|
||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
|
|
||||||
::v-deep(.el-dropdown-menu__item) {
|
::v-deep(.el-dropdown-menu__item) {
|
||||||
min-width: 100%;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useDark } from "@pureadmin/utils";
|
import { ref, computed } from "vue";
|
||||||
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
|
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
@@ -8,7 +10,25 @@ interface Props {
|
|||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
isActive: false
|
isActive: false
|
||||||
});
|
});
|
||||||
const { isDark } = useDark();
|
|
||||||
|
const visible = ref(false);
|
||||||
|
const { tooltipEffect } = useNav();
|
||||||
|
|
||||||
|
const iconClass = computed(() => {
|
||||||
|
return [
|
||||||
|
"ml-4",
|
||||||
|
"mb-1",
|
||||||
|
"w-[16px]",
|
||||||
|
"h-[16px]",
|
||||||
|
"inline-block",
|
||||||
|
"align-middle",
|
||||||
|
"text-primary",
|
||||||
|
"cursor-pointer",
|
||||||
|
"duration-[100ms]",
|
||||||
|
"hover:text-primary",
|
||||||
|
"dark:hover:!text-white"
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "toggleClick"): void;
|
(e: "toggleClick"): void;
|
||||||
@@ -23,13 +43,17 @@ const toggleClick = () => {
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
placement="right"
|
placement="right"
|
||||||
:effect="isDark ? 'dark' : 'light'"
|
:visible="visible"
|
||||||
|
:effect="tooltipEffect"
|
||||||
:content="props.isActive ? '点击折叠' : '点击展开'"
|
:content="props.isActive ? '点击折叠' : '点击展开'"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline
|
<IconifyIconOffline
|
||||||
:icon="props.isActive ? 'menu-fold' : 'menu-unfold'"
|
:icon="MenuFold"
|
||||||
class="cursor-pointer inline-block align-middle text-primary hover:text-primary dark:hover:!text-white w-[16px] h-[16px] ml-4 mb-1"
|
:class="iconClass"
|
||||||
|
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }"
|
||||||
@click="toggleClick"
|
@click="toggleClick"
|
||||||
|
@mouseenter="visible = true"
|
||||||
|
@mouseleave="visible = false"
|
||||||
/>
|
/>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { getTopMenu } from "@/router/utils";
|
||||||
import { useNav } from "@/layout/hooks/useNav";
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -6,6 +7,7 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { title } = useNav();
|
const { title } = useNav();
|
||||||
|
const topPath = getTopMenu().path;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -16,9 +18,9 @@ const { title } = useNav();
|
|||||||
key="props.collapse"
|
key="props.collapse"
|
||||||
:title="title"
|
:title="title"
|
||||||
class="sidebar-logo-link"
|
class="sidebar-logo-link"
|
||||||
to="/"
|
:to="topPath"
|
||||||
>
|
>
|
||||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
<img src="/logo.svg" alt="logo" />
|
||||||
<span class="sidebar-title">{{ title }}</span>
|
<span class="sidebar-title">{{ title }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link
|
<router-link
|
||||||
@@ -26,9 +28,9 @@ const { title } = useNav();
|
|||||||
key="expand"
|
key="expand"
|
||||||
:title="title"
|
:title="title"
|
||||||
class="sidebar-logo-link"
|
class="sidebar-logo-link"
|
||||||
to="/"
|
:to="topPath"
|
||||||
>
|
>
|
||||||
<FontIcon icon="team-iconlogo" svg style="width: 35px; height: 35px" />
|
<img src="/logo.svg" alt="logo" />
|
||||||
<span class="sidebar-title">{{ title }}</span>
|
<span class="sidebar-title">{{ title }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</transition>
|
</transition>
|
||||||
@@ -40,34 +42,30 @@ const { title } = useNav();
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
text-align: center;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.sidebar-logo-link {
|
.sidebar-logo-link {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
img {
|
||||||
text-overflow: ellipsis;
|
display: inline-block;
|
||||||
margin-top: 5px;
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-title {
|
.sidebar-title {
|
||||||
display: block;
|
display: inline-block;
|
||||||
width: 160px;
|
height: 32px;
|
||||||
|
margin: 2px 0 0 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 32px;
|
||||||
|
color: $subMenuActiveText;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: left;
|
|
||||||
color: #1890ff;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 20px;
|
|
||||||
margin-top: 10px;
|
|
||||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapses {
|
|
||||||
.sidebar-logo {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import extraIcon from "./extraIcon.vue";
|
||||||
import Search from "../search/index.vue";
|
import Search from "../search/index.vue";
|
||||||
import Notice from "../notice/index.vue";
|
import Notice from "../notice/index.vue";
|
||||||
import { useNav } from "@/layout/hooks/useNav";
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
@@ -6,6 +7,8 @@ import { ref, toRaw, watch, onMounted, nextTick } from "vue";
|
|||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
import { getParentPaths, findRouteByPath } from "@/router/utils";
|
import { getParentPaths, findRouteByPath } from "@/router/utils";
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||||
|
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||||
|
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||||
|
|
||||||
const menuRef = ref();
|
const menuRef = ref();
|
||||||
const defaultActive = ref(null);
|
const defaultActive = ref(null);
|
||||||
@@ -19,6 +22,7 @@ const {
|
|||||||
menuSelect,
|
menuSelect,
|
||||||
resolvePath,
|
resolvePath,
|
||||||
username,
|
username,
|
||||||
|
getDivStyle,
|
||||||
avatarsStyle
|
avatarsStyle
|
||||||
} = useNav();
|
} = useNav();
|
||||||
|
|
||||||
@@ -49,7 +53,11 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="device !== 'mobile'" class="horizontal-header">
|
<div
|
||||||
|
v-if="device !== 'mobile'"
|
||||||
|
class="horizontal-header"
|
||||||
|
v-loading="usePermissionStoreHook().wholeMenus.length === 0"
|
||||||
|
>
|
||||||
<el-menu
|
<el-menu
|
||||||
router
|
router
|
||||||
ref="menuRef"
|
ref="menuRef"
|
||||||
@@ -72,15 +80,12 @@ watch(
|
|||||||
:is="useRenderIcon(route.meta && toRaw(route.meta.icon))"
|
:is="useRenderIcon(route.meta && toRaw(route.meta.icon))"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="select-none">{{ route.meta.title }}</span>
|
<div :style="getDivStyle">
|
||||||
<FontIcon
|
<span class="select-none">
|
||||||
v-if="route.meta.extraIcon"
|
{{ route.meta.title }}
|
||||||
width="30px"
|
</span>
|
||||||
height="30px"
|
<extraIcon :extraIcon="route.meta.extraIcon" />
|
||||||
style="position: absolute; right: 10px"
|
</div>
|
||||||
:icon="route.meta.extraIcon.name"
|
|
||||||
:svg="route.meta.extraIcon.svg ? true : false"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
@@ -102,7 +107,7 @@ watch(
|
|||||||
<el-dropdown-menu class="logout">
|
<el-dropdown-menu class="logout">
|
||||||
<el-dropdown-item @click="logout">
|
<el-dropdown-item @click="logout">
|
||||||
<IconifyIconOffline
|
<IconifyIconOffline
|
||||||
icon="logout-circle-r-line"
|
:icon="LogoutCircleRLine"
|
||||||
style="margin: 5px"
|
style="margin: 5px"
|
||||||
/>
|
/>
|
||||||
退出系统
|
退出系统
|
||||||
@@ -115,20 +120,24 @@ watch(
|
|||||||
title="打开项目配置"
|
title="打开项目配置"
|
||||||
@click="onPanel"
|
@click="onPanel"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline icon="setting" />
|
<IconifyIconOffline :icon="Setting" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-loading-mask) {
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
.logout {
|
.logout {
|
||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
|
|
||||||
::v-deep(.el-dropdown-menu__item) {
|
::v-deep(.el-dropdown-menu__item) {
|
||||||
min-width: 100%;
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
min-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { getConfig } from "@/config";
|
import { getConfig } from "@/config";
|
||||||
import { childrenType } from "../../types";
|
import { menuType } from "../../types";
|
||||||
|
import extraIcon from "./extraIcon.vue";
|
||||||
import { useNav } from "@/layout/hooks/useNav";
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
|
import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
|
||||||
|
|
||||||
const { layout, isCollapse } = useNav();
|
import ArrowUp from "@iconify-icons/ep/arrow-up-bold";
|
||||||
|
import EpArrowDown from "@iconify-icons/ep/arrow-down-bold";
|
||||||
|
import ArrowLeft from "@iconify-icons/ep/arrow-left-bold";
|
||||||
|
import ArrowRight from "@iconify-icons/ep/arrow-right-bold";
|
||||||
|
|
||||||
|
const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
type: Object as PropType<childrenType>
|
type: Object as PropType<menuType>
|
||||||
},
|
},
|
||||||
isNest: {
|
isNest: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -22,17 +28,11 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const getExtraIconStyle = computed((): CSSProperties => {
|
const getSpanStyle = computed((): CSSProperties => {
|
||||||
if (!isCollapse.value) {
|
return {
|
||||||
return {
|
width: "100%",
|
||||||
position: "absolute",
|
textAlign: "center"
|
||||||
right: "10px"
|
};
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
position: "static"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const getNoDropdownStyle = computed((): CSSProperties => {
|
const getNoDropdownStyle = computed((): CSSProperties => {
|
||||||
@@ -42,16 +42,6 @@ const getNoDropdownStyle = computed((): CSSProperties => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const getDivStyle = computed((): CSSProperties => {
|
|
||||||
return {
|
|
||||||
width: !isCollapse.value ? "" : "100%",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
overflow: "hidden"
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const getMenuTextStyle = computed(() => {
|
const getMenuTextStyle = computed(() => {
|
||||||
return {
|
return {
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
@@ -60,27 +50,68 @@ const getMenuTextStyle = computed(() => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSubTextStyle = computed((): CSSProperties => {
|
const getsubMenuIconStyle = computed((): CSSProperties => {
|
||||||
return {
|
return {
|
||||||
width: !isCollapse.value ? "210px" : "",
|
display: "flex",
|
||||||
display: "inline-block",
|
justifyContent: "center",
|
||||||
overflow: "hidden",
|
alignItems: "center",
|
||||||
textOverflow: "ellipsis"
|
margin:
|
||||||
|
layout.value === "horizontal"
|
||||||
|
? "0 5px 0 0"
|
||||||
|
: isCollapse.value
|
||||||
|
? "0 auto"
|
||||||
|
: "0 5px 0 0"
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSpanStyle = computed(() => {
|
const getSubTextStyle = computed((): CSSProperties => {
|
||||||
return {
|
if (!isCollapse.value) {
|
||||||
overflow: "hidden",
|
return {
|
||||||
textOverflow: "ellipsis"
|
width: "210px",
|
||||||
|
display: "inline-block",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
width: ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSubMenuDivStyle = computed((): any => {
|
||||||
|
return item => {
|
||||||
|
return !isCollapse.value
|
||||||
|
? {
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
overflow: "hidden"
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
width: "100%",
|
||||||
|
textAlign:
|
||||||
|
item?.parentId === null
|
||||||
|
? "center"
|
||||||
|
: layout.value === "mix" && item?.pathList?.length === 2
|
||||||
|
? "center"
|
||||||
|
: ""
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const expandCloseIcon = computed(() => {
|
const expandCloseIcon = computed(() => {
|
||||||
return getConfig()?.MenuArrowIconNoTransition ? "expand-close-icon" : "";
|
if (!getConfig()?.MenuArrowIconNoTransition) return "";
|
||||||
|
return {
|
||||||
|
"expand-close-icon": useRenderIcon(EpArrowDown),
|
||||||
|
"expand-open-icon": useRenderIcon(ArrowUp),
|
||||||
|
"collapse-close-icon": useRenderIcon(ArrowRight),
|
||||||
|
"collapse-open-icon": useRenderIcon(ArrowLeft)
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const onlyOneChild: childrenType = ref(null);
|
const onlyOneChild: menuType = ref(null);
|
||||||
// 存放菜单是否存在showTooltip属性标识
|
// 存放菜单是否存在showTooltip属性标识
|
||||||
const hoverMenuMap = new WeakMap();
|
const hoverMenuMap = new WeakMap();
|
||||||
// 存储菜单文本dom元素
|
// 存储菜单文本dom元素
|
||||||
@@ -103,10 +134,21 @@ function hoverMenu(key) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hasOneShowingChild(
|
// 左侧菜单折叠后,当菜单没有图标时只显示第一个文字并加上省略号
|
||||||
children: childrenType[] = [],
|
function overflowSlice(text, item?: any) {
|
||||||
parent: childrenType
|
const newText =
|
||||||
) {
|
(text?.length > 1 ? text.toString().slice(0, 1) : text) + "...";
|
||||||
|
if (item && !(isCollapse.value && item?.parentId === null)) {
|
||||||
|
return layout.value === "mix" &&
|
||||||
|
item?.pathList?.length === 2 &&
|
||||||
|
isCollapse.value
|
||||||
|
? newText
|
||||||
|
: text;
|
||||||
|
}
|
||||||
|
return newText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasOneShowingChild(children: menuType[] = [], parent: menuType) {
|
||||||
const showingChildren = children.filter((item: any) => {
|
const showingChildren = children.filter((item: any) => {
|
||||||
onlyOneChild.value = item;
|
onlyOneChild.value = item;
|
||||||
return true;
|
return true;
|
||||||
@@ -132,101 +174,98 @@ function resolvePath(routePath) {
|
|||||||
if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
|
if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
|
||||||
return routePath || props.basePath;
|
return routePath || props.basePath;
|
||||||
} else {
|
} else {
|
||||||
return path.resolve(props.basePath, routePath);
|
// 使用path.posix.resolve替代path.resolve 避免windows环境下使用electron出现盘符问题
|
||||||
|
return path.posix.resolve(props.basePath, routePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<template
|
<el-menu-item
|
||||||
v-if="
|
v-if="
|
||||||
hasOneShowingChild(props.item.children, props.item) &&
|
hasOneShowingChild(props.item.children, props.item) &&
|
||||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren)
|
(!onlyOneChild.children || onlyOneChild.noShowingChildren)
|
||||||
"
|
"
|
||||||
|
:index="resolvePath(onlyOneChild.path)"
|
||||||
|
:class="{ 'submenu-title-noDropdown': !isNest }"
|
||||||
|
:style="getNoDropdownStyle"
|
||||||
>
|
>
|
||||||
<el-menu-item
|
<div
|
||||||
:index="resolvePath(onlyOneChild.path)"
|
v-if="toRaw(props.item.meta.icon)"
|
||||||
:class="{ 'submenu-title-noDropdown': !isNest }"
|
class="sub-menu-icon"
|
||||||
:style="getNoDropdownStyle"
|
:style="getsubMenuIconStyle"
|
||||||
>
|
>
|
||||||
<div class="sub-menu-icon" v-if="toRaw(props.item.meta.icon)">
|
<component
|
||||||
<component
|
:is="
|
||||||
:is="
|
useRenderIcon(
|
||||||
useRenderIcon(
|
toRaw(onlyOneChild.meta.icon) ||
|
||||||
toRaw(onlyOneChild.meta.icon) ||
|
(props.item.meta && toRaw(props.item.meta.icon))
|
||||||
(props.item.meta && toRaw(props.item.meta.icon))
|
)
|
||||||
)
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="
|
|
||||||
isCollapse &&
|
|
||||||
layout === 'vertical' &&
|
|
||||||
props.item?.pathList?.length === 1
|
|
||||||
"
|
"
|
||||||
:style="getDivStyle"
|
/>
|
||||||
>
|
</div>
|
||||||
<span :style="getMenuTextStyle">
|
<span
|
||||||
|
v-if="
|
||||||
|
!props.item?.meta.icon &&
|
||||||
|
isCollapse &&
|
||||||
|
layout === 'vertical' &&
|
||||||
|
props.item?.pathList?.length === 1
|
||||||
|
"
|
||||||
|
:style="getSpanStyle"
|
||||||
|
>
|
||||||
|
{{ overflowSlice(onlyOneChild.meta.title) }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
!onlyOneChild.meta.icon &&
|
||||||
|
isCollapse &&
|
||||||
|
layout === 'mix' &&
|
||||||
|
props.item?.pathList?.length === 2
|
||||||
|
"
|
||||||
|
:style="getSpanStyle"
|
||||||
|
>
|
||||||
|
{{ overflowSlice(onlyOneChild.meta.title) }}
|
||||||
|
</span>
|
||||||
|
<template #title>
|
||||||
|
<div :style="getDivStyle">
|
||||||
|
<span v-if="layout === 'horizontal'">
|
||||||
{{ onlyOneChild.meta.title }}
|
{{ onlyOneChild.meta.title }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
<el-tooltip
|
||||||
<div
|
v-else
|
||||||
v-if="
|
placement="top"
|
||||||
isCollapse && layout === 'mix' && props.item?.pathList?.length === 2
|
:effect="tooltipEffect"
|
||||||
"
|
:offset="-10"
|
||||||
:style="getDivStyle"
|
:disabled="!onlyOneChild.showTooltip"
|
||||||
>
|
>
|
||||||
<span :style="getMenuTextStyle">
|
<template #content>
|
||||||
{{ onlyOneChild.meta.title }}
|
{{ onlyOneChild.meta.title }}
|
||||||
</span>
|
</template>
|
||||||
</div>
|
<span
|
||||||
<template #title>
|
ref="menuTextRef"
|
||||||
<div :style="getDivStyle">
|
:style="getMenuTextStyle"
|
||||||
<span v-if="layout === 'horizontal'">
|
@mouseover="hoverMenu(onlyOneChild)"
|
||||||
|
>
|
||||||
{{ onlyOneChild.meta.title }}
|
{{ onlyOneChild.meta.title }}
|
||||||
</span>
|
</span>
|
||||||
<el-tooltip
|
</el-tooltip>
|
||||||
v-else
|
<extraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
|
||||||
placement="top"
|
</div>
|
||||||
:offset="-10"
|
</template>
|
||||||
:disabled="!onlyOneChild.showTooltip"
|
</el-menu-item>
|
||||||
>
|
|
||||||
<template #content>
|
|
||||||
{{ onlyOneChild.meta.title }}
|
|
||||||
</template>
|
|
||||||
<span
|
|
||||||
ref="menuTextRef"
|
|
||||||
:style="getMenuTextStyle"
|
|
||||||
@mouseover="hoverMenu(onlyOneChild)"
|
|
||||||
>
|
|
||||||
{{ onlyOneChild.meta.title }}
|
|
||||||
</span>
|
|
||||||
</el-tooltip>
|
|
||||||
<FontIcon
|
|
||||||
v-if="onlyOneChild.meta.extraIcon"
|
|
||||||
width="30px"
|
|
||||||
height="30px"
|
|
||||||
:style="getExtraIconStyle"
|
|
||||||
:icon="onlyOneChild.meta.extraIcon.name"
|
|
||||||
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-menu-item>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-sub-menu
|
<el-sub-menu
|
||||||
v-else
|
v-else
|
||||||
ref="subMenu"
|
ref="subMenu"
|
||||||
|
v-bind="expandCloseIcon"
|
||||||
:index="resolvePath(props.item.path)"
|
:index="resolvePath(props.item.path)"
|
||||||
v-bind:[expandCloseIcon]="useRenderIcon('ep-arrow-down')"
|
|
||||||
:expand-open-icon="useRenderIcon('ep-arrow-up')"
|
|
||||||
:collapse-close-icon="useRenderIcon('ep-arrow-right')"
|
|
||||||
:collapse-open-icon="useRenderIcon('ep-arrow-left')"
|
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div v-if="toRaw(props.item.meta.icon)" class="sub-menu-icon">
|
<div
|
||||||
|
v-if="toRaw(props.item.meta.icon)"
|
||||||
|
:style="getsubMenuIconStyle"
|
||||||
|
class="sub-menu-icon"
|
||||||
|
>
|
||||||
<component
|
<component
|
||||||
:is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
|
:is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
|
||||||
/>
|
/>
|
||||||
@@ -234,33 +273,36 @@ function resolvePath(routePath) {
|
|||||||
<span v-if="layout === 'horizontal'">
|
<span v-if="layout === 'horizontal'">
|
||||||
{{ props.item.meta.title }}
|
{{ props.item.meta.title }}
|
||||||
</span>
|
</span>
|
||||||
<el-tooltip
|
<div
|
||||||
v-else
|
:style="getSubMenuDivStyle(props.item)"
|
||||||
placement="top"
|
v-if="
|
||||||
:offset="-10"
|
!(
|
||||||
:disabled="!isCollapse || !props.item.showTooltip"
|
isCollapse &&
|
||||||
|
toRaw(props.item.meta.icon) &&
|
||||||
|
props.item.parentId === null
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<template #content>
|
<el-tooltip
|
||||||
{{ props.item.meta.title }}
|
v-if="layout !== 'horizontal'"
|
||||||
</template>
|
placement="top"
|
||||||
<div
|
:effect="tooltipEffect"
|
||||||
ref="menuTextRef"
|
:offset="-10"
|
||||||
:style="getSubTextStyle"
|
:disabled="!props.item.showTooltip"
|
||||||
@mouseover="hoverMenu(props.item)"
|
|
||||||
>
|
>
|
||||||
<span :style="getSpanStyle">
|
<template #content>
|
||||||
{{ props.item.meta.title }}
|
{{ props.item.meta.title }}
|
||||||
|
</template>
|
||||||
|
<span
|
||||||
|
ref="menuTextRef"
|
||||||
|
:style="getSubTextStyle"
|
||||||
|
@mouseover="hoverMenu(props.item)"
|
||||||
|
>
|
||||||
|
{{ overflowSlice(props.item.meta.title, props.item) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</el-tooltip>
|
||||||
</el-tooltip>
|
<extraIcon v-if="!isCollapse" :extraIcon="props.item.meta.extraIcon" />
|
||||||
<FontIcon
|
</div>
|
||||||
v-if="props.item.meta.extraIcon"
|
|
||||||
width="30px"
|
|
||||||
height="30px"
|
|
||||||
style="position: absolute; right: 10px"
|
|
||||||
:icon="props.item.meta.extraIcon.name"
|
|
||||||
:svg="props.item.meta.extraIcon.svg ? true : false"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<sidebar-item
|
<sidebar-item
|
||||||
v-for="child in props.item.children"
|
v-for="child in props.item.children"
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import MenuFold from "@iconify-icons/ri/menu-fold-fill";
|
||||||
|
import MenuUnfold from "@iconify-icons/ri/menu-unfold-fill";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
}
|
}
|
||||||
@@ -23,7 +26,7 @@ const toggleClick = () => {
|
|||||||
@click="toggleClick"
|
@click="toggleClick"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline
|
<IconifyIconOffline
|
||||||
:icon="props.isActive ? 'menu-fold' : 'menu-unfold'"
|
:icon="props.isActive ? MenuFold : MenuUnfold"
|
||||||
class="inline-block align-middle hover:text-primary dark:hover:!text-white"
|
class="inline-block align-middle hover:text-primary dark:hover:!text-white"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,13 +6,16 @@ import SidebarItem from "./sidebarItem.vue";
|
|||||||
import leftCollapse from "./leftCollapse.vue";
|
import leftCollapse from "./leftCollapse.vue";
|
||||||
import { useNav } from "@/layout/hooks/useNav";
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
import { storageLocal } from "@pureadmin/utils";
|
import { storageLocal } from "@pureadmin/utils";
|
||||||
|
import { responsiveStorageNameSpace } from "@/config";
|
||||||
import { ref, computed, watch, onBeforeMount } from "vue";
|
import { ref, computed, watch, onBeforeMount } from "vue";
|
||||||
import { findRouteByPath, getParentPaths } from "@/router/utils";
|
import { findRouteByPath, getParentPaths } from "@/router/utils";
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const showLogo = ref(
|
const showLogo = ref(
|
||||||
storageLocal.getItem<StorageConfigs>("responsive-configure")?.showLogo ?? true
|
storageLocal().getItem<StorageConfigs>(
|
||||||
|
`${responsiveStorageNameSpace()}configure`
|
||||||
|
)?.showLogo ?? true
|
||||||
);
|
);
|
||||||
|
|
||||||
const { routers, device, pureApp, isCollapse, menuSelect, toggleSideBar } =
|
const { routers, device, pureApp, isCollapse, menuSelect, toggleSideBar } =
|
||||||
@@ -26,7 +29,12 @@ const menuData = computed(() => {
|
|||||||
: usePermissionStoreHook().wholeMenus;
|
: usePermissionStoreHook().wholeMenus;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loading = computed(() =>
|
||||||
|
pureApp.layout === "mix" ? false : menuData.value.length === 0 ? true : false
|
||||||
|
);
|
||||||
|
|
||||||
function getSubMenuData(path: string) {
|
function getSubMenuData(path: string) {
|
||||||
|
subMenuData.value = [];
|
||||||
// path的上级路由组成的数组
|
// path的上级路由组成的数组
|
||||||
const parentPathArr = getParentPaths(
|
const parentPathArr = getParentPaths(
|
||||||
path,
|
path,
|
||||||
@@ -52,6 +60,7 @@ onBeforeMount(() => {
|
|||||||
watch(
|
watch(
|
||||||
() => [route.path, usePermissionStoreHook().wholeMenus],
|
() => [route.path, usePermissionStoreHook().wholeMenus],
|
||||||
() => {
|
() => {
|
||||||
|
if (route.path.includes("/redirect")) return;
|
||||||
getSubMenuData(route.path);
|
getSubMenuData(route.path);
|
||||||
menuSelect(route.path, routers);
|
menuSelect(route.path, routers);
|
||||||
}
|
}
|
||||||
@@ -59,7 +68,10 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="['sidebar-container', showLogo ? 'has-logo' : '']">
|
<div
|
||||||
|
v-loading="loading"
|
||||||
|
:class="['sidebar-container', showLogo ? 'has-logo' : '']"
|
||||||
|
>
|
||||||
<Logo v-if="showLogo" :collapse="isCollapse" />
|
<Logo v-if="showLogo" :collapse="isCollapse" />
|
||||||
<el-scrollbar
|
<el-scrollbar
|
||||||
wrap-class="scrollbar-wrapper"
|
wrap-class="scrollbar-wrapper"
|
||||||
@@ -91,3 +103,9 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.el-loading-mask) {
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@keyframes scheduleInWidth {
|
@keyframes schedule-in-width {
|
||||||
from {
|
from {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes scheduleOutWidth {
|
@keyframes schedule-out-width {
|
||||||
from {
|
from {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -39,41 +39,41 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tags-view {
|
.tags-view {
|
||||||
width: 100%;
|
position: relative;
|
||||||
font-size: 14px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 14px;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
background: #fff;
|
background: #fff;
|
||||||
position: relative;
|
|
||||||
box-shadow: 0 0 1px #888;
|
box-shadow: 0 0 1px #888;
|
||||||
|
|
||||||
.scroll-item {
|
.scroll-item {
|
||||||
border-radius: 3px 3px 0 0;
|
|
||||||
padding: 0 6px;
|
|
||||||
box-shadow: 0 0 1px #888;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-right: 4px;
|
|
||||||
height: 28px;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 6px;
|
||||||
|
margin-right: 4px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
transition: all 0.4s;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
box-shadow: 0 0 1px #888;
|
||||||
|
transition: all 0.4s;
|
||||||
|
|
||||||
.el-icon-close {
|
.el-icon-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
transition: font-size 0.2s;
|
transition: font-size 0.2s;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-radius: 50%;
|
font-size: 13px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #b4bccc;
|
background: #b4bccc;
|
||||||
font-size: 13px;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,24 +91,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scroll-container {
|
.scroll-container {
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 5px 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
padding: 5px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
position: relative;
|
position: relative;
|
||||||
float: left;
|
float: left;
|
||||||
list-style: none;
|
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
list-style: none;
|
||||||
transition: transform 0.5s ease-in-out;
|
transition: transform 0.5s ease-in-out;
|
||||||
|
|
||||||
.scroll-item {
|
.scroll-item {
|
||||||
@@ -123,29 +123,28 @@
|
|||||||
|
|
||||||
/* 右键菜单 */
|
/* 右键菜单 */
|
||||||
.contextmenu {
|
.contextmenu {
|
||||||
margin: 0;
|
|
||||||
background: #fff;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
list-style-type: none;
|
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
border-radius: 4px;
|
margin: 0;
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
list-style-type: none;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
|
box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
|
||||||
|
|
||||||
li {
|
li {
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 7px 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 7px 12px;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
// background: var(--el-color-primary-light-9);
|
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,11 +158,11 @@
|
|||||||
|
|
||||||
.el-dropdown-menu {
|
.el-dropdown-menu {
|
||||||
li {
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -184,6 +183,7 @@
|
|||||||
:deep(.el-dropdown-menu__item--divided) {
|
:deep(.el-dropdown-menu__item--divided) {
|
||||||
margin: 1px 0;
|
margin: 1px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dropdown-menu__item--divided::before {
|
.el-dropdown-menu__item--divided::before {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -193,7 +193,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.scroll-item.is-active {
|
.scroll-item.is-active {
|
||||||
// background-color: var(--el-color-primary-light-9);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
@@ -213,16 +212,16 @@
|
|||||||
.arrow-left,
|
.arrow-left,
|
||||||
.arrow-right,
|
.arrow-right,
|
||||||
.arrow-down {
|
.arrow-down {
|
||||||
|
position: relative;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 38px;
|
height: 38px;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
position: relative;
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
transform: translate(-50%, 50%);
|
transform: translate(-50%, 50%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,8 +235,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.arrow-right {
|
.arrow-right {
|
||||||
box-shadow: -5px 0 5px -6px #ccc;
|
|
||||||
border-right: 0.5px solid #ccc;
|
border-right: 0.5px solid #ccc;
|
||||||
|
box-shadow: -5px 0 5px -6px #ccc;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: e-resize;
|
cursor: e-resize;
|
||||||
@@ -255,8 +254,8 @@
|
|||||||
|
|
||||||
/* 卡片模式下鼠标移出隐藏蓝色边框 */
|
/* 卡片模式下鼠标移出隐藏蓝色边框 */
|
||||||
.card-out {
|
.card-out {
|
||||||
border: none;
|
|
||||||
color: #666;
|
color: #666;
|
||||||
|
border: none;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #666;
|
color: #666;
|
||||||
@@ -265,32 +264,32 @@
|
|||||||
|
|
||||||
/* 灵动模式 */
|
/* 灵动模式 */
|
||||||
.schedule-active {
|
.schedule-active {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: var(--el-color-primary);
|
background: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 灵动模式下鼠标移入显示蓝色进度条 */
|
/* 灵动模式下鼠标移入显示蓝色进度条 */
|
||||||
.schedule-in {
|
.schedule-in {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: var(--el-color-primary);
|
background: var(--el-color-primary);
|
||||||
animation: scheduleInWidth 400ms ease-in;
|
animation: schedule-in-width 200ms ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 灵动模式下鼠标移出隐藏蓝色进度条 */
|
/* 灵动模式下鼠标移出隐藏蓝色进度条 */
|
||||||
.schedule-out {
|
.schedule-out {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: var(--el-color-primary);
|
background: var(--el-color-primary);
|
||||||
animation: scheduleOutWidth 400ms ease-in;
|
animation: schedule-out-width 200ms ease-in;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,20 @@ import { emitter } from "@/utils/mitt";
|
|||||||
import { RouteConfigs } from "../../types";
|
import { RouteConfigs } from "../../types";
|
||||||
import { useTags } from "../../hooks/useTag";
|
import { useTags } from "../../hooks/useTag";
|
||||||
import { routerArrays } from "@/layout/types";
|
import { routerArrays } from "@/layout/types";
|
||||||
import { isEqual, isEmpty } from "lodash-unified";
|
import { isEqual, isAllEmpty } from "@pureadmin/utils";
|
||||||
|
import { handleAliveRoute, getTopMenu } from "@/router/utils";
|
||||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||||
import { ref, watch, unref, nextTick, onBeforeMount } from "vue";
|
|
||||||
import { handleAliveRoute, delAliveRoutes } from "@/router/utils";
|
|
||||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||||
|
import { ref, watch, unref, toRaw, nextTick, onBeforeMount } from "vue";
|
||||||
import { useResizeObserver, useDebounceFn, useFullscreen } from "@vueuse/core";
|
import { useResizeObserver, useDebounceFn, useFullscreen } from "@vueuse/core";
|
||||||
|
|
||||||
|
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
|
||||||
|
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||||
|
import ArrowDown from "@iconify-icons/ri/arrow-down-s-line";
|
||||||
|
import ArrowRightSLine from "@iconify-icons/ri/arrow-right-s-line";
|
||||||
|
import ArrowLeftSLine from "@iconify-icons/ri/arrow-left-s-line";
|
||||||
|
import CloseBold from "@iconify-icons/ep/close-bold";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
route,
|
route,
|
||||||
router,
|
router,
|
||||||
@@ -41,6 +48,8 @@ const tabDom = ref();
|
|||||||
const containerDom = ref();
|
const containerDom = ref();
|
||||||
const scrollbarDom = ref();
|
const scrollbarDom = ref();
|
||||||
const isShowArrow = ref(false);
|
const isShowArrow = ref(false);
|
||||||
|
const topPath = getTopMenu().path;
|
||||||
|
const { VITE_HIDE_HOME } = import.meta.env;
|
||||||
const { isFullscreen, toggle } = useFullscreen();
|
const { isFullscreen, toggle } = useFullscreen();
|
||||||
|
|
||||||
const dynamicTagView = () => {
|
const dynamicTagView = () => {
|
||||||
@@ -56,7 +65,7 @@ const dynamicTagView = () => {
|
|||||||
moveToView(index);
|
moveToView(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveToView = (index: number): void => {
|
const moveToView = async (index: number): Promise<void> => {
|
||||||
const tabNavPadding = 10;
|
const tabNavPadding = 10;
|
||||||
if (!instance.refs["dynamic" + index]) return;
|
if (!instance.refs["dynamic" + index]) return;
|
||||||
const tabItemEl = instance.refs["dynamic" + index][0];
|
const tabItemEl = instance.refs["dynamic" + index][0];
|
||||||
@@ -66,8 +75,13 @@ const moveToView = (index: number): void => {
|
|||||||
const scrollbarDomWidth = scrollbarDom.value
|
const scrollbarDomWidth = scrollbarDom.value
|
||||||
? scrollbarDom.value?.offsetWidth
|
? scrollbarDom.value?.offsetWidth
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
// 获取视图更新后dom
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
// 已有标签页总长度(包含溢出部分)
|
// 已有标签页总长度(包含溢出部分)
|
||||||
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
|
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
|
||||||
|
|
||||||
scrollbarDomWidth <= tabDomWidth
|
scrollbarDomWidth <= tabDomWidth
|
||||||
? (isShowArrow.value = true)
|
? (isShowArrow.value = true)
|
||||||
: (isShowArrow.value = false);
|
: (isShowArrow.value = false);
|
||||||
@@ -151,13 +165,12 @@ function onFresh() {
|
|||||||
const { fullPath, query } = unref(route);
|
const { fullPath, query } = unref(route);
|
||||||
router.replace({
|
router.replace({
|
||||||
path: "/redirect" + fullPath,
|
path: "/redirect" + fullPath,
|
||||||
query: query
|
query
|
||||||
});
|
});
|
||||||
|
handleAliveRoute(route as toRouteType, "refresh");
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
||||||
// 存放被删除的缓存路由
|
|
||||||
let delAliveRouteList = [];
|
|
||||||
const valueIndex: number = multiTags.value.findIndex((item: any) => {
|
const valueIndex: number = multiTags.value.findIndex((item: any) => {
|
||||||
if (item.query) {
|
if (item.query) {
|
||||||
if (item.path === obj.path) {
|
if (item.path === obj.path) {
|
||||||
@@ -178,13 +191,17 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
|||||||
other?: boolean
|
other?: boolean
|
||||||
): void => {
|
): void => {
|
||||||
if (other) {
|
if (other) {
|
||||||
useMultiTagsStoreHook().handleTags("equal", [routerArrays[0], obj]);
|
useMultiTagsStoreHook().handleTags("equal", [
|
||||||
|
VITE_HIDE_HOME === "false" ? routerArrays[0] : toRaw(getTopMenu()),
|
||||||
|
obj
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
delAliveRouteList = useMultiTagsStoreHook().handleTags("splice", "", {
|
useMultiTagsStoreHook().handleTags("splice", "", {
|
||||||
startIndex,
|
startIndex,
|
||||||
length
|
length
|
||||||
}) as any;
|
}) as any;
|
||||||
}
|
}
|
||||||
|
dynamicTagView();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (tag === "other") {
|
if (tag === "other") {
|
||||||
@@ -199,10 +216,6 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
|||||||
}
|
}
|
||||||
const newRoute = useMultiTagsStoreHook().handleTags("slice");
|
const newRoute = useMultiTagsStoreHook().handleTags("slice");
|
||||||
if (current === route.path) {
|
if (current === route.path) {
|
||||||
// 删除缓存路由
|
|
||||||
tag
|
|
||||||
? delAliveRoutes(delAliveRouteList)
|
|
||||||
: handleAliveRoute(route.matched, "delete");
|
|
||||||
// 如果删除当前激活tag就自动切换到最后一个tag
|
// 如果删除当前激活tag就自动切换到最后一个tag
|
||||||
if (tag === "left") return;
|
if (tag === "left") return;
|
||||||
if (newRoute[0]?.query) {
|
if (newRoute[0]?.query) {
|
||||||
@@ -213,8 +226,6 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
|||||||
router.push({ path: newRoute[0].path });
|
router.push({ path: newRoute[0].path });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 删除缓存路由
|
|
||||||
tag ? delAliveRoutes(delAliveRouteList) : delAliveRoutes([obj]);
|
|
||||||
if (!multiTags.value.length) return;
|
if (!multiTags.value.length) return;
|
||||||
if (multiTags.value.some(item => item.path === route.path)) return;
|
if (multiTags.value.some(item => item.path === route.path)) return;
|
||||||
if (newRoute[0]?.query) {
|
if (newRoute[0]?.query) {
|
||||||
@@ -229,6 +240,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
|
|||||||
|
|
||||||
function deleteMenu(item, tag?: string) {
|
function deleteMenu(item, tag?: string) {
|
||||||
deleteDynamicTag(item, item.path, tag);
|
deleteDynamicTag(item, item.path, tag);
|
||||||
|
handleAliveRoute(route as toRouteType);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
||||||
@@ -275,18 +287,19 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
|||||||
startIndex: 1,
|
startIndex: 1,
|
||||||
length: multiTags.value.length
|
length: multiTags.value.length
|
||||||
});
|
});
|
||||||
router.push("/welcome");
|
router.push(topPath);
|
||||||
|
handleAliveRoute(route as toRouteType);
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
// 整体页面全屏
|
// 整体页面全屏
|
||||||
toggle();
|
toggle();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isFullscreen.value) {
|
if (isFullscreen.value) {
|
||||||
tagsViews[6].icon = "exit-fullscreen";
|
tagsViews[6].icon = ExitFullscreen;
|
||||||
tagsViews[6].text = "整体页面退出全屏";
|
tagsViews[6].text = "退出全屏";
|
||||||
} else {
|
} else {
|
||||||
tagsViews[6].icon = "fullscreen";
|
tagsViews[6].icon = Fullscreen;
|
||||||
tagsViews[6].text = "整体页面全屏";
|
tagsViews[6].text = "全屏";
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
break;
|
break;
|
||||||
@@ -295,10 +308,10 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
|
|||||||
onContentFullScreen();
|
onContentFullScreen();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (pureSetting.hiddenSideBar) {
|
if (pureSetting.hiddenSideBar) {
|
||||||
tagsViews[7].icon = "exit-fullscreen";
|
tagsViews[7].icon = ExitFullscreen;
|
||||||
tagsViews[7].text = "内容区退出全屏";
|
tagsViews[7].text = "内容区退出全屏";
|
||||||
} else {
|
} else {
|
||||||
tagsViews[7].icon = "fullscreen";
|
tagsViews[7].icon = Fullscreen;
|
||||||
tagsViews[7].text = "内容区全屏";
|
tagsViews[7].text = "内容区全屏";
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -331,7 +344,7 @@ function disabledMenus(value: boolean) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是首页,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
|
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
|
||||||
function showMenuModel(
|
function showMenuModel(
|
||||||
currentPath: string,
|
currentPath: string,
|
||||||
query: object = {},
|
query: object = {},
|
||||||
@@ -340,7 +353,7 @@ function showMenuModel(
|
|||||||
const allRoute = multiTags.value;
|
const allRoute = multiTags.value;
|
||||||
const routeLength = multiTags.value.length;
|
const routeLength = multiTags.value.length;
|
||||||
let currentIndex = -1;
|
let currentIndex = -1;
|
||||||
if (isEmpty(query)) {
|
if (isAllEmpty(query)) {
|
||||||
currentIndex = allRoute.findIndex(v => v.path === currentPath);
|
currentIndex = allRoute.findIndex(v => v.path === currentPath);
|
||||||
} else {
|
} else {
|
||||||
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
|
currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
|
||||||
@@ -353,11 +366,11 @@ function showMenuModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* currentIndex为1时,左侧的菜单是首页,则不显示关闭左侧标签页
|
* currentIndex为1时,左侧的菜单顶级菜单,则不显示关闭左侧标签页
|
||||||
* 如果currentIndex等于routeLength-1,右侧没有菜单,则不显示关闭右侧标签页
|
* 如果currentIndex等于routeLength-1,右侧没有菜单,则不显示关闭右侧标签页
|
||||||
*/
|
*/
|
||||||
if (currentIndex === 1 && routeLength !== 2) {
|
if (currentIndex === 1 && routeLength !== 2) {
|
||||||
// 左侧的菜单是首页,右侧存在别的菜单
|
// 左侧的菜单是顶级菜单,右侧存在别的菜单
|
||||||
tagsViews[2].show = false;
|
tagsViews[2].show = false;
|
||||||
Array.of(1, 3, 4, 5).forEach(v => {
|
Array.of(1, 3, 4, 5).forEach(v => {
|
||||||
tagsViews[v].disabled = false;
|
tagsViews[v].disabled = false;
|
||||||
@@ -365,7 +378,7 @@ function showMenuModel(
|
|||||||
tagsViews[2].disabled = true;
|
tagsViews[2].disabled = true;
|
||||||
} else if (currentIndex === 1 && routeLength === 2) {
|
} else if (currentIndex === 1 && routeLength === 2) {
|
||||||
disabledMenus(false);
|
disabledMenus(false);
|
||||||
// 左侧的菜单是首页,右侧不存在别的菜单
|
// 左侧的菜单是顶级菜单,右侧不存在别的菜单
|
||||||
Array.of(2, 3, 4).forEach(v => {
|
Array.of(2, 3, 4).forEach(v => {
|
||||||
tagsViews[v].show = false;
|
tagsViews[v].show = false;
|
||||||
tagsViews[v].disabled = true;
|
tagsViews[v].disabled = true;
|
||||||
@@ -377,8 +390,8 @@ function showMenuModel(
|
|||||||
tagsViews[v].disabled = false;
|
tagsViews[v].disabled = false;
|
||||||
});
|
});
|
||||||
tagsViews[3].disabled = true;
|
tagsViews[3].disabled = true;
|
||||||
} else if (currentIndex === 0 || currentPath === "/redirect/welcome") {
|
} else if (currentIndex === 0 || currentPath === `/redirect${topPath}`) {
|
||||||
// 当前路由为首页
|
// 当前路由为顶级菜单
|
||||||
disabledMenus(true);
|
disabledMenus(true);
|
||||||
} else {
|
} else {
|
||||||
disabledMenus(false);
|
disabledMenus(false);
|
||||||
@@ -387,8 +400,8 @@ function showMenuModel(
|
|||||||
|
|
||||||
function openMenu(tag, e) {
|
function openMenu(tag, e) {
|
||||||
closeMenu();
|
closeMenu();
|
||||||
if (tag.path === "/welcome") {
|
if (tag.path === topPath) {
|
||||||
// 右键菜单为首页,只显示刷新
|
// 右键菜单为顶级菜单,只显示刷新
|
||||||
showMenus(false);
|
showMenus(false);
|
||||||
tagsViews[0].show = true;
|
tagsViews[0].show = true;
|
||||||
} else if (route.path !== tag.path && route.name !== tag.name) {
|
} else if (route.path !== tag.path && route.name !== tag.name) {
|
||||||
@@ -481,6 +494,11 @@ watch([route], () => {
|
|||||||
dynamicTagView();
|
dynamicTagView();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(isFullscreen, () => {
|
||||||
|
tagsViews[6].icon = Fullscreen;
|
||||||
|
tagsViews[6].text = "全屏";
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
useResizeObserver(
|
useResizeObserver(
|
||||||
scrollbarDom,
|
scrollbarDom,
|
||||||
@@ -494,7 +512,7 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div ref="containerDom" class="tags-view" v-if="!showTags">
|
<div ref="containerDom" class="tags-view" v-if="!showTags">
|
||||||
<span v-show="isShowArrow" class="arrow-left">
|
<span v-show="isShowArrow" class="arrow-left">
|
||||||
<IconifyIconOffline icon="arrow-left-s-line" @click="handleScroll(200)" />
|
<IconifyIconOffline :icon="ArrowLeftSLine" @click="handleScroll(200)" />
|
||||||
</span>
|
</span>
|
||||||
<div ref="scrollbarDom" class="scroll-container">
|
<div ref="scrollbarDom" class="scroll-container">
|
||||||
<div class="tab select-none" ref="tabDom" :style="getTabStyle">
|
<div class="tab select-none" ref="tabDom" :style="getTabStyle">
|
||||||
@@ -505,7 +523,7 @@ onMounted(() => {
|
|||||||
:class="[
|
:class="[
|
||||||
'scroll-item is-closable',
|
'scroll-item is-closable',
|
||||||
linkIsActive(item),
|
linkIsActive(item),
|
||||||
$route.path === item.path && showModel === 'card'
|
route.path === item.path && showModel === 'card'
|
||||||
? 'card-active'
|
? 'card-active'
|
||||||
: ''
|
: ''
|
||||||
]"
|
]"
|
||||||
@@ -528,7 +546,7 @@ onMounted(() => {
|
|||||||
class="el-icon-close"
|
class="el-icon-close"
|
||||||
@click.stop="deleteMenu(item)"
|
@click.stop="deleteMenu(item)"
|
||||||
>
|
>
|
||||||
<IconifyIconOffline icon="close-bold" />
|
<IconifyIconOffline :icon="CloseBold" />
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
:ref="'schedule' + index"
|
:ref="'schedule' + index"
|
||||||
@@ -539,10 +557,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span v-show="isShowArrow" class="arrow-right">
|
<span v-show="isShowArrow" class="arrow-right">
|
||||||
<IconifyIconOffline
|
<IconifyIconOffline :icon="ArrowRightSLine" @click="handleScroll(-200)" />
|
||||||
icon="arrow-right-s-line"
|
|
||||||
@click="handleScroll(-200)"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<!-- 右键菜单按钮 -->
|
<!-- 右键菜单按钮 -->
|
||||||
<transition name="el-zoom-in-top">
|
<transition name="el-zoom-in-top">
|
||||||
@@ -571,7 +586,7 @@ onMounted(() => {
|
|||||||
@command="handleCommand"
|
@command="handleCommand"
|
||||||
>
|
>
|
||||||
<span class="arrow-down">
|
<span class="arrow-down">
|
||||||
<IconifyIconOffline icon="arrow-down" class="dark:text-white" />
|
<IconifyIconOffline :icon="ArrowDown" class="dark:text-white" />
|
||||||
</span>
|
</span>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
@@ -592,5 +607,5 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "./index.scss";
|
@import url("./index.scss");
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -50,15 +50,15 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.frame {
|
.frame {
|
||||||
height: calc(100vh - 88px);
|
|
||||||
z-index: 998;
|
z-index: 998;
|
||||||
|
height: calc(100vh - 88px);
|
||||||
|
|
||||||
.frame-iframe {
|
.frame-iframe {
|
||||||
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 0;
|
border: 0;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { getConfig } from "@/config";
|
import { getConfig } from "@/config";
|
||||||
import { find } from "lodash-unified";
|
|
||||||
import { useLayout } from "./useLayout";
|
import { useLayout } from "./useLayout";
|
||||||
import { themeColorsType } from "../types";
|
import { themeColorsType } from "../types";
|
||||||
import { TinyColor } from "@ctrl/tinycolor";
|
|
||||||
import { useGlobal } from "@pureadmin/utils";
|
import { useGlobal } from "@pureadmin/utils";
|
||||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||||
import {
|
import {
|
||||||
@@ -40,7 +38,7 @@ export function useDataThemeChange() {
|
|||||||
const body = document.documentElement as HTMLElement;
|
const body = document.documentElement as HTMLElement;
|
||||||
|
|
||||||
/** 设置导航主题色 */
|
/** 设置导航主题色 */
|
||||||
function setLayoutThemeColor(theme = "default") {
|
function setLayoutThemeColor(theme = getConfig().Theme ?? "default") {
|
||||||
layoutTheme.value.theme = theme;
|
layoutTheme.value.theme = theme;
|
||||||
toggleTheme({
|
toggleTheme({
|
||||||
scopeName: `layout-theme-${theme}`
|
scopeName: `layout-theme-${theme}`
|
||||||
@@ -56,35 +54,27 @@ export function useDataThemeChange() {
|
|||||||
if (theme === "default" || theme === "light") {
|
if (theme === "default" || theme === "light") {
|
||||||
setEpThemeColor(getConfig().EpThemeColor);
|
setEpThemeColor(getConfig().EpThemeColor);
|
||||||
} else {
|
} else {
|
||||||
const colors = find(themeColors.value, { themeColor: theme });
|
const colors = themeColors.value.find(v => v.themeColor === theme);
|
||||||
setEpThemeColor(colors.color);
|
setEpThemeColor(colors.color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function setPropertyPrimary(mode: string, i: number, color: string) {
|
||||||
* @description 自动计算hover和active颜色
|
document.documentElement.style.setProperty(
|
||||||
* @see {@link https://element-plus.org/zh-CN/component/button.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E9%A2%9C%E8%89%B2}
|
`--el-color-primary-${mode}-${i}`,
|
||||||
*/
|
dataTheme.value ? darken(color, i / 10) : lighten(color, i / 10)
|
||||||
const shadeBgColor = (color: string): string => {
|
);
|
||||||
return new TinyColor(color).shade(10).toString();
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/** 设置 `element-plus` 主题色 */
|
/** 设置 `element-plus` 主题色 */
|
||||||
const setEpThemeColor = (color: string) => {
|
const setEpThemeColor = (color: string) => {
|
||||||
useEpThemeStoreHook().setEpThemeColor(color);
|
useEpThemeStoreHook().setEpThemeColor(color);
|
||||||
body.style.setProperty("--el-color-primary-active", shadeBgColor(color));
|
|
||||||
document.documentElement.style.setProperty("--el-color-primary", color);
|
document.documentElement.style.setProperty("--el-color-primary", color);
|
||||||
for (let i = 1; i <= 9; i++) {
|
|
||||||
document.documentElement.style.setProperty(
|
|
||||||
`--el-color-primary-light-${i}`,
|
|
||||||
lighten(color, i / 10)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (let i = 1; i <= 2; i++) {
|
for (let i = 1; i <= 2; i++) {
|
||||||
document.documentElement.style.setProperty(
|
setPropertyPrimary("dark", i, color);
|
||||||
`--el-color-primary-dark-${i}`,
|
}
|
||||||
darken(color, i / 10)
|
for (let i = 1; i <= 9; i++) {
|
||||||
);
|
setPropertyPrimary("light", i, color);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { computed } from "vue";
|
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { getConfig } from "@/config";
|
import { getConfig } from "@/config";
|
||||||
import { emitter } from "@/utils/mitt";
|
import { emitter } from "@/utils/mitt";
|
||||||
import { routeMetaType } from "../types";
|
import { routeMetaType } from "../types";
|
||||||
|
import { getTopMenu } from "@/router/utils";
|
||||||
import { useGlobal } from "@pureadmin/utils";
|
import { useGlobal } from "@pureadmin/utils";
|
||||||
|
import { computed, CSSProperties } from "vue";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { router, remainingPaths } from "@/router";
|
import { router, remainingPaths } from "@/router";
|
||||||
import { useAppStoreHook } from "@/store/modules/app";
|
import { useAppStoreHook } from "@/store/modules/app";
|
||||||
@@ -17,6 +18,18 @@ export function useNav() {
|
|||||||
const pureApp = useAppStoreHook();
|
const pureApp = useAppStoreHook();
|
||||||
const routers = useRouter().options.routes;
|
const routers = useRouter().options.routes;
|
||||||
const { wholeMenus } = storeToRefs(usePermissionStoreHook());
|
const { wholeMenus } = storeToRefs(usePermissionStoreHook());
|
||||||
|
/** 平台`layout`中所有`el-tooltip`的`effect`配置,默认`light` */
|
||||||
|
const tooltipEffect = getConfig()?.TooltipEffect ?? "light";
|
||||||
|
|
||||||
|
const getDivStyle = computed((): CSSProperties => {
|
||||||
|
return {
|
||||||
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
overflow: "hidden"
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
/** 用户名 */
|
/** 用户名 */
|
||||||
const username = computed(() => {
|
const username = computed(() => {
|
||||||
@@ -56,8 +69,8 @@ export function useNav() {
|
|||||||
useUserStoreHook().logOut();
|
useUserStoreHook().logOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
function backHome() {
|
function backTopMenu() {
|
||||||
router.push("/welcome");
|
router.push(getTopMenu().path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPanel() {
|
function onPanel() {
|
||||||
@@ -126,8 +139,9 @@ export function useNav() {
|
|||||||
logout,
|
logout,
|
||||||
routers,
|
routers,
|
||||||
$storage,
|
$storage,
|
||||||
backHome,
|
backTopMenu,
|
||||||
onPanel,
|
onPanel,
|
||||||
|
getDivStyle,
|
||||||
changeTitle,
|
changeTitle,
|
||||||
toggleSideBar,
|
toggleSideBar,
|
||||||
menuSelect,
|
menuSelect,
|
||||||
@@ -136,6 +150,7 @@ export function useNav() {
|
|||||||
isCollapse,
|
isCollapse,
|
||||||
pureApp,
|
pureApp,
|
||||||
username,
|
username,
|
||||||
avatarsStyle
|
avatarsStyle,
|
||||||
|
tooltipEffect
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,24 @@ import {
|
|||||||
import { tagsViewsType } from "../types";
|
import { tagsViewsType } from "../types";
|
||||||
import { useEventListener } from "@vueuse/core";
|
import { useEventListener } from "@vueuse/core";
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRoute, useRouter } from "vue-router";
|
||||||
import { isEqual, isBoolean } from "@pureadmin/utils";
|
import { responsiveStorageNameSpace } from "@/config";
|
||||||
import { useSettingStoreHook } from "@/store/modules/settings";
|
import { useSettingStoreHook } from "@/store/modules/settings";
|
||||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||||
import { storageLocal, toggleClass, hasClass } from "@pureadmin/utils";
|
import {
|
||||||
|
isEqual,
|
||||||
|
isBoolean,
|
||||||
|
storageLocal,
|
||||||
|
toggleClass,
|
||||||
|
hasClass
|
||||||
|
} from "@pureadmin/utils";
|
||||||
|
|
||||||
|
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
|
||||||
|
import CloseAllTags from "@iconify-icons/ri/subtract-line";
|
||||||
|
import CloseOtherTags from "@iconify-icons/ri/text-spacing";
|
||||||
|
import CloseRightTags from "@iconify-icons/ri/text-direction-l";
|
||||||
|
import CloseLeftTags from "@iconify-icons/ri/text-direction-r";
|
||||||
|
import RefreshRight from "@iconify-icons/ep/refresh-right";
|
||||||
|
import Close from "@iconify-icons/ep/close";
|
||||||
|
|
||||||
export function useTags() {
|
export function useTags() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -32,13 +46,16 @@ export function useTags() {
|
|||||||
|
|
||||||
/** 显示模式,默认灵动模式 */
|
/** 显示模式,默认灵动模式 */
|
||||||
const showModel = ref(
|
const showModel = ref(
|
||||||
storageLocal.getItem<StorageConfigs>("responsive-configure")?.showModel ||
|
storageLocal().getItem<StorageConfigs>(
|
||||||
"smart"
|
`${responsiveStorageNameSpace()}configure`
|
||||||
|
)?.showModel || "smart"
|
||||||
);
|
);
|
||||||
/** 是否隐藏标签页,默认显示 */
|
/** 是否隐藏标签页,默认显示 */
|
||||||
const showTags =
|
const showTags =
|
||||||
ref(
|
ref(
|
||||||
storageLocal.getItem<StorageConfigs>("responsive-configure").hideTabs
|
storageLocal().getItem<StorageConfigs>(
|
||||||
|
`${responsiveStorageNameSpace()}configure`
|
||||||
|
).hideTabs
|
||||||
) ?? ref("false");
|
) ?? ref("false");
|
||||||
const multiTags: any = computed(() => {
|
const multiTags: any = computed(() => {
|
||||||
return useMultiTagsStoreHook().multiTags;
|
return useMultiTagsStoreHook().multiTags;
|
||||||
@@ -46,56 +63,56 @@ export function useTags() {
|
|||||||
|
|
||||||
const tagsViews = reactive<Array<tagsViewsType>>([
|
const tagsViews = reactive<Array<tagsViewsType>>([
|
||||||
{
|
{
|
||||||
icon: "refresh-right",
|
icon: RefreshRight,
|
||||||
text: "重新加载",
|
text: "重新加载",
|
||||||
divided: false,
|
divided: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "close",
|
icon: Close,
|
||||||
text: "关闭当前标签页",
|
text: "关闭当前标签页",
|
||||||
divided: false,
|
divided: false,
|
||||||
disabled: multiTags.value.length > 1 ? false : true,
|
disabled: multiTags.value.length > 1 ? false : true,
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "close-left-tags",
|
icon: CloseLeftTags,
|
||||||
text: "关闭左侧标签页",
|
text: "关闭左侧标签页",
|
||||||
divided: true,
|
divided: true,
|
||||||
disabled: multiTags.value.length > 1 ? false : true,
|
disabled: multiTags.value.length > 1 ? false : true,
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "close-right-tags",
|
icon: CloseRightTags,
|
||||||
text: "关闭右侧标签页",
|
text: "关闭右侧标签页",
|
||||||
divided: false,
|
divided: false,
|
||||||
disabled: multiTags.value.length > 1 ? false : true,
|
disabled: multiTags.value.length > 1 ? false : true,
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "close-other-tags",
|
icon: CloseOtherTags,
|
||||||
text: "关闭其他标签页",
|
text: "关闭其他标签页",
|
||||||
divided: true,
|
divided: true,
|
||||||
disabled: multiTags.value.length > 2 ? false : true,
|
disabled: multiTags.value.length > 2 ? false : true,
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "close-all-tags",
|
icon: CloseAllTags,
|
||||||
text: "关闭全部标签页",
|
text: "关闭全部标签页",
|
||||||
divided: false,
|
divided: false,
|
||||||
disabled: multiTags.value.length > 1 ? false : true,
|
disabled: multiTags.value.length > 1 ? false : true,
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "fullscreen",
|
icon: Fullscreen,
|
||||||
text: "整体页面全屏",
|
text: "整体页面全屏",
|
||||||
divided: true,
|
divided: true,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
show: true
|
show: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: "fullscreen",
|
icon: Fullscreen,
|
||||||
text: "内容区全屏",
|
text: "内容区全屏",
|
||||||
divided: false,
|
divided: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
@@ -186,11 +203,14 @@ export function useTags() {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!showModel.value) {
|
if (!showModel.value) {
|
||||||
const configure = storageLocal.getItem<StorageConfigs>(
|
const configure = storageLocal().getItem<StorageConfigs>(
|
||||||
"responsive-configure"
|
`${responsiveStorageNameSpace()}configure`
|
||||||
);
|
);
|
||||||
configure.showModel = "card";
|
configure.showModel = "card";
|
||||||
storageLocal.setItem("responsive-configure", configure);
|
storageLocal().setItem(
|
||||||
|
`${responsiveStorageNameSpace()}configure`,
|
||||||
|
configure
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import "animate.css";
|
||||||
|
// 引入 src/components/ReIcon/src/offlineIcon.ts 文件中所有使用addIcon添加过的本地图标
|
||||||
|
import "@/components/ReIcon/src/offlineIcon";
|
||||||
import { setType } from "./types";
|
import { setType } from "./types";
|
||||||
import { emitter } from "@/utils/mitt";
|
import { emitter } from "@/utils/mitt";
|
||||||
import { useLayout } from "./hooks/useLayout";
|
import { useLayout } from "./hooks/useLayout";
|
||||||
@@ -176,20 +179,16 @@ const layoutHeader = defineComponent({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@mixin clearfix {
|
.app-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
|
||||||
display: table;
|
display: table;
|
||||||
clear: both;
|
clear: both;
|
||||||
|
content: "";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.app-wrapper {
|
|
||||||
@include clearfix;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&.mobile.openSidebar {
|
&.mobile.openSidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -198,13 +197,13 @@ const layoutHeader = defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.app-mask {
|
.app-mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 999;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
background: #000;
|
background: #000;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
width: 100%;
|
|
||||||
top: 0;
|
|
||||||
height: 100%;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.re-screen {
|
.re-screen {
|
||||||
|
|||||||
@@ -2,19 +2,11 @@
|
|||||||
* @description ⚠️:此文件仅供主题插件使用,请不要在此文件中导出别的工具函数(仅在页面加载前运行)
|
* @description ⚠️:此文件仅供主题插件使用,请不要在此文件中导出别的工具函数(仅在页面加载前运行)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { EpThemeColor } from "../../../public/serverConfig.json";
|
import { type multipleScopeVarsOptions } from "@pureadmin/theme";
|
||||||
|
|
||||||
type MultipleScopeVarsItem = {
|
|
||||||
scopeName: string;
|
|
||||||
varsContent: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 将vxe默认主题色和ep默认主题色保持一致 */
|
|
||||||
const vxeColor = EpThemeColor;
|
|
||||||
/** 预设主题色 */
|
/** 预设主题色 */
|
||||||
const themeColors = {
|
const themeColors = {
|
||||||
default: {
|
default: {
|
||||||
vxeColor,
|
|
||||||
subMenuActiveText: "#fff",
|
subMenuActiveText: "#fff",
|
||||||
menuBg: "#001529",
|
menuBg: "#001529",
|
||||||
menuHover: "#4091f7",
|
menuHover: "#4091f7",
|
||||||
@@ -26,7 +18,6 @@ const themeColors = {
|
|||||||
menuActiveBefore: "#4091f7"
|
menuActiveBefore: "#4091f7"
|
||||||
},
|
},
|
||||||
light: {
|
light: {
|
||||||
vxeColor,
|
|
||||||
subMenuActiveText: "#409eff",
|
subMenuActiveText: "#409eff",
|
||||||
menuBg: "#fff",
|
menuBg: "#fff",
|
||||||
menuHover: "#e0ebf6",
|
menuHover: "#e0ebf6",
|
||||||
@@ -38,7 +29,6 @@ const themeColors = {
|
|||||||
menuActiveBefore: "#4091f7"
|
menuActiveBefore: "#4091f7"
|
||||||
},
|
},
|
||||||
dusk: {
|
dusk: {
|
||||||
vxeColor: "#f5222d",
|
|
||||||
subMenuActiveText: "#fff",
|
subMenuActiveText: "#fff",
|
||||||
menuBg: "#2a0608",
|
menuBg: "#2a0608",
|
||||||
menuHover: "#e13c39",
|
menuHover: "#e13c39",
|
||||||
@@ -50,7 +40,6 @@ const themeColors = {
|
|||||||
menuActiveBefore: "#e13c39"
|
menuActiveBefore: "#e13c39"
|
||||||
},
|
},
|
||||||
volcano: {
|
volcano: {
|
||||||
vxeColor: "#fa541c",
|
|
||||||
subMenuActiveText: "#fff",
|
subMenuActiveText: "#fff",
|
||||||
menuBg: "#2b0e05",
|
menuBg: "#2b0e05",
|
||||||
menuHover: "#e85f33",
|
menuHover: "#e85f33",
|
||||||
@@ -62,7 +51,6 @@ const themeColors = {
|
|||||||
menuActiveBefore: "#e85f33"
|
menuActiveBefore: "#e85f33"
|
||||||
},
|
},
|
||||||
yellow: {
|
yellow: {
|
||||||
vxeColor: "#fadb14",
|
|
||||||
subMenuActiveText: "#d25f00",
|
subMenuActiveText: "#d25f00",
|
||||||
menuBg: "#2b2503",
|
menuBg: "#2b2503",
|
||||||
menuHover: "#f6da4d",
|
menuHover: "#f6da4d",
|
||||||
@@ -74,7 +62,6 @@ const themeColors = {
|
|||||||
menuActiveBefore: "#f6da4d"
|
menuActiveBefore: "#f6da4d"
|
||||||
},
|
},
|
||||||
mingQing: {
|
mingQing: {
|
||||||
vxeColor: "#13c2c2",
|
|
||||||
subMenuActiveText: "#fff",
|
subMenuActiveText: "#fff",
|
||||||
menuBg: "#032121",
|
menuBg: "#032121",
|
||||||
menuHover: "#59bfc1",
|
menuHover: "#59bfc1",
|
||||||
@@ -86,7 +73,6 @@ const themeColors = {
|
|||||||
menuActiveBefore: "#59bfc1"
|
menuActiveBefore: "#59bfc1"
|
||||||
},
|
},
|
||||||
auroraGreen: {
|
auroraGreen: {
|
||||||
vxeColor: "#52c41a",
|
|
||||||
subMenuActiveText: "#fff",
|
subMenuActiveText: "#fff",
|
||||||
menuBg: "#0b1e15",
|
menuBg: "#0b1e15",
|
||||||
menuHover: "#60ac80",
|
menuHover: "#60ac80",
|
||||||
@@ -98,7 +84,6 @@ const themeColors = {
|
|||||||
menuActiveBefore: "#60ac80"
|
menuActiveBefore: "#60ac80"
|
||||||
},
|
},
|
||||||
pink: {
|
pink: {
|
||||||
vxeColor: "#eb2f96",
|
|
||||||
subMenuActiveText: "#fff",
|
subMenuActiveText: "#fff",
|
||||||
menuBg: "#28081a",
|
menuBg: "#28081a",
|
||||||
menuHover: "#d84493",
|
menuHover: "#d84493",
|
||||||
@@ -110,7 +95,6 @@ const themeColors = {
|
|||||||
menuActiveBefore: "#d84493"
|
menuActiveBefore: "#d84493"
|
||||||
},
|
},
|
||||||
saucePurple: {
|
saucePurple: {
|
||||||
vxeColor: "#722ed1",
|
|
||||||
subMenuActiveText: "#fff",
|
subMenuActiveText: "#fff",
|
||||||
menuBg: "#130824",
|
menuBg: "#130824",
|
||||||
menuHover: "#693ac9",
|
menuHover: "#693ac9",
|
||||||
@@ -126,13 +110,12 @@ const themeColors = {
|
|||||||
/**
|
/**
|
||||||
* @description 将预设主题色处理成主题插件所需格式
|
* @description 将预设主题色处理成主题插件所需格式
|
||||||
*/
|
*/
|
||||||
export const genScssMultipleScopeVars = (): MultipleScopeVarsItem[] => {
|
export const genScssMultipleScopeVars = (): multipleScopeVarsOptions[] => {
|
||||||
const result = [] as MultipleScopeVarsItem[];
|
const result = [] as multipleScopeVarsOptions[];
|
||||||
Object.keys(themeColors).forEach(key => {
|
Object.keys(themeColors).forEach(key => {
|
||||||
result.push({
|
result.push({
|
||||||
scopeName: `layout-theme-${key}`,
|
scopeName: `layout-theme-${key}`,
|
||||||
varsContent: `
|
varsContent: `
|
||||||
$vxe-primary-color: ${themeColors[key].vxeColor} !default;
|
|
||||||
$subMenuActiveText: ${themeColors[key].subMenuActiveText} !default;
|
$subMenuActiveText: ${themeColors[key].subMenuActiveText} !default;
|
||||||
$menuBg: ${themeColors[key].menuBg} !default;
|
$menuBg: ${themeColors[key].menuBg} !default;
|
||||||
$menuHover: ${themeColors[key].menuHover} !default;
|
$menuHover: ${themeColors[key].menuHover} !default;
|
||||||
@@ -143,7 +126,7 @@ export const genScssMultipleScopeVars = (): MultipleScopeVarsItem[] => {
|
|||||||
$menuTitleHover: ${themeColors[key].menuTitleHover} !default;
|
$menuTitleHover: ${themeColors[key].menuTitleHover} !default;
|
||||||
$menuActiveBefore: ${themeColors[key].menuActiveBefore} !default;
|
$menuActiveBefore: ${themeColors[key].menuActiveBefore} !default;
|
||||||
`
|
`
|
||||||
} as MultipleScopeVarsItem);
|
} as multipleScopeVarsOptions);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
export const routerArrays: Array<RouteConfigs> = [
|
import type { IconifyIcon } from "@iconify/vue";
|
||||||
{
|
const { VITE_HIDE_HOME } = import.meta.env;
|
||||||
path: "/welcome",
|
|
||||||
parentPath: "/",
|
export const routerArrays: Array<RouteConfigs> =
|
||||||
meta: {
|
VITE_HIDE_HOME === "false"
|
||||||
title: "首页",
|
? [
|
||||||
icon: "home-filled"
|
{
|
||||||
}
|
path: "/welcome",
|
||||||
}
|
parentPath: "/",
|
||||||
];
|
meta: {
|
||||||
|
title: "首页",
|
||||||
|
icon: "homeFilled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
|
||||||
export type routeMetaType = {
|
export type routeMetaType = {
|
||||||
title?: string;
|
title?: string;
|
||||||
icon?: string;
|
icon?: string | IconifyIcon;
|
||||||
showLink?: boolean;
|
showLink?: boolean;
|
||||||
savedPosition?: boolean;
|
savedPosition?: boolean;
|
||||||
auths?: Array<string>;
|
auths?: Array<string>;
|
||||||
@@ -32,7 +38,7 @@ export type multiTagsType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type tagsViewsType = {
|
export type tagsViewsType = {
|
||||||
icon: string;
|
icon: string | IconifyIcon;
|
||||||
text: string;
|
text: string;
|
||||||
divided: boolean;
|
divided: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
@@ -56,23 +62,23 @@ export interface setType {
|
|||||||
hideTabs: boolean;
|
hideTabs: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type childrenType = {
|
export type menuType = {
|
||||||
|
id?: number;
|
||||||
path?: string;
|
path?: string;
|
||||||
noShowingChildren?: boolean;
|
noShowingChildren?: boolean;
|
||||||
children?: childrenType[];
|
children?: menuType[];
|
||||||
value: unknown;
|
value: unknown;
|
||||||
meta?: {
|
meta?: {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
rank?: number;
|
||||||
showParent?: boolean;
|
showParent?: boolean;
|
||||||
extraIcon?: {
|
extraIcon?: string;
|
||||||
svg?: boolean;
|
|
||||||
name?: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
showTooltip?: boolean;
|
showTooltip?: boolean;
|
||||||
parentId?: number;
|
parentId?: number;
|
||||||
pathList?: number[];
|
pathList?: number[];
|
||||||
|
redirect?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type themeColorsType = {
|
export type themeColorsType = {
|
||||||
|
|||||||
@@ -6,19 +6,18 @@ import { getServerConfig } from "./config";
|
|||||||
import { createApp, Directive } from "vue";
|
import { createApp, Directive } from "vue";
|
||||||
import { MotionPlugin } from "@vueuse/motion";
|
import { MotionPlugin } from "@vueuse/motion";
|
||||||
// import { useEcharts } from "@/plugins/echarts";
|
// import { useEcharts } from "@/plugins/echarts";
|
||||||
// import { useTable } from "@/plugins/vxe-table";
|
|
||||||
import { injectResponsiveStorage } from "@/utils/responsive";
|
import { injectResponsiveStorage } from "@/utils/responsive";
|
||||||
|
|
||||||
// import Table from "@pureadmin/table";
|
// import Table from "@pureadmin/table";
|
||||||
// import PureDescriptions from "@pureadmin/descriptions";
|
// import PureDescriptions from "@pureadmin/descriptions";
|
||||||
|
|
||||||
import "animate.css";
|
|
||||||
// 引入重置样式
|
// 引入重置样式
|
||||||
import "./style/reset.scss";
|
import "./style/reset.scss";
|
||||||
// 导入公共样式
|
// 导入公共样式
|
||||||
import "./style/index.scss";
|
import "./style/index.scss";
|
||||||
|
// 一定要在main.ts中导入tailwind.css,防止vite每次hmr都会请求src/style/index.scss整体css文件导致热更新慢的问题
|
||||||
|
import "./style/tailwind.css";
|
||||||
import "element-plus/dist/index.css";
|
import "element-plus/dist/index.css";
|
||||||
|
|
||||||
// 导入字体图标
|
// 导入字体图标
|
||||||
import "./assets/iconfont/iconfont.js";
|
import "./assets/iconfont/iconfont.js";
|
||||||
import "./assets/iconfont/iconfont.css";
|
import "./assets/iconfont/iconfont.css";
|
||||||
@@ -54,6 +53,5 @@ getServerConfig(app).then(async config => {
|
|||||||
// .use(useEcharts);
|
// .use(useEcharts);
|
||||||
// .use(Table);
|
// .use(Table);
|
||||||
// .use(PureDescriptions);
|
// .use(PureDescriptions);
|
||||||
// .use(useTable);
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { App } from "vue";
|
import type { App } from "vue";
|
||||||
import * as echarts from "echarts/core";
|
import * as echarts from "echarts/core";
|
||||||
import { SVGRenderer } from "echarts/renderers";
|
import { CanvasRenderer } from "echarts/renderers";
|
||||||
import { PieChart, BarChart, LineChart } from "echarts/charts";
|
import { PieChart, BarChart, LineChart } from "echarts/charts";
|
||||||
import {
|
import {
|
||||||
GridComponent,
|
GridComponent,
|
||||||
@@ -19,7 +19,7 @@ use([
|
|||||||
PieChart,
|
PieChart,
|
||||||
BarChart,
|
BarChart,
|
||||||
LineChart,
|
LineChart,
|
||||||
SVGRenderer,
|
CanvasRenderer,
|
||||||
GridComponent,
|
GridComponent,
|
||||||
TitleComponent,
|
TitleComponent,
|
||||||
LegendComponent,
|
LegendComponent,
|
||||||
@@ -33,6 +33,7 @@ use([
|
|||||||
/**
|
/**
|
||||||
* @description 按需引入echarts
|
* @description 按需引入echarts
|
||||||
* @see {@link https://echarts.apache.org/handbook/zh/basics/import#%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5-echarts-%E5%9B%BE%E8%A1%A8%E5%92%8C%E7%BB%84%E4%BB%B6}
|
* @see {@link https://echarts.apache.org/handbook/zh/basics/import#%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5-echarts-%E5%9B%BE%E8%A1%A8%E5%92%8C%E7%BB%84%E4%BB%B6}
|
||||||
|
* @see 温馨提示:必须将 `$echarts` 添加到全局 `globalProperties` ,为了配合 https://pure-admin-utils.netlify.app/hooks/useEcharts/useEcharts.html 使用
|
||||||
*/
|
*/
|
||||||
export function useEcharts(app: App) {
|
export function useEcharts(app: App) {
|
||||||
app.config.globalProperties.$echarts = echarts;
|
app.config.globalProperties.$echarts = echarts;
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
@import "vxe-table/styles/variable.scss";
|
|
||||||
@import "vxe-table/styles/modules.scss";
|
|
||||||
|
|
||||||
i {
|
|
||||||
border-color: initial;
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import "xe-utils";
|
|
||||||
import "./index.scss";
|
|
||||||
import { App } from "vue";
|
|
||||||
import "font-awesome/css/font-awesome.min.css";
|
|
||||||
|
|
||||||
import {
|
|
||||||
// 核心
|
|
||||||
VXETable,
|
|
||||||
// 表格功能
|
|
||||||
Icon,
|
|
||||||
Filter,
|
|
||||||
Edit,
|
|
||||||
Menu,
|
|
||||||
Export,
|
|
||||||
Keyboard,
|
|
||||||
Validator,
|
|
||||||
// 可选组件
|
|
||||||
Column,
|
|
||||||
Colgroup,
|
|
||||||
Grid,
|
|
||||||
Tooltip,
|
|
||||||
Toolbar,
|
|
||||||
Pager,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormGather,
|
|
||||||
Checkbox,
|
|
||||||
CheckboxGroup,
|
|
||||||
Radio,
|
|
||||||
RadioGroup,
|
|
||||||
RadioButton,
|
|
||||||
Switch,
|
|
||||||
Input,
|
|
||||||
Select,
|
|
||||||
Optgroup,
|
|
||||||
Option,
|
|
||||||
Textarea,
|
|
||||||
Button,
|
|
||||||
Modal,
|
|
||||||
List,
|
|
||||||
Pulldown,
|
|
||||||
// 表格
|
|
||||||
Table
|
|
||||||
} from "vxe-table";
|
|
||||||
|
|
||||||
// 全局默认参数
|
|
||||||
VXETable.setup({
|
|
||||||
size: "medium",
|
|
||||||
version: 0,
|
|
||||||
zIndex: 1002,
|
|
||||||
table: {
|
|
||||||
// 自动监听父元素的变化去重新计算表格
|
|
||||||
autoResize: true,
|
|
||||||
// 鼠标移到行是否要高亮显示
|
|
||||||
highlightHoverRow: true
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
clearable: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export function useTable(app: App) {
|
|
||||||
app
|
|
||||||
.use(Icon)
|
|
||||||
.use(Filter)
|
|
||||||
.use(Edit)
|
|
||||||
.use(Menu)
|
|
||||||
.use(Export)
|
|
||||||
.use(Keyboard)
|
|
||||||
.use(Validator)
|
|
||||||
// 可选组件
|
|
||||||
.use(Column)
|
|
||||||
.use(Colgroup)
|
|
||||||
.use(Grid)
|
|
||||||
.use(Tooltip)
|
|
||||||
.use(Toolbar)
|
|
||||||
.use(Pager)
|
|
||||||
.use(Form)
|
|
||||||
.use(FormItem)
|
|
||||||
.use(FormGather)
|
|
||||||
.use(Checkbox)
|
|
||||||
.use(CheckboxGroup)
|
|
||||||
.use(Radio)
|
|
||||||
.use(RadioGroup)
|
|
||||||
.use(RadioButton)
|
|
||||||
.use(Switch)
|
|
||||||
.use(Input)
|
|
||||||
.use(Select)
|
|
||||||
.use(Optgroup)
|
|
||||||
.use(Option)
|
|
||||||
.use(Textarea)
|
|
||||||
.use(Button)
|
|
||||||
.use(Modal)
|
|
||||||
.use(List)
|
|
||||||
.use(Pulldown)
|
|
||||||
// 安装表格
|
|
||||||
.use(Table);
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
// import "@/utils/sso";
|
// import "@/utils/sso";
|
||||||
import { getConfig } from "@/config";
|
import { getConfig } from "@/config";
|
||||||
import NProgress from "@/utils/progress";
|
import NProgress from "@/utils/progress";
|
||||||
import { findIndex } from "lodash-unified";
|
|
||||||
import { sessionKey, type DataInfo } from "@/utils/auth";
|
import { sessionKey, type DataInfo } from "@/utils/auth";
|
||||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||||
@@ -13,6 +12,7 @@ import {
|
|||||||
} from "vue-router";
|
} from "vue-router";
|
||||||
import {
|
import {
|
||||||
ascending,
|
ascending,
|
||||||
|
getTopMenu,
|
||||||
initRouter,
|
initRouter,
|
||||||
isOneOfArray,
|
isOneOfArray,
|
||||||
getHistoryMode,
|
getHistoryMode,
|
||||||
@@ -61,7 +61,7 @@ export const remainingPaths = Object.keys(remainingRouter).map(v => {
|
|||||||
|
|
||||||
/** 创建路由实例 */
|
/** 创建路由实例 */
|
||||||
export const router: Router = createRouter({
|
export const router: Router = createRouter({
|
||||||
history: getHistoryMode(),
|
history: getHistoryMode(import.meta.env.VITE_ROUTER_HISTORY),
|
||||||
routes: constantRoutes.concat(...(remainingRouter as any)),
|
routes: constantRoutes.concat(...(remainingRouter as any)),
|
||||||
strict: true,
|
strict: true,
|
||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
@@ -96,16 +96,17 @@ export function resetRouter() {
|
|||||||
/** 路由白名单 */
|
/** 路由白名单 */
|
||||||
const whiteList = ["/login"];
|
const whiteList = ["/login"];
|
||||||
|
|
||||||
|
const { VITE_HIDE_HOME } = import.meta.env;
|
||||||
|
|
||||||
router.beforeEach((to: toRouteType, _from, next) => {
|
router.beforeEach((to: toRouteType, _from, next) => {
|
||||||
if (to.meta?.keepAlive) {
|
if (to.meta?.keepAlive) {
|
||||||
const newMatched = to.matched;
|
handleAliveRoute(to, "add");
|
||||||
handleAliveRoute(newMatched, "add");
|
|
||||||
// 页面整体刷新和点击标签页刷新
|
// 页面整体刷新和点击标签页刷新
|
||||||
if (_from.name === undefined || _from.name === "Redirect") {
|
if (_from.name === undefined || _from.name === "Redirect") {
|
||||||
handleAliveRoute(newMatched);
|
handleAliveRoute(to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const userInfo = storageSession.getItem<DataInfo<number>>(sessionKey);
|
const userInfo = storageSession().getItem<DataInfo<number>>(sessionKey);
|
||||||
NProgress.start();
|
NProgress.start();
|
||||||
const externalLink = isUrl(to?.name as string);
|
const externalLink = isUrl(to?.name as string);
|
||||||
if (!externalLink) {
|
if (!externalLink) {
|
||||||
@@ -125,6 +126,10 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
|||||||
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
|
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
|
||||||
next({ path: "/error/403" });
|
next({ path: "/error/403" });
|
||||||
}
|
}
|
||||||
|
// 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面
|
||||||
|
if (VITE_HIDE_HOME === "true" && to.fullPath === "/welcome") {
|
||||||
|
next({ path: "/error/404" });
|
||||||
|
}
|
||||||
if (_from?.name) {
|
if (_from?.name) {
|
||||||
// name为超链接
|
// name为超链接
|
||||||
if (externalLink) {
|
if (externalLink) {
|
||||||
@@ -138,18 +143,15 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
|||||||
if (
|
if (
|
||||||
usePermissionStoreHook().wholeMenus.length === 0 &&
|
usePermissionStoreHook().wholeMenus.length === 0 &&
|
||||||
to.path !== "/login"
|
to.path !== "/login"
|
||||||
)
|
) {
|
||||||
initRouter().then((router: Router) => {
|
initRouter().then((router: Router) => {
|
||||||
if (!useMultiTagsStoreHook().getMultiTagsCache) {
|
if (!useMultiTagsStoreHook().getMultiTagsCache) {
|
||||||
const { path } = to;
|
const { path } = to;
|
||||||
const index = findIndex(remainingRouter, v => {
|
const route = findRouteByPath(
|
||||||
return v.path == path;
|
path,
|
||||||
});
|
router.options.routes[0].children
|
||||||
const routes: any =
|
);
|
||||||
index === -1
|
getTopMenu(true);
|
||||||
? router.options.routes[0].children
|
|
||||||
: router.options.routes;
|
|
||||||
const route = findRouteByPath(path, routes);
|
|
||||||
// query、params模式路由传参数的标签页不在此处处理
|
// query、params模式路由传参数的标签页不在此处处理
|
||||||
if (route && route.meta?.title) {
|
if (route && route.meta?.title) {
|
||||||
useMultiTagsStoreHook().handleTags("push", {
|
useMultiTagsStoreHook().handleTags("push", {
|
||||||
@@ -161,6 +163,7 @@ router.beforeEach((to: toRouteType, _from, next) => {
|
|||||||
}
|
}
|
||||||
router.push(to.fullPath);
|
router.push(to.fullPath);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
toCorrectRoute();
|
toCorrectRoute();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ export default {
|
|||||||
path: "/error",
|
path: "/error",
|
||||||
redirect: "/error/403",
|
redirect: "/error/403",
|
||||||
meta: {
|
meta: {
|
||||||
icon: "information-line",
|
icon: "informationLine",
|
||||||
title: "异常页面",
|
title: "异常页面",
|
||||||
|
// showLink: false,
|
||||||
rank: 9
|
rank: 9
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
const { VITE_HIDE_HOME } = import.meta.env;
|
||||||
const Layout = () => import("@/layout/index.vue");
|
const Layout = () => import("@/layout/index.vue");
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -6,7 +7,7 @@ export default {
|
|||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: "/welcome",
|
redirect: "/welcome",
|
||||||
meta: {
|
meta: {
|
||||||
icon: "home-filled",
|
icon: "homeFilled",
|
||||||
title: "首页",
|
title: "首页",
|
||||||
rank: 0
|
rank: 0
|
||||||
},
|
},
|
||||||
@@ -16,7 +17,8 @@ export default {
|
|||||||
name: "Welcome",
|
name: "Welcome",
|
||||||
component: () => import("@/views/welcome/index.vue"),
|
component: () => import("@/views/welcome/index.vue"),
|
||||||
meta: {
|
meta: {
|
||||||
title: "首页"
|
title: "首页",
|
||||||
|
showLink: VITE_HIDE_HOME === "true" ? false : true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,10 +15,9 @@ export default [
|
|||||||
path: "/redirect",
|
path: "/redirect",
|
||||||
component: Layout,
|
component: Layout,
|
||||||
meta: {
|
meta: {
|
||||||
icon: "home-filled",
|
title: "加载中...",
|
||||||
title: "首页",
|
|
||||||
showLink: false,
|
showLink: false,
|
||||||
rank: 104
|
rank: 102
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,22 +3,24 @@ import {
|
|||||||
RouteRecordRaw,
|
RouteRecordRaw,
|
||||||
RouteComponent,
|
RouteComponent,
|
||||||
createWebHistory,
|
createWebHistory,
|
||||||
createWebHashHistory,
|
createWebHashHistory
|
||||||
RouteRecordNormalized
|
|
||||||
} from "vue-router";
|
} from "vue-router";
|
||||||
import { router } from "./index";
|
import { router } from "./index";
|
||||||
import { isProxy, toRaw } from "vue";
|
import { isProxy, toRaw } from "vue";
|
||||||
import { loadEnv } from "../../build";
|
|
||||||
import { useTimeoutFn } from "@vueuse/core";
|
import { useTimeoutFn } from "@vueuse/core";
|
||||||
import { RouteConfigs } from "@/layout/types";
|
|
||||||
import {
|
import {
|
||||||
isString,
|
isString,
|
||||||
|
cloneDeep,
|
||||||
|
isAllEmpty,
|
||||||
|
intersection,
|
||||||
storageSession,
|
storageSession,
|
||||||
isIncludeAllChildren
|
isIncludeAllChildren
|
||||||
} from "@pureadmin/utils";
|
} from "@pureadmin/utils";
|
||||||
|
import { getConfig } from "@/config";
|
||||||
|
import { menuType } from "@/layout/types";
|
||||||
import { buildHierarchyTree } from "@/utils/tree";
|
import { buildHierarchyTree } from "@/utils/tree";
|
||||||
import { cloneDeep, intersection } from "lodash-unified";
|
|
||||||
import { sessionKey, type DataInfo } from "@/utils/auth";
|
import { sessionKey, type DataInfo } from "@/utils/auth";
|
||||||
|
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||||
const IFrame = () => import("@/layout/frameView.vue");
|
const IFrame = () => import("@/layout/frameView.vue");
|
||||||
// https://cn.vitejs.dev/guide/features.html#glob-import
|
// https://cn.vitejs.dev/guide/features.html#glob-import
|
||||||
@@ -27,19 +29,25 @@ const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}");
|
|||||||
// 动态路由
|
// 动态路由
|
||||||
import { getAsyncRoutes } from "@/api/routes";
|
import { getAsyncRoutes } from "@/api/routes";
|
||||||
|
|
||||||
|
function handRank(routeInfo: any) {
|
||||||
|
const { name, path, parentId, meta } = routeInfo;
|
||||||
|
return isAllEmpty(parentId)
|
||||||
|
? isAllEmpty(meta?.rank) ||
|
||||||
|
(meta?.rank === 0 && name !== "Home" && path !== "/")
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
/** 按照路由中meta下的rank等级升序来排序路由 */
|
/** 按照路由中meta下的rank等级升序来排序路由 */
|
||||||
function ascending(arr: any[]) {
|
function ascending(arr: any[]) {
|
||||||
arr.forEach(v => {
|
arr.forEach((v, index) => {
|
||||||
if (v?.meta?.rank === null) v.meta.rank = undefined;
|
// 当rank不存在时,根据顺序自动创建,首页路由永远在第一位
|
||||||
if (v?.meta?.rank === 0) {
|
if (handRank(v)) v.meta.rank = index + 2;
|
||||||
if (v.name !== "Home" && v.path !== "/") {
|
|
||||||
console.warn("rank only the home page can be 0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return arr.sort(
|
return arr.sort(
|
||||||
(a: { meta: { rank: number } }, b: { meta: { rank: number } }) => {
|
(a: { meta: { rank: number } }, b: { meta: { rank: number } }) => {
|
||||||
return a?.meta?.rank - b?.meta?.rank;
|
return a?.meta.rank - b?.meta.rank;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -76,7 +84,7 @@ function isOneOfArray(a: Array<string>, b: Array<string>) {
|
|||||||
/** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */
|
/** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */
|
||||||
function filterNoPermissionTree(data: RouteComponent[]) {
|
function filterNoPermissionTree(data: RouteComponent[]) {
|
||||||
const currentRoles =
|
const currentRoles =
|
||||||
storageSession.getItem<DataInfo<number>>(sessionKey)?.roles ?? [];
|
storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? [];
|
||||||
const newTree = cloneDeep(data).filter((v: any) =>
|
const newTree = cloneDeep(data).filter((v: any) =>
|
||||||
isOneOfArray(v.meta?.roles, currentRoles)
|
isOneOfArray(v.meta?.roles, currentRoles)
|
||||||
);
|
);
|
||||||
@@ -86,30 +94,20 @@ function filterNoPermissionTree(data: RouteComponent[]) {
|
|||||||
return filterChildrenTree(newTree);
|
return filterChildrenTree(newTree);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 批量删除缓存路由(keepalive) */
|
/** 通过指定 `key` 获取父级路径集合,默认 `key` 为 `path` */
|
||||||
function delAliveRoutes(delAliveRouteList: Array<RouteConfigs>) {
|
function getParentPaths(value: string, routes: RouteRecordRaw[], key = "path") {
|
||||||
delAliveRouteList.forEach(route => {
|
|
||||||
usePermissionStoreHook().cacheOperate({
|
|
||||||
mode: "delete",
|
|
||||||
name: route?.name
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 通过path获取父级路径 */
|
|
||||||
function getParentPaths(path: string, routes: RouteRecordRaw[]) {
|
|
||||||
// 深度遍历查找
|
// 深度遍历查找
|
||||||
function dfs(routes: RouteRecordRaw[], path: string, parents: string[]) {
|
function dfs(routes: RouteRecordRaw[], value: string, parents: string[]) {
|
||||||
for (let i = 0; i < routes.length; i++) {
|
for (let i = 0; i < routes.length; i++) {
|
||||||
const item = routes[i];
|
const item = routes[i];
|
||||||
// 找到path则返回父级path
|
// 返回父级path
|
||||||
if (item.path === path) return parents;
|
if (item[key] === value) return parents;
|
||||||
// children不存在或为空则不递归
|
// children不存在或为空则不递归
|
||||||
if (!item.children || !item.children.length) continue;
|
if (!item.children || !item.children.length) continue;
|
||||||
// 往下查找时将当前path入栈
|
// 往下查找时将当前path入栈
|
||||||
parents.push(item.path);
|
parents.push(item.path);
|
||||||
|
|
||||||
if (dfs(item.children, path, parents).length) return parents;
|
if (dfs(item.children, value, parents).length) return parents;
|
||||||
// 深度遍历查找未找到时当前path 出栈
|
// 深度遍历查找未找到时当前path 出栈
|
||||||
parents.pop();
|
parents.pop();
|
||||||
}
|
}
|
||||||
@@ -117,10 +115,10 @@ function getParentPaths(path: string, routes: RouteRecordRaw[]) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return dfs(routes, path, []);
|
return dfs(routes, value, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查找对应path的路由信息 */
|
/** 查找对应 `path` 的路由信息 */
|
||||||
function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
|
function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
|
||||||
let res = routes.find((item: { path: string }) => item.path == path);
|
let res = routes.find((item: { path: string }) => item.path == path);
|
||||||
if (res) {
|
if (res) {
|
||||||
@@ -151,42 +149,66 @@ function addPathMatch() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化路由 */
|
/** 处理动态路由(后端返回的路由) */
|
||||||
function initRouter() {
|
function handleAsyncRoutes(routeList) {
|
||||||
return new Promise(resolve => {
|
if (routeList.length === 0) {
|
||||||
getAsyncRoutes().then(({ data }) => {
|
usePermissionStoreHook().handleWholeMenus(routeList);
|
||||||
if (data.length === 0) {
|
} else {
|
||||||
usePermissionStoreHook().handleWholeMenus(data);
|
formatFlatteningRoutes(addAsyncRoutes(routeList)).map(
|
||||||
resolve(router);
|
(v: RouteRecordRaw) => {
|
||||||
} else {
|
// 防止重复添加路由
|
||||||
formatFlatteningRoutes(addAsyncRoutes(data)).map(
|
if (
|
||||||
(v: RouteRecordRaw) => {
|
router.options.routes[0].children.findIndex(
|
||||||
// 防止重复添加路由
|
value => value.path === v.path
|
||||||
if (
|
) !== -1
|
||||||
router.options.routes[0].children.findIndex(
|
) {
|
||||||
value => value.path === v.path
|
return;
|
||||||
) !== -1
|
} else {
|
||||||
) {
|
// 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转
|
||||||
return;
|
router.options.routes[0].children.push(v);
|
||||||
} else {
|
// 最终路由进行升序
|
||||||
// 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转
|
ascending(router.options.routes[0].children);
|
||||||
router.options.routes[0].children.push(v);
|
if (!router.hasRoute(v?.name)) router.addRoute(v);
|
||||||
// 最终路由进行升序
|
const flattenRouters: any = router
|
||||||
ascending(router.options.routes[0].children);
|
.getRoutes()
|
||||||
if (!router.hasRoute(v?.name)) router.addRoute(v);
|
.find(n => n.path === "/");
|
||||||
const flattenRouters: any = router
|
router.addRoute(flattenRouters);
|
||||||
.getRoutes()
|
}
|
||||||
.find(n => n.path === "/");
|
|
||||||
router.addRoute(flattenRouters);
|
|
||||||
}
|
|
||||||
resolve(router);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
usePermissionStoreHook().handleWholeMenus(data);
|
|
||||||
}
|
}
|
||||||
addPathMatch();
|
);
|
||||||
|
usePermissionStoreHook().handleWholeMenus(routeList);
|
||||||
|
}
|
||||||
|
addPathMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化路由(`new Promise` 写法防止在异步请求中造成无限循环)*/
|
||||||
|
function initRouter() {
|
||||||
|
if (getConfig()?.CachingAsyncRoutes) {
|
||||||
|
// 开启动态路由缓存本地sessionStorage
|
||||||
|
const key = "async-routes";
|
||||||
|
const asyncRouteList = storageSession().getItem(key) as any;
|
||||||
|
if (asyncRouteList && asyncRouteList?.length > 0) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
handleAsyncRoutes(asyncRouteList);
|
||||||
|
resolve(router);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
getAsyncRoutes().then(({ data }) => {
|
||||||
|
handleAsyncRoutes(cloneDeep(data));
|
||||||
|
storageSession().setItem(key, data);
|
||||||
|
resolve(router);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
getAsyncRoutes().then(({ data }) => {
|
||||||
|
handleAsyncRoutes(cloneDeep(data));
|
||||||
|
resolve(router);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,7 +231,7 @@ function formatFlatteningRoutes(routesList: RouteRecordRaw[]) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 一维数组处理成多级嵌套数组(三级及以上的路由全部拍成二级,keep-alive 只支持到二级缓存)
|
* 一维数组处理成多级嵌套数组(三级及以上的路由全部拍成二级,keep-alive 只支持到二级缓存)
|
||||||
* https://github.com/xiaoxian521/vue-pure-admin/issues/67
|
* https://github.com/pure-admin/vue-pure-admin/issues/67
|
||||||
* @param routesList 处理后的一维路由菜单数组
|
* @param routesList 处理后的一维路由菜单数组
|
||||||
* @returns 返回将一维数组重新处理成规定路由的格式
|
* @returns 返回将一维数组重新处理成规定路由的格式
|
||||||
*/
|
*/
|
||||||
@@ -227,34 +249,42 @@ function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
|
|||||||
children: []
|
children: []
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newRoutesList[0].children.push({ ...v });
|
newRoutesList[0]?.children.push({ ...v });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return newRoutesList;
|
return newRoutesList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 处理缓存路由(添加、删除、刷新) */
|
/** 处理缓存路由(添加、删除、刷新) */
|
||||||
function handleAliveRoute(matched: RouteRecordNormalized[], mode?: string) {
|
function handleAliveRoute({ name }: toRouteType, mode?: string) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "add":
|
case "add":
|
||||||
matched.forEach(v => {
|
usePermissionStoreHook().cacheOperate({
|
||||||
usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
|
mode: "add",
|
||||||
|
name
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
usePermissionStoreHook().cacheOperate({
|
usePermissionStoreHook().cacheOperate({
|
||||||
mode: "delete",
|
mode: "delete",
|
||||||
name: matched[matched.length - 1].name
|
name
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "refresh":
|
||||||
|
usePermissionStoreHook().cacheOperate({
|
||||||
|
mode: "refresh",
|
||||||
|
name
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
usePermissionStoreHook().cacheOperate({
|
usePermissionStoreHook().cacheOperate({
|
||||||
mode: "delete",
|
mode: "delete",
|
||||||
name: matched[matched.length - 1].name
|
name
|
||||||
});
|
});
|
||||||
useTimeoutFn(() => {
|
useTimeoutFn(() => {
|
||||||
matched.forEach(v => {
|
usePermissionStoreHook().cacheOperate({
|
||||||
usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
|
mode: "add",
|
||||||
|
name
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@@ -290,8 +320,7 @@ function addAsyncRoutes(arrRoutes: Array<RouteRecordRaw>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html */
|
/** 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html */
|
||||||
function getHistoryMode(): RouterHistory {
|
function getHistoryMode(routerHistory): RouterHistory {
|
||||||
const routerHistory = loadEnv().VITE_ROUTER_HISTORY;
|
|
||||||
// len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1
|
// len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1
|
||||||
const historyMode = routerHistory.split(",");
|
const historyMode = routerHistory.split(",");
|
||||||
const leftMode = historyMode[0];
|
const leftMode = historyMode[0];
|
||||||
@@ -323,22 +352,31 @@ function hasAuth(value: string | Array<string>): boolean {
|
|||||||
if (!value) return false;
|
if (!value) return false;
|
||||||
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */
|
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */
|
||||||
const metaAuths = getAuths();
|
const metaAuths = getAuths();
|
||||||
|
if (!metaAuths) return false;
|
||||||
const isAuths = isString(value)
|
const isAuths = isString(value)
|
||||||
? metaAuths.includes(value)
|
? metaAuths.includes(value)
|
||||||
: isIncludeAllChildren(value, metaAuths);
|
: isIncludeAllChildren(value, metaAuths);
|
||||||
return isAuths ? true : false;
|
return isAuths ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取所有菜单中的第一个菜单(顶级菜单)*/
|
||||||
|
function getTopMenu(tag = false): menuType {
|
||||||
|
const topMenu = usePermissionStoreHook().wholeMenus[0]?.children[0];
|
||||||
|
tag && useMultiTagsStoreHook().handleTags("push", topMenu);
|
||||||
|
return topMenu;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
hasAuth,
|
hasAuth,
|
||||||
getAuths,
|
getAuths,
|
||||||
ascending,
|
ascending,
|
||||||
filterTree,
|
filterTree,
|
||||||
initRouter,
|
initRouter,
|
||||||
|
getTopMenu,
|
||||||
|
addPathMatch,
|
||||||
isOneOfArray,
|
isOneOfArray,
|
||||||
getHistoryMode,
|
getHistoryMode,
|
||||||
addAsyncRoutes,
|
addAsyncRoutes,
|
||||||
delAliveRoutes,
|
|
||||||
getParentPaths,
|
getParentPaths,
|
||||||
findRouteByPath,
|
findRouteByPath,
|
||||||
handleAliveRoute,
|
handleAliveRoute,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
import { appType } from "./types";
|
import { appType } from "./types";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { getConfig } from "@/config";
|
import { getConfig, responsiveStorageNameSpace } from "@/config";
|
||||||
import { deviceDetection, storageLocal } from "@pureadmin/utils";
|
import { deviceDetection, storageLocal } from "@pureadmin/utils";
|
||||||
|
|
||||||
export const useAppStore = defineStore({
|
export const useAppStore = defineStore({
|
||||||
@@ -9,28 +9,32 @@ export const useAppStore = defineStore({
|
|||||||
state: (): appType => ({
|
state: (): appType => ({
|
||||||
sidebar: {
|
sidebar: {
|
||||||
opened:
|
opened:
|
||||||
storageLocal.getItem<StorageConfigs>("responsive-layout")
|
storageLocal().getItem<StorageConfigs>(
|
||||||
?.sidebarStatus ?? getConfig().SidebarStatus,
|
`${responsiveStorageNameSpace()}layout`
|
||||||
|
)?.sidebarStatus ?? getConfig().SidebarStatus,
|
||||||
withoutAnimation: false,
|
withoutAnimation: false,
|
||||||
isClickCollapse: false
|
isClickCollapse: false
|
||||||
},
|
},
|
||||||
// 这里的layout用于监听容器拖拉后恢复对应的导航模式
|
// 这里的layout用于监听容器拖拉后恢复对应的导航模式
|
||||||
layout:
|
layout:
|
||||||
storageLocal.getItem<StorageConfigs>("responsive-layout")?.layout ??
|
storageLocal().getItem<StorageConfigs>(
|
||||||
getConfig().Layout,
|
`${responsiveStorageNameSpace()}layout`
|
||||||
|
)?.layout ?? getConfig().Layout,
|
||||||
device: deviceDetection() ? "mobile" : "desktop"
|
device: deviceDetection() ? "mobile" : "desktop"
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getSidebarStatus() {
|
getSidebarStatus(state) {
|
||||||
return this.sidebar.opened;
|
return state.sidebar.opened;
|
||||||
},
|
},
|
||||||
getDevice() {
|
getDevice(state) {
|
||||||
return this.device;
|
return state.device;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
TOGGLE_SIDEBAR(opened?: boolean, resize?: string) {
|
TOGGLE_SIDEBAR(opened?: boolean, resize?: string) {
|
||||||
const layout = storageLocal.getItem<StorageConfigs>("responsive-layout");
|
const layout = storageLocal().getItem<StorageConfigs>(
|
||||||
|
`${responsiveStorageNameSpace()}layout`
|
||||||
|
);
|
||||||
if (opened && resize) {
|
if (opened && resize) {
|
||||||
this.sidebar.withoutAnimation = true;
|
this.sidebar.withoutAnimation = true;
|
||||||
this.sidebar.opened = true;
|
this.sidebar.opened = true;
|
||||||
@@ -45,7 +49,7 @@ export const useAppStore = defineStore({
|
|||||||
this.sidebar.isClickCollapse = !this.sidebar.opened;
|
this.sidebar.isClickCollapse = !this.sidebar.opened;
|
||||||
layout.sidebarStatus = this.sidebar.opened;
|
layout.sidebarStatus = this.sidebar.opened;
|
||||||
}
|
}
|
||||||
storageLocal.setItem("responsive-layout", layout);
|
storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout);
|
||||||
},
|
},
|
||||||
async toggleSideBar(opened?: boolean, resize?: string) {
|
async toggleSideBar(opened?: boolean, resize?: string) {
|
||||||
await this.TOGGLE_SIDEBAR(opened, resize);
|
await this.TOGGLE_SIDEBAR(opened, resize);
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { getConfig } from "@/config";
|
|
||||||
import { storageLocal } from "@pureadmin/utils";
|
import { storageLocal } from "@pureadmin/utils";
|
||||||
|
import { getConfig, responsiveStorageNameSpace } from "@/config";
|
||||||
|
|
||||||
export const useEpThemeStore = defineStore({
|
export const useEpThemeStore = defineStore({
|
||||||
id: "pure-epTheme",
|
id: "pure-epTheme",
|
||||||
state: () => ({
|
state: () => ({
|
||||||
epThemeColor:
|
epThemeColor:
|
||||||
storageLocal.getItem<StorageConfigs>("responsive-layout")?.epThemeColor ??
|
storageLocal().getItem<StorageConfigs>(
|
||||||
getConfig().EpThemeColor,
|
`${responsiveStorageNameSpace()}layout`
|
||||||
|
)?.epThemeColor ?? getConfig().EpThemeColor,
|
||||||
epTheme:
|
epTheme:
|
||||||
storageLocal.getItem<StorageConfigs>("responsive-layout")?.theme ??
|
storageLocal().getItem<StorageConfigs>(
|
||||||
getConfig().Theme
|
`${responsiveStorageNameSpace()}layout`
|
||||||
|
)?.theme ?? getConfig().Theme
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getEpThemeColor() {
|
getEpThemeColor(state) {
|
||||||
return this.epThemeColor;
|
return state.epThemeColor;
|
||||||
},
|
},
|
||||||
/** 用于mix导航模式下hamburger-svg的fill属性 */
|
/** 用于mix导航模式下hamburger-svg的fill属性 */
|
||||||
fill() {
|
fill(state) {
|
||||||
if (this.epTheme === "light") {
|
if (state.epTheme === "light") {
|
||||||
return "#409eff";
|
return "#409eff";
|
||||||
} else if (this.epTheme === "yellow") {
|
} else if (state.epTheme === "yellow") {
|
||||||
return "#d25f00";
|
return "#d25f00";
|
||||||
} else {
|
} else {
|
||||||
return "#fff";
|
return "#fff";
|
||||||
@@ -30,12 +32,14 @@ export const useEpThemeStore = defineStore({
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setEpThemeColor(newColor: string): void {
|
setEpThemeColor(newColor: string): void {
|
||||||
const layout = storageLocal.getItem<StorageConfigs>("responsive-layout");
|
const layout = storageLocal().getItem<StorageConfigs>(
|
||||||
|
`${responsiveStorageNameSpace()}layout`
|
||||||
|
);
|
||||||
this.epTheme = layout?.theme;
|
this.epTheme = layout?.theme;
|
||||||
this.epThemeColor = newColor;
|
this.epThemeColor = newColor;
|
||||||
if (!layout) return;
|
if (!layout) return;
|
||||||
layout.epThemeColor = newColor;
|
layout.epThemeColor = newColor;
|
||||||
storageLocal.setItem("responsive-layout", layout);
|
storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,38 +1,48 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
import { isEqual } from "@pureadmin/utils";
|
|
||||||
import { routerArrays } from "@/layout/types";
|
import { routerArrays } from "@/layout/types";
|
||||||
import { multiType, positionType } from "./types";
|
import { multiType, positionType } from "./types";
|
||||||
import { isUrl, storageLocal } from "@pureadmin/utils";
|
import { responsiveStorageNameSpace } from "@/config";
|
||||||
|
import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils";
|
||||||
|
|
||||||
export const useMultiTagsStore = defineStore({
|
export const useMultiTagsStore = defineStore({
|
||||||
id: "pure-multiTags",
|
id: "pure-multiTags",
|
||||||
state: () => ({
|
state: () => ({
|
||||||
// 存储标签页信息(路由信息)
|
// 存储标签页信息(路由信息)
|
||||||
multiTags: storageLocal.getItem<StorageConfigs>("responsive-configure")
|
multiTags: storageLocal().getItem<StorageConfigs>(
|
||||||
?.multiTagsCache
|
`${responsiveStorageNameSpace()}configure`
|
||||||
? storageLocal.getItem<StorageConfigs>("responsive-tags")
|
)?.multiTagsCache
|
||||||
|
? storageLocal().getItem<StorageConfigs>(
|
||||||
|
`${responsiveStorageNameSpace()}tags`
|
||||||
|
)
|
||||||
: [...routerArrays],
|
: [...routerArrays],
|
||||||
multiTagsCache: storageLocal.getItem<StorageConfigs>("responsive-configure")
|
multiTagsCache: storageLocal().getItem<StorageConfigs>(
|
||||||
?.multiTagsCache
|
`${responsiveStorageNameSpace()}configure`
|
||||||
|
)?.multiTagsCache
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getMultiTagsCache() {
|
getMultiTagsCache(state) {
|
||||||
return this.multiTagsCache;
|
return state.multiTagsCache;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
multiTagsCacheChange(multiTagsCache: boolean) {
|
multiTagsCacheChange(multiTagsCache: boolean) {
|
||||||
this.multiTagsCache = multiTagsCache;
|
this.multiTagsCache = multiTagsCache;
|
||||||
if (multiTagsCache) {
|
if (multiTagsCache) {
|
||||||
storageLocal.setItem("responsive-tags", this.multiTags);
|
storageLocal().setItem(
|
||||||
|
`${responsiveStorageNameSpace()}tags`,
|
||||||
|
this.multiTags
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
storageLocal.removeItem("responsive-tags");
|
storageLocal().removeItem(`${responsiveStorageNameSpace()}tags`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tagsCache(multiTags) {
|
tagsCache(multiTags) {
|
||||||
this.getMultiTagsCache &&
|
this.getMultiTagsCache &&
|
||||||
storageLocal.setItem("responsive-tags", multiTags);
|
storageLocal().setItem(
|
||||||
|
`${responsiveStorageNameSpace()}tags`,
|
||||||
|
multiTags
|
||||||
|
);
|
||||||
},
|
},
|
||||||
handleTags<T>(
|
handleTags<T>(
|
||||||
mode: string,
|
mode: string,
|
||||||
@@ -53,6 +63,9 @@ export const useMultiTagsStore = defineStore({
|
|||||||
if (isUrl(tagVal?.name)) return;
|
if (isUrl(tagVal?.name)) return;
|
||||||
// 如果title为空拒绝添加空信息到标签页
|
// 如果title为空拒绝添加空信息到标签页
|
||||||
if (tagVal?.meta?.title.length === 0) return;
|
if (tagVal?.meta?.title.length === 0) return;
|
||||||
|
// showLink:false 不添加到标签页
|
||||||
|
if (isBoolean(tagVal?.meta?.showLink) && !tagVal?.meta?.showLink)
|
||||||
|
return;
|
||||||
const tagPath = tagVal.path;
|
const tagPath = tagVal.path;
|
||||||
// 判断tag是否已存在
|
// 判断tag是否已存在
|
||||||
const tagHasExits = this.multiTags.some(tag => {
|
const tagHasExits = this.multiTags.some(tag => {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { defineStore } from "pinia";
|
|||||||
import { store } from "@/store";
|
import { store } from "@/store";
|
||||||
import { cacheType } from "./types";
|
import { cacheType } from "./types";
|
||||||
import { constantMenus } from "@/router";
|
import { constantMenus } from "@/router";
|
||||||
|
import { getKeyList } from "@pureadmin/utils";
|
||||||
|
import { useMultiTagsStoreHook } from "./multiTags";
|
||||||
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
|
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
|
||||||
|
|
||||||
export const usePermissionStore = defineStore({
|
export const usePermissionStore = defineStore({
|
||||||
@@ -22,17 +24,32 @@ export const usePermissionStore = defineStore({
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
cacheOperate({ mode, name }: cacheType) {
|
cacheOperate({ mode, name }: cacheType) {
|
||||||
|
const delIndex = this.cachePageList.findIndex(v => v === name);
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
|
case "refresh":
|
||||||
|
this.cachePageList = this.cachePageList.filter(v => v !== name);
|
||||||
|
break;
|
||||||
case "add":
|
case "add":
|
||||||
this.cachePageList.push(name);
|
this.cachePageList.push(name);
|
||||||
this.cachePageList = [...new Set(this.cachePageList)];
|
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
// eslint-disable-next-line no-case-declarations
|
|
||||||
const delIndex = this.cachePageList.findIndex(v => v === name);
|
|
||||||
delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
|
delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
/** 监听缓存页面是否存在于标签页,不存在则删除 */
|
||||||
|
(() => {
|
||||||
|
let cacheLength = this.cachePageList.length;
|
||||||
|
const nameList = getKeyList(useMultiTagsStoreHook().multiTags, "name");
|
||||||
|
while (cacheLength > 0) {
|
||||||
|
nameList.findIndex(v => v === this.cachePageList[cacheLength - 1]) ===
|
||||||
|
-1 &&
|
||||||
|
this.cachePageList.splice(
|
||||||
|
this.cachePageList.indexOf(this.cachePageList[cacheLength - 1]),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
cacheLength--;
|
||||||
|
}
|
||||||
|
})();
|
||||||
},
|
},
|
||||||
/** 清空缓存页面 */
|
/** 清空缓存页面 */
|
||||||
clearAllCachePage() {
|
clearAllCachePage() {
|
||||||
|
|||||||
@@ -11,20 +11,19 @@ export const useSettingStore = defineStore({
|
|||||||
hiddenSideBar: getConfig().HiddenSideBar
|
hiddenSideBar: getConfig().HiddenSideBar
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getTitle() {
|
getTitle(state) {
|
||||||
return this.title;
|
return state.title;
|
||||||
},
|
},
|
||||||
getFixedHeader() {
|
getFixedHeader(state) {
|
||||||
return this.fixedHeader;
|
return state.fixedHeader;
|
||||||
},
|
},
|
||||||
getHiddenSideBar() {
|
getHiddenSideBar(state) {
|
||||||
return this.HiddenSideBar;
|
return state.hiddenSideBar;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
CHANGE_SETTING({ key, value }) {
|
CHANGE_SETTING({ key, value }) {
|
||||||
// eslint-disable-next-line no-prototype-builtins
|
if (Reflect.has(this, key)) {
|
||||||
if (this.hasOwnProperty(key)) {
|
|
||||||
this[key] = value;
|
this[key] = value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ export const useUserStore = defineStore({
|
|||||||
state: (): userType => ({
|
state: (): userType => ({
|
||||||
// 用户名
|
// 用户名
|
||||||
username:
|
username:
|
||||||
storageSession.getItem<DataInfo<number>>(sessionKey)?.username ?? "",
|
storageSession().getItem<DataInfo<number>>(sessionKey)?.username ?? "",
|
||||||
// 页面级别权限
|
// 页面级别权限
|
||||||
roles: storageSession.getItem<DataInfo<number>>(sessionKey)?.roles ?? []
|
roles: storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? []
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
/** 存储用户名 */
|
/** 存储用户名 */
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "element-plus/theme-chalk/src/dark/css-vars.scss";
|
@use "element-plus/theme-chalk/src/dark/css-vars.scss" as *;
|
||||||
|
|
||||||
/* 暗黑模式适配 */
|
/* 暗黑模式适配 */
|
||||||
html.dark {
|
html.dark {
|
||||||
@@ -30,8 +30,8 @@ html.dark {
|
|||||||
.tags-view {
|
.tags-view {
|
||||||
.arrow-left,
|
.arrow-left,
|
||||||
.arrow-right {
|
.arrow-right {
|
||||||
box-shadow: none;
|
|
||||||
border-right: 1px solid $border-style;
|
border-right: 1px solid $border-style;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow-right {
|
.arrow-right {
|
||||||
@@ -39,102 +39,12 @@ html.dark {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* vxe-table */
|
|
||||||
.vxe-table--header-wrapper,
|
|
||||||
.vxe-table--body-wrapper {
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
background: var(--el-bg-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-table--render-default.border--full .vxe-header--column,
|
|
||||||
.vxe-table--render-default.border--full .vxe-body--column,
|
|
||||||
.vxe-table--render-default.border--full .vxe-footer--column {
|
|
||||||
background-image: linear-gradient(
|
|
||||||
var(--el-border-color-lighter),
|
|
||||||
var(--el-border-color-lighter)
|
|
||||||
),
|
|
||||||
linear-gradient(
|
|
||||||
var(--el-border-color-lighter),
|
|
||||||
var(--el-border-color-lighter)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表头 */
|
|
||||||
.vxe-table--header-wrapper {
|
|
||||||
background: #262727 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-table--render-wrapper,
|
|
||||||
.vxe-table--main-wrapper {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-pager.is--perfect,
|
|
||||||
.vxe-table--render-default .vxe-table--border-line {
|
|
||||||
border: 1px solid var(--el-border-color-lighter);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-table--header-border-line {
|
|
||||||
border-bottom: 1px solid var(--el-border-color-lighter) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-body--row.row--hover,
|
|
||||||
.vxe-pager {
|
|
||||||
background-color: #262727;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-input--inner,
|
|
||||||
.vxe-pager .vxe-pager--jump-prev,
|
|
||||||
.vxe-pager .vxe-pager--prev-btn,
|
|
||||||
.vxe-pager .vxe-pager--next-btn,
|
|
||||||
.vxe-pager .vxe-pager--jump-next,
|
|
||||||
.vxe-pager .vxe-pager--num-btn,
|
|
||||||
.vxe-pager .vxe-pager--jump .vxe-pager--goto {
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
// outline: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-select-option--wrapper {
|
|
||||||
background: var(--el-bg-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-select-option:not(.is--disabled).is--hover {
|
|
||||||
background: var(--el-color-primary-light-6) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-modal--wrapper.type--modal .vxe-modal--box,
|
|
||||||
.vxe-modal--wrapper.type--alert .vxe-modal--box,
|
|
||||||
.vxe-modal--wrapper.type--confirm .vxe-modal--box,
|
|
||||||
.vxe-form {
|
|
||||||
background: var(--el-bg-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-modal--box,
|
|
||||||
.vxe-modal--header {
|
|
||||||
border: none;
|
|
||||||
background: var(--el-bg-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-modal--title,
|
|
||||||
.vxe-button--content,
|
|
||||||
.vxe-modal--header-title {
|
|
||||||
color: var(--el-text-color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-button.type--button:hover {
|
|
||||||
background: var(--el-color-primary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vxe-button {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 项目配置面板 */
|
/* 项目配置面板 */
|
||||||
.right-panel-items {
|
.right-panel-items {
|
||||||
.el-divider__text {
|
.el-divider__text {
|
||||||
--el-bg-color: var(--el-bg-color);
|
--el-bg-color: var(--el-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-divider--horizontal {
|
.el-divider--horizontal {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
@@ -144,15 +54,53 @@ html.dark {
|
|||||||
.el-table__cell {
|
.el-table__cell {
|
||||||
background: var(--el-bg-color);
|
background: var(--el-bg-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-card {
|
.el-card {
|
||||||
--el-card-bg-color: var(--el-bg-color);
|
--el-card-bg-color: var(--el-bg-color);
|
||||||
|
|
||||||
// border: none !important;
|
// border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-backtop {
|
.el-backtop {
|
||||||
--el-backtop-bg-color: var(--el-color-primary-light-9);
|
--el-backtop-bg-color: var(--el-color-primary-light-9);
|
||||||
--el-backtop-hover-bg-color: var(--el-color-primary);
|
--el-backtop-hover-bg-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dropdown-menu__item:not(.is-disabled):hover {
|
.el-dropdown-menu__item:not(.is-disabled):hover {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 全局覆盖element-plus的el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标的样式,表现更鲜明 */
|
||||||
|
.el-icon {
|
||||||
|
&.el-dialog__close,
|
||||||
|
&.el-drawer__close,
|
||||||
|
&.el-message-box__close,
|
||||||
|
&.el-notification__closeBtn {
|
||||||
|
&:hover {
|
||||||
|
color: rgb(255 255 255 / 85%) !important;
|
||||||
|
background-color: rgb(255 255 255 / 12%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,非暗黑模式在 src/style/element-plus.scss 文件进行了适配 */
|
||||||
|
.pure-message {
|
||||||
|
background-color: rgb(36 37 37) !important;
|
||||||
|
background-image: initial !important;
|
||||||
|
box-shadow: rgb(13 13 13 / 12%) 0 3px 6px -4px,
|
||||||
|
rgb(13 13 13 / 8%) 0 6px 16px 0, rgb(13 13 13 / 5%) 0 9px 28px 8px !important;
|
||||||
|
|
||||||
|
& .el-message__content {
|
||||||
|
color: $color-white !important;
|
||||||
|
pointer-events: all !important;
|
||||||
|
background-image: initial !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .el-message__closeBtn {
|
||||||
|
&:hover {
|
||||||
|
color: rgb(255 255 255 / 85%);
|
||||||
|
background-color: rgb(255 255 255 / 12%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,12 @@
|
|||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 自定义 tooltip 的类名 */
|
||||||
|
.pure-tooltip {
|
||||||
|
// 右侧操作面板right-panel类名的z-index为40000,tooltip需要大于它才能显示
|
||||||
|
z-index: 41000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* nprogress 适配 element-plus 的主题色 */
|
/* nprogress 适配 element-plus 的主题色 */
|
||||||
#nprogress {
|
#nprogress {
|
||||||
& .bar {
|
& .bar {
|
||||||
@@ -62,3 +68,66 @@
|
|||||||
border-left-color: var(--el-color-primary);
|
border-left-color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 全局覆盖element-plus的el-dialog、el-drawer、el-message-box、el-notification组件右上角关闭图标的样式,表现更鲜明 */
|
||||||
|
.el-dialog__headerbtn,
|
||||||
|
.el-message-box__headerbtn {
|
||||||
|
&:hover {
|
||||||
|
.el-dialog__close {
|
||||||
|
color: var(--el-color-info) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
&.el-dialog__close,
|
||||||
|
&.el-drawer__close,
|
||||||
|
&.el-message-box__close,
|
||||||
|
&.el-notification__closeBtn {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
transition: background-color 0.2s, color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgb(0 0 0 / 88%) !important;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: rgb(0 0 0 / 6%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,暗黑模式在 src/style/dark.scss 文件进行了适配 */
|
||||||
|
.pure-message {
|
||||||
|
padding: 10px 13px !important;
|
||||||
|
background: #fff !important;
|
||||||
|
border-width: 0 !important;
|
||||||
|
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014,
|
||||||
|
0 9px 28px 8px #0000000d !important;
|
||||||
|
|
||||||
|
&.el-message.is-closable .el-message__content {
|
||||||
|
padding-right: 17px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .el-message__content {
|
||||||
|
color: #000000d9 !important;
|
||||||
|
pointer-events: all !important;
|
||||||
|
background-image: initial !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .el-message__icon {
|
||||||
|
margin-right: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .el-message__closeBtn {
|
||||||
|
right: 9px !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
transition: background-color 0.2s, color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(0 0 0 / 6%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
@import "./mixin.scss";
|
@import "./transition";
|
||||||
@import "./transition.scss";
|
@import "./element-plus";
|
||||||
@import "./element-plus.scss";
|
@import "./sidebar";
|
||||||
@import "./sidebar.scss";
|
@import "./dark";
|
||||||
@import "./dark.scss";
|
|
||||||
@import "./tailwind.css";
|
|
||||||
|
|
||||||
/* 自定义全局 CssVar */
|
/* 自定义全局 CssVar */
|
||||||
:root {
|
:root {
|
||||||
--pure-transition-duration: 0.016s;
|
/* 左侧菜单展开、收起动画时长 */
|
||||||
|
--pure-transition-duration: 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 灰色模式 */
|
/* 灰色模式 */
|
||||||
@@ -19,9 +18,3 @@
|
|||||||
.html-weakness {
|
.html-weakness {
|
||||||
filter: invert(80%);
|
filter: invert(80%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 重置 vxe-table 样式 */
|
|
||||||
.vxe-button.type--button.theme--primary:hover,
|
|
||||||
.vxe-pager .vxe-pager--num-btn:not(.is--disabled).is--active {
|
|
||||||
color: #fff !important;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
@mixin clearfix {
|
|
||||||
&::after {
|
|
||||||
content: "";
|
|
||||||
display: table;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin relative {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin scrollBar {
|
|
||||||
&::-webkit-scrollbar-track-piece {
|
|
||||||
background: #d3dce6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: #99a9bf;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
::before,
|
::before,
|
||||||
::after {
|
::after {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-width: 0;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: currentColor;
|
border-color: currentColor;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
@@ -13,25 +13,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
line-height: 1.5;
|
box-sizing: border-box;
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
tab-size: 4;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-sizing: border-box;
|
line-height: 1.5;
|
||||||
|
tab-size: 4;
|
||||||
|
text-size-adjust: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
|
||||||
line-height: inherit;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
||||||
|
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||||
|
line-height: inherit;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
text-rendering: optimizelegibility;
|
text-rendering: optimizelegibility;
|
||||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
|
|
||||||
"Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
@@ -69,9 +68,9 @@ small {
|
|||||||
|
|
||||||
sub,
|
sub,
|
||||||
sup {
|
sup {
|
||||||
|
position: relative;
|
||||||
font-size: 75%;
|
font-size: 75%;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,8 +84,8 @@ sup {
|
|||||||
|
|
||||||
table {
|
table {
|
||||||
text-indent: 0;
|
text-indent: 0;
|
||||||
border-color: inherit;
|
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
|
border-color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
@@ -94,12 +93,12 @@ input,
|
|||||||
optgroup,
|
optgroup,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
@@ -111,7 +110,6 @@ button,
|
|||||||
[type="button"],
|
[type="button"],
|
||||||
[type="reset"],
|
[type="reset"],
|
||||||
[type="submit"] {
|
[type="submit"] {
|
||||||
-webkit-appearance: button;
|
|
||||||
background-image: none;
|
background-image: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,16 +131,10 @@ progress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[type="search"] {
|
[type="search"] {
|
||||||
-webkit-appearance: textfield;
|
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
::-webkit-file-upload-button {
|
||||||
-webkit-appearance: button;
|
|
||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +159,8 @@ pre {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
legend {
|
legend {
|
||||||
@@ -178,9 +170,9 @@ legend {
|
|||||||
ol,
|
ol,
|
||||||
ul,
|
ul,
|
||||||
menu {
|
menu {
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@@ -189,8 +181,8 @@ textarea {
|
|||||||
|
|
||||||
input::placeholder,
|
input::placeholder,
|
||||||
textarea::placeholder {
|
textarea::placeholder {
|
||||||
opacity: 1;
|
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
button,
|
button,
|
||||||
@@ -211,7 +203,6 @@ iframe,
|
|||||||
embed,
|
embed,
|
||||||
object {
|
object {
|
||||||
display: block;
|
display: block;
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img,
|
img,
|
||||||
@@ -246,9 +237,9 @@ a:active {
|
|||||||
a,
|
a,
|
||||||
a:focus,
|
a:focus,
|
||||||
a:hover {
|
a:hover {
|
||||||
cursor: pointer;
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
div:focus {
|
div:focus {
|
||||||
@@ -257,11 +248,11 @@ div:focus {
|
|||||||
|
|
||||||
.clearfix {
|
.clearfix {
|
||||||
&::after {
|
&::after {
|
||||||
visibility: hidden;
|
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0;
|
|
||||||
content: " ";
|
|
||||||
clear: both;
|
|
||||||
height: 0;
|
height: 0;
|
||||||
|
clear: both;
|
||||||
|
font-size: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
content: " ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,47 +2,50 @@
|
|||||||
@mixin merge-style($sideBarWidth) {
|
@mixin merge-style($sideBarWidth) {
|
||||||
$menuActiveText: #7a80b4;
|
$menuActiveText: #7a80b4;
|
||||||
|
|
||||||
@media screen and (min-width: 150px) and (max-width: 420px) {
|
@media screen and (width >= 150px) and (width <= 420px) {
|
||||||
.app-main-nofixed-header {
|
.app-main-nofixed-header {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media screen and (min-width: 420px) {
|
|
||||||
|
@media screen and (width >= 420px) {
|
||||||
.app-main-nofixed-header {
|
.app-main-nofixed-header {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-menu-icon {
|
.sub-menu-icon {
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 18px;
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.set-icon {
|
.set-icon {
|
||||||
height: 48px;
|
|
||||||
width: 40px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
cursor: pointer;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 48px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-container {
|
.main-container {
|
||||||
|
position: relative;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
/* main-content 属性动画 */
|
|
||||||
transition: margin-left var(--pure-transition-duration);
|
|
||||||
margin-left: $sideBarWidth;
|
margin-left: $sideBarWidth;
|
||||||
position: relative;
|
|
||||||
background: #f0f2f5;
|
background: #f0f2f5;
|
||||||
|
|
||||||
|
/* main-content 属性动画 */
|
||||||
|
transition: margin-left var(--pure-transition-duration);
|
||||||
|
|
||||||
.el-scrollbar__wrap {
|
.el-scrollbar__wrap {
|
||||||
overflow: auto;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
z-index: 998;
|
z-index: 998;
|
||||||
width: calc(100% - 210px);
|
width: calc(100% - 210px);
|
||||||
|
|
||||||
/* fixed-header 属性左上角动画 */
|
/* fixed-header 属性左上角动画 */
|
||||||
transition: width var(--pure-transition-duration);
|
transition: width var(--pure-transition-duration);
|
||||||
}
|
}
|
||||||
@@ -69,20 +73,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
/* 展开动画 */
|
|
||||||
transition: width var(--pure-transition-duration);
|
|
||||||
width: $sideBarWidth !important;
|
|
||||||
background: $menuBg;
|
|
||||||
height: 100%;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
font-size: 0;
|
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
|
width: $sideBarWidth !important;
|
||||||
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
font-size: 0;
|
||||||
|
background: $menuBg;
|
||||||
box-shadow: 0 0 1px #888;
|
box-shadow: 0 0 1px #888;
|
||||||
|
|
||||||
|
/* 展开动画 */
|
||||||
|
transition: width var(--pure-transition-duration);
|
||||||
|
|
||||||
.scrollbar-wrapper {
|
.scrollbar-wrapper {
|
||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
}
|
}
|
||||||
@@ -100,6 +105,7 @@
|
|||||||
/* logo: 48px、leftCollapse: 40px、leftCollapse-shadow: 4px */
|
/* logo: 48px、leftCollapse: 40px、leftCollapse-shadow: 4px */
|
||||||
height: calc(100% - 92px);
|
height: calc(100% - 92px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-scrollbar.mobile {
|
.el-scrollbar.mobile {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -112,15 +118,15 @@
|
|||||||
a {
|
a {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
display: flex;
|
display: flex;
|
||||||
padding-left: 10px;
|
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu {
|
.el-menu {
|
||||||
border: none;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu-item,
|
.el-menu-item,
|
||||||
@@ -157,152 +163,72 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.is-active {
|
.is-active {
|
||||||
transition: color 0.3s;
|
|
||||||
color: $subMenuActiveText !important;
|
color: $subMenuActiveText !important;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item.is-active.nest-menu > * {
|
||||||
|
z-index: 1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item.is-active.nest-menu::before {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 8px;
|
||||||
|
margin: 4px 0;
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
background: var(--el-color-primary) !important;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu .el-menu--inline .el-sub-menu__title,
|
.el-menu .el-menu--inline .el-sub-menu__title,
|
||||||
& .el-sub-menu .el-menu-item {
|
& .el-sub-menu .el-menu-item {
|
||||||
font-size: 12px;
|
|
||||||
min-width: $sideBarWidth !important;
|
min-width: $sideBarWidth !important;
|
||||||
|
font-size: 12px;
|
||||||
background-color: $subMenuBg !important;
|
background-color: $subMenuBg !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 有子集的激活菜单左侧小竖条 */
|
||||||
|
.el-menu--collapse
|
||||||
|
.is-active.outer-most.el-sub-menu
|
||||||
|
> .el-sub-menu__title::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
background-color: $menuActiveBefore;
|
||||||
|
transition: all var(--pure-transition-duration) ease-in-out;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu--collapse .outer-most.el-sub-menu > .el-sub-menu__title::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
display: block;
|
||||||
|
width: 3px;
|
||||||
|
height: 0;
|
||||||
|
content: "";
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
/* 无子集的激活菜单背景 */
|
/* 无子集的激活菜单背景 */
|
||||||
.is-active.submenu-title-noDropdown.outer-most {
|
.is-active.submenu-title-noDropdown.outer-most > * {
|
||||||
background: $subMenuActiveBg !important;
|
z-index: 1;
|
||||||
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 有子集的激活菜单背景 */
|
.is-active.submenu-title-noDropdown.outer-most::before {
|
||||||
.is-active.nest-menu {
|
position: absolute;
|
||||||
background: $subMenuActiveBg !important;
|
inset: 0 8px;
|
||||||
}
|
margin: 4px 0;
|
||||||
}
|
clear: both;
|
||||||
|
content: "";
|
||||||
.horizontal-header {
|
background: var(--el-color-primary) !important;
|
||||||
display: flex;
|
border-radius: 3px;
|
||||||
justify-content: space-around;
|
|
||||||
background: $menuBg;
|
|
||||||
width: 100%;
|
|
||||||
height: 48px;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.horizontal-header-left {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
width: auto;
|
|
||||||
min-width: 200px;
|
|
||||||
align-items: center;
|
|
||||||
padding-left: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.125s ease;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 30px;
|
|
||||||
color: #1890ff;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 700;
|
|
||||||
color: $subMenuActiveText;
|
|
||||||
transition: all 0.5s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal-header-menu {
|
|
||||||
height: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
flex: 1;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontal-header-right {
|
|
||||||
display: flex;
|
|
||||||
min-width: 340px;
|
|
||||||
align-items: center;
|
|
||||||
color: $subMenuActiveText;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
/* 搜索 */
|
|
||||||
.search-container,
|
|
||||||
/* 告警 */
|
|
||||||
.dropdown-badge,
|
|
||||||
/* 用户名 */
|
|
||||||
.el-dropdown-link,
|
|
||||||
/* 设置 */
|
|
||||||
.set-icon {
|
|
||||||
&:hover {
|
|
||||||
background: $menuHover;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-badge {
|
|
||||||
height: 48px;
|
|
||||||
color: $subMenuActiveText;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-dropdown-link {
|
|
||||||
height: 48px;
|
|
||||||
padding: 10px;
|
|
||||||
display: flex;
|
|
||||||
cursor: pointer;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
color: $subMenuActiveText;
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu {
|
|
||||||
border: none;
|
|
||||||
height: 100%;
|
|
||||||
width: 100% !important;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu-item,
|
|
||||||
.el-sub-menu__title {
|
|
||||||
color: $menuText;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $menuTitleHover !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.submenu-title-noDropdown,
|
|
||||||
.el-sub-menu__title {
|
|
||||||
height: 48px;
|
|
||||||
line-height: 48px;
|
|
||||||
background: $menuBg;
|
|
||||||
|
|
||||||
svg {
|
|
||||||
position: static !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-active > .el-sub-menu__title,
|
|
||||||
.is-active.submenu-title-noDropdown {
|
|
||||||
color: $subMenuActiveText !important;
|
|
||||||
|
|
||||||
i {
|
|
||||||
color: $subMenuActiveText !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-active {
|
|
||||||
transition: color 0.3s;
|
|
||||||
color: $subMenuActiveText !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,8 +261,8 @@
|
|||||||
|
|
||||||
/* 子菜单中还有子菜单 */
|
/* 子菜单中还有子菜单 */
|
||||||
.el-menu .el-sub-menu__title {
|
.el-menu .el-sub-menu__title {
|
||||||
font-size: 12px;
|
|
||||||
min-width: $sideBarWidth !important;
|
min-width: $sideBarWidth !important;
|
||||||
|
font-size: 12px;
|
||||||
background-color: $subMenuBg !important;
|
background-color: $subMenuBg !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,29 +279,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.is-active {
|
.is-active {
|
||||||
transition: color 0.3s;
|
|
||||||
color: $subMenuActiveText !important;
|
color: $subMenuActiveText !important;
|
||||||
|
transition: color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu-item.is-active.nest-menu {
|
.el-menu-item.is-active.nest-menu > * {
|
||||||
background: $subMenuActiveBg !important;
|
z-index: 1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item.is-active.nest-menu::before {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 8px;
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
background: var(--el-color-primary) !important;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu-item,
|
.el-menu-item,
|
||||||
.el-sub-menu {
|
.el-sub-menu {
|
||||||
// i {
|
.iconfont {
|
||||||
// width: 20px;
|
font-size: 18px;
|
||||||
// text-align: center;
|
}
|
||||||
// font-size: 16px;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// i.fa {
|
|
||||||
// margin-right: 5px;
|
|
||||||
// font-size: 16px;
|
|
||||||
// }
|
|
||||||
.el-menu-tooltip__trigger {
|
.el-menu-tooltip__trigger {
|
||||||
width: 54px;
|
width: 54px;
|
||||||
padding: 18px !important;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -412,8 +342,8 @@
|
|||||||
|
|
||||||
/* 子菜单中还有子菜单 */
|
/* 子菜单中还有子菜单 */
|
||||||
.el-menu .el-sub-menu__title {
|
.el-menu .el-sub-menu__title {
|
||||||
font-size: 12px;
|
|
||||||
min-width: $sideBarWidth !important;
|
min-width: $sideBarWidth !important;
|
||||||
|
font-size: 12px;
|
||||||
background-color: $subMenuBg !important;
|
background-color: $subMenuBg !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -437,14 +367,156 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 有子集的激活菜单背景 */
|
.el-menu-item.is-active {
|
||||||
.is-active.nest-menu {
|
color: $subMenuActiveText !important;
|
||||||
background: $subMenuActiveBg !important;
|
transition: color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-menu-item.is-active {
|
.el-menu-item.is-active.nest-menu > * {
|
||||||
transition: color 0.3s;
|
z-index: 1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item.is-active.nest-menu::before {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 5px;
|
||||||
|
clear: both;
|
||||||
|
content: "";
|
||||||
|
background: var(--el-color-primary) !important;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
background: $menuBg;
|
||||||
|
|
||||||
|
.horizontal-header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: auto;
|
||||||
|
min-width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
padding-left: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all var(--pure-transition-duration) ease;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: inline-block;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
height: 32px;
|
||||||
|
margin: 2px 0 0 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 32px;
|
||||||
|
color: $subMenuActiveText;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-header-menu {
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.horizontal-header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
min-width: 340px;
|
||||||
|
color: $subMenuActiveText;
|
||||||
|
|
||||||
|
/* 搜索 */
|
||||||
|
.search-container,
|
||||||
|
/* 告警 */
|
||||||
|
.dropdown-badge,
|
||||||
|
/* 用户名 */
|
||||||
|
.el-dropdown-link,
|
||||||
|
/* 设置 */
|
||||||
|
.set-icon {
|
||||||
|
&:hover {
|
||||||
|
background: $menuHover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-badge {
|
||||||
|
height: 48px;
|
||||||
|
color: $subMenuActiveText;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dropdown-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
height: 48px;
|
||||||
|
padding: 10px;
|
||||||
|
color: $subMenuActiveText;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu-item,
|
||||||
|
.el-sub-menu__title {
|
||||||
|
padding-right: var(--el-menu-base-level-padding);
|
||||||
|
color: $menuText;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $menuTitleHover !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.submenu-title-noDropdown,
|
||||||
|
.el-sub-menu__title {
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
background: $menuBg;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: static !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-active > .el-sub-menu__title,
|
||||||
|
.is-active.submenu-title-noDropdown {
|
||||||
color: $subMenuActiveText !important;
|
color: $subMenuActiveText !important;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: $subMenuActiveText !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-active {
|
||||||
|
color: $subMenuActiveText !important;
|
||||||
|
transition: color 0.3s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -452,35 +524,6 @@
|
|||||||
min-width: $sideBarWidth !important;
|
min-width: $sideBarWidth !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 有子菜单 */
|
|
||||||
.el-menu--collapse
|
|
||||||
.is-active.outer-most.el-sub-menu
|
|
||||||
> .el-sub-menu__title::before,
|
|
||||||
/* 无子菜单 */
|
|
||||||
.el-menu--collapse .is-active.submenu-title-noDropdown.outer-most::before {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 2px;
|
|
||||||
width: 2px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: $menuActiveBefore;
|
|
||||||
content: "";
|
|
||||||
clear: both;
|
|
||||||
transition: all 0.125s ease-in-out;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-menu--collapse .outer-most.el-sub-menu > .el-sub-menu__title::before,
|
|
||||||
.el-menu--collapse .submenu-title-noDropdown.outer-most::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
height: 0;
|
|
||||||
width: 3px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 手机端 */
|
/* 手机端 */
|
||||||
.mobile {
|
.mobile {
|
||||||
.fixed-header {
|
.fixed-header {
|
||||||
@@ -493,8 +536,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
transition: transform var(--pure-transition-duration);
|
|
||||||
width: $sideBarWidth;
|
width: $sideBarWidth;
|
||||||
|
transition: transform var(--pure-transition-duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hideSidebar {
|
&.hideSidebar {
|
||||||
@@ -509,6 +552,7 @@
|
|||||||
|
|
||||||
body[layout="vertical"] {
|
body[layout="vertical"] {
|
||||||
$sideBarWidth: 210px;
|
$sideBarWidth: 210px;
|
||||||
|
|
||||||
@include merge-style($sideBarWidth);
|
@include merge-style($sideBarWidth);
|
||||||
|
|
||||||
.el-menu--collapse {
|
.el-menu--collapse {
|
||||||
@@ -526,8 +570,8 @@ body[layout="vertical"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
transition: width 0.125s;
|
|
||||||
width: 54px !important;
|
width: 54px !important;
|
||||||
|
transition: width var(--pure-transition-duration);
|
||||||
|
|
||||||
.is-active.submenu-title-noDropdown.outer-most {
|
.is-active.submenu-title-noDropdown.outer-most {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
@@ -543,11 +587,10 @@ body[layout="vertical"] {
|
|||||||
.el-sub-menu {
|
.el-sub-menu {
|
||||||
& > .el-sub-menu__title {
|
& > .el-sub-menu__title {
|
||||||
& > span {
|
& > span {
|
||||||
height: 0;
|
width: 100%;
|
||||||
width: 0;
|
height: 100%;
|
||||||
overflow: hidden;
|
text-align: center;
|
||||||
visibility: hidden;
|
visibility: visible;
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -557,7 +600,7 @@ body[layout="vertical"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-sub-menu__title {
|
.el-sub-menu__title {
|
||||||
padding: 0 18px !important;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -582,16 +625,22 @@ body[layout="vertical"] {
|
|||||||
|
|
||||||
body[layout="horizontal"] {
|
body[layout="horizontal"] {
|
||||||
$sideBarWidth: 0;
|
$sideBarWidth: 0;
|
||||||
|
|
||||||
@include merge-style($sideBarWidth);
|
@include merge-style($sideBarWidth);
|
||||||
|
|
||||||
|
.fixed-header,
|
||||||
|
.main-container {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.fixed-header {
|
.fixed-header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: none !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body[layout="mix"] {
|
body[layout="mix"] {
|
||||||
$sideBarWidth: 210px;
|
$sideBarWidth: 210px;
|
||||||
|
|
||||||
@include merge-style($sideBarWidth);
|
@include merge-style($sideBarWidth);
|
||||||
|
|
||||||
.el-menu--collapse {
|
.el-menu--collapse {
|
||||||
@@ -609,8 +658,8 @@ body[layout="mix"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-container {
|
.sidebar-container {
|
||||||
transition: width 0.125s;
|
|
||||||
width: 54px !important;
|
width: 54px !important;
|
||||||
|
transition: width var(--pure-transition-duration);
|
||||||
|
|
||||||
.is-active.submenu-title-noDropdown.outer-most {
|
.is-active.submenu-title-noDropdown.outer-most {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
@@ -625,12 +674,13 @@ body[layout="mix"] {
|
|||||||
.el-menu--collapse {
|
.el-menu--collapse {
|
||||||
.el-sub-menu {
|
.el-sub-menu {
|
||||||
& > .el-sub-menu__title {
|
& > .el-sub-menu__title {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
height: 0;
|
width: 100%;
|
||||||
width: 0;
|
height: 100%;
|
||||||
overflow: hidden;
|
text-align: center;
|
||||||
visibility: hidden;
|
visibility: visible;
|
||||||
display: inline-block;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* breadcrumb transition */
|
/* breadcrumb transition */
|
||||||
.breadcrumb-enter-active,
|
.breadcrumb-enter-active {
|
||||||
|
transition: all 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
.breadcrumb-leave-active {
|
.breadcrumb-leave-active {
|
||||||
transition: all 0.5s;
|
position: absolute;
|
||||||
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-enter-from,
|
.breadcrumb-enter-from,
|
||||||
@@ -37,10 +41,6 @@
|
|||||||
transform: translateX(20px);
|
transform: translateX(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-leave-active {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 重置el-menu的展开收起动画时长
|
* @description 重置el-menu的展开收起动画时长
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function getToken(): DataInfo<number> {
|
|||||||
// 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
|
// 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
|
||||||
return Cookies.get(TokenKey)
|
return Cookies.get(TokenKey)
|
||||||
? JSON.parse(Cookies.get(TokenKey))
|
? JSON.parse(Cookies.get(TokenKey))
|
||||||
: storageSession.getItem(sessionKey);
|
: storageSession().getItem(sessionKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +47,7 @@ export function setToken(data: DataInfo<Date>) {
|
|||||||
function setSessionKey(username: string, roles: Array<string>) {
|
function setSessionKey(username: string, roles: Array<string>) {
|
||||||
useUserStoreHook().SET_USERNAME(username);
|
useUserStoreHook().SET_USERNAME(username);
|
||||||
useUserStoreHook().SET_ROLES(roles);
|
useUserStoreHook().SET_ROLES(roles);
|
||||||
storageSession.setItem(sessionKey, {
|
storageSession().setItem(sessionKey, {
|
||||||
refreshToken,
|
refreshToken,
|
||||||
expires,
|
expires,
|
||||||
username,
|
username,
|
||||||
@@ -60,9 +60,9 @@ export function setToken(data: DataInfo<Date>) {
|
|||||||
setSessionKey(username, roles);
|
setSessionKey(username, roles);
|
||||||
} else {
|
} else {
|
||||||
const username =
|
const username =
|
||||||
storageSession.getItem<DataInfo<number>>(sessionKey)?.username ?? "";
|
storageSession().getItem<DataInfo<number>>(sessionKey)?.username ?? "";
|
||||||
const roles =
|
const roles =
|
||||||
storageSession.getItem<DataInfo<number>>(sessionKey)?.roles ?? [];
|
storageSession().getItem<DataInfo<number>>(sessionKey)?.roles ?? [];
|
||||||
setSessionKey(username, roles);
|
setSessionKey(username, roles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ export function setToken(data: DataInfo<Date>) {
|
|||||||
/** 删除`token`以及key值为`user-info`的session信息 */
|
/** 删除`token`以及key值为`user-info`的session信息 */
|
||||||
export function removeToken() {
|
export function removeToken() {
|
||||||
Cookies.remove(TokenKey);
|
Cookies.remove(TokenKey);
|
||||||
sessionStorage.removeItem(sessionKey);
|
sessionStorage.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 格式化token(jwt格式) */
|
/** 格式化token(jwt格式) */
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class PureHttp {
|
|||||||
/** 请求拦截 */
|
/** 请求拦截 */
|
||||||
private httpInterceptorsRequest(): void {
|
private httpInterceptorsRequest(): void {
|
||||||
PureHttp.axiosInstance.interceptors.request.use(
|
PureHttp.axiosInstance.interceptors.request.use(
|
||||||
async (config: PureHttpRequestConfig) => {
|
async (config: PureHttpRequestConfig): Promise<any> => {
|
||||||
// 开启进度条动画
|
// 开启进度条动画
|
||||||
NProgress.start();
|
NProgress.start();
|
||||||
// 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
|
// 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
|
||||||
@@ -175,7 +175,7 @@ class PureHttp {
|
|||||||
/** 单独抽离的post工具函数 */
|
/** 单独抽离的post工具函数 */
|
||||||
public post<T, P>(
|
public post<T, P>(
|
||||||
url: string,
|
url: string,
|
||||||
params?: T,
|
params?: AxiosRequestConfig<T>,
|
||||||
config?: PureHttpRequestConfig
|
config?: PureHttpRequestConfig
|
||||||
): Promise<P> {
|
): Promise<P> {
|
||||||
return this.request<P>("post", url, params, config);
|
return this.request<P>("post", url, params, config);
|
||||||
@@ -184,7 +184,7 @@ class PureHttp {
|
|||||||
/** 单独抽离的get工具函数 */
|
/** 单独抽离的get工具函数 */
|
||||||
public get<T, P>(
|
public get<T, P>(
|
||||||
url: string,
|
url: string,
|
||||||
params?: T,
|
params?: AxiosRequestConfig<T>,
|
||||||
config?: PureHttpRequestConfig
|
config?: PureHttpRequestConfig
|
||||||
): Promise<P> {
|
): Promise<P> {
|
||||||
return this.request<P>("get", url, params, config);
|
return this.request<P>("get", url, params, config);
|
||||||
|
|||||||
@@ -1,32 +1,84 @@
|
|||||||
|
import { type VNode } from "vue";
|
||||||
|
import { isFunction } from "@pureadmin/utils";
|
||||||
import { type MessageHandler, ElMessage } from "element-plus";
|
import { type MessageHandler, ElMessage } from "element-plus";
|
||||||
|
|
||||||
// 更多配置请看:https://element-plus.org/zh-CN/component/message.html#message-%E9%85%8D%E7%BD%AE%E9%A1%B9
|
type messageStyle = "el" | "antd";
|
||||||
|
type messageTypes = "info" | "success" | "warning" | "error";
|
||||||
|
|
||||||
type messageTypes = "success" | "info" | "warning" | "error";
|
interface MessageParams {
|
||||||
|
/** 消息类型,可选 `info` 、`success` 、`warning` 、`error` ,默认 `info` */
|
||||||
|
type?: messageTypes;
|
||||||
|
/** 自定义图标,该属性会覆盖 `type` 的图标 */
|
||||||
|
icon?: any;
|
||||||
|
/** 是否将 `message` 属性作为 `HTML` 片段处理,默认 `false` */
|
||||||
|
dangerouslyUseHTMLString?: boolean;
|
||||||
|
/** 消息风格,可选 `el` 、`antd` ,默认 `antd` */
|
||||||
|
customClass?: messageStyle;
|
||||||
|
/** 显示时间,单位为毫秒。设为 `0` 则不会自动关闭,`element-plus` 默认是 `3000` ,平台改成默认 `2000` */
|
||||||
|
duration?: number;
|
||||||
|
/** 是否显示关闭按钮,默认值 `false` */
|
||||||
|
showClose?: boolean;
|
||||||
|
/** 文字是否居中,默认值 `false` */
|
||||||
|
center?: boolean;
|
||||||
|
/** `Message` 距离窗口顶部的偏移量,默认 `20` */
|
||||||
|
offset?: number;
|
||||||
|
/** 设置组件的根元素,默认 `document.body` */
|
||||||
|
appendTo?: string | HTMLElement;
|
||||||
|
/** 合并内容相同的消息,不支持 `VNode` 类型的消息,默认值 `false` */
|
||||||
|
grouping?: boolean;
|
||||||
|
/** 关闭时的回调函数, 参数为被关闭的 `message` 实例 */
|
||||||
|
onClose?: Function | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用法非常简单,参考 src/views/components/message/index.vue 文件 */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `element-plus` 的 `info` 消息类型
|
* `Message` 消息提示函数
|
||||||
*/
|
*/
|
||||||
const message = (
|
const message = (
|
||||||
message: string,
|
message: string | VNode | (() => VNode),
|
||||||
type = "info" as messageTypes,
|
params?: MessageParams
|
||||||
showClose = true,
|
|
||||||
duration = 2000,
|
|
||||||
center = false,
|
|
||||||
grouping = false
|
|
||||||
): MessageHandler => {
|
): MessageHandler => {
|
||||||
return ElMessage({
|
if (!params) {
|
||||||
message,
|
return ElMessage({
|
||||||
type,
|
message,
|
||||||
showClose,
|
customClass: "pure-message"
|
||||||
duration,
|
});
|
||||||
center,
|
} else {
|
||||||
grouping
|
const {
|
||||||
});
|
icon,
|
||||||
|
type = "info",
|
||||||
|
dangerouslyUseHTMLString = false,
|
||||||
|
customClass = "antd",
|
||||||
|
duration = 2000,
|
||||||
|
showClose = false,
|
||||||
|
center = false,
|
||||||
|
offset = 20,
|
||||||
|
appendTo = document.body,
|
||||||
|
grouping = false,
|
||||||
|
onClose
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
return ElMessage({
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
icon,
|
||||||
|
dangerouslyUseHTMLString,
|
||||||
|
duration,
|
||||||
|
showClose,
|
||||||
|
center,
|
||||||
|
offset,
|
||||||
|
appendTo,
|
||||||
|
grouping,
|
||||||
|
// 全局搜 pure-message 即可知道该类的样式位置
|
||||||
|
customClass: customClass === "antd" ? "pure-message" : "",
|
||||||
|
onClose: () => (isFunction(onClose) ? onClose() : null)
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭 `element-plus` 的所有消息实例
|
* 关闭所有 `Message` 消息提示函数
|
||||||
*/
|
*/
|
||||||
const closeAllMessage = (): void => ElMessage.closeAll();
|
const closeAllMessage = (): void => ElMessage.closeAll();
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ const Print = function (dom, options?: object): PrintFunction {
|
|||||||
styleStr: "",
|
styleStr: "",
|
||||||
// Elements that need to dynamically get and set the height
|
// Elements that need to dynamically get and set the height
|
||||||
setDomHeightArr: [],
|
setDomHeightArr: [],
|
||||||
// Echart dom List
|
|
||||||
echartDomArr: [],
|
|
||||||
// Callback before printing
|
// Callback before printing
|
||||||
printBeforeFn: null,
|
printBeforeFn: null,
|
||||||
// Callback after printing
|
// Callback after printing
|
||||||
@@ -73,6 +71,8 @@ Print.prototype = {
|
|||||||
const inputs = document.querySelectorAll("input");
|
const inputs = document.querySelectorAll("input");
|
||||||
const selects = document.querySelectorAll("select");
|
const selects = document.querySelectorAll("select");
|
||||||
const textareas = document.querySelectorAll("textarea");
|
const textareas = document.querySelectorAll("textarea");
|
||||||
|
const canvass = document.querySelectorAll("canvas");
|
||||||
|
|
||||||
for (let k = 0; k < inputs.length; k++) {
|
for (let k = 0; k < inputs.length; k++) {
|
||||||
if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
|
if (inputs[k].type == "checkbox" || inputs[k].type == "radio") {
|
||||||
if (inputs[k].checked == true) {
|
if (inputs[k].checked == true) {
|
||||||
@@ -108,6 +108,15 @@ Print.prototype = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (let k4 = 0; k4 < canvass.length; k4++) {
|
||||||
|
const imageURL = canvass[k4].toDataURL("image/png");
|
||||||
|
const img = document.createElement("img");
|
||||||
|
img.src = imageURL;
|
||||||
|
img.setAttribute("style", "max-width: 100%;");
|
||||||
|
img.className = "isNeedRemove";
|
||||||
|
canvass[k4].parentNode.insertBefore(img, canvass[k4].nextElementSibling);
|
||||||
|
}
|
||||||
|
|
||||||
return this.dom.outerHTML;
|
return this.dom.outerHTML;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -130,6 +139,12 @@ Print.prototype = {
|
|||||||
doc.open();
|
doc.open();
|
||||||
doc.write(content);
|
doc.write(content);
|
||||||
doc.close();
|
doc.close();
|
||||||
|
|
||||||
|
const removes = document.querySelectorAll(".isNeedRemove");
|
||||||
|
for (let k = 0; k < removes.length; k++) {
|
||||||
|
removes[k].parentNode.removeChild(removes[k]);
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const _this = this;
|
const _this = this;
|
||||||
iframe.onload = function (): void {
|
iframe.onload = function (): void {
|
||||||
@@ -137,42 +152,16 @@ Print.prototype = {
|
|||||||
if (_this.conf.printBeforeFn) {
|
if (_this.conf.printBeforeFn) {
|
||||||
_this.conf.printBeforeFn({ doc });
|
_this.conf.printBeforeFn({ doc });
|
||||||
}
|
}
|
||||||
|
_this.toPrint(w);
|
||||||
_this.drawEchartImg(doc).then(() => {
|
setTimeout(function () {
|
||||||
_this.toPrint(w);
|
document.body.removeChild(iframe);
|
||||||
setTimeout(function () {
|
// After popup, callback
|
||||||
document.body.removeChild(iframe);
|
if (_this.conf.printDoneCallBack) {
|
||||||
// After popup, callback
|
_this.conf.printDoneCallBack();
|
||||||
if (_this.conf.printDoneCallBack) {
|
}
|
||||||
_this.conf.printDoneCallBack();
|
}, 100);
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* echarts printing
|
|
||||||
* @param {Object} doc iframe window
|
|
||||||
*/
|
|
||||||
drawEchartImg(doc): Promise<void> {
|
|
||||||
return new Promise<void>(resolve => {
|
|
||||||
if (this.conf.echartDomArr && this.conf.echartDomArr.length > 0) {
|
|
||||||
this.conf.echartDomArr.forEach(e => {
|
|
||||||
const dom = doc.querySelector("#" + e.$el.id);
|
|
||||||
const img = new Image();
|
|
||||||
const w = dom.offsetWidth + "px";
|
|
||||||
const H = dom.offsetHeight + "px";
|
|
||||||
|
|
||||||
img.style.width = w;
|
|
||||||
img.style.height = H;
|
|
||||||
img.src = e.imgSrc;
|
|
||||||
dom.innerHTML = "";
|
|
||||||
dom.appendChild(img);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
Print
|
Print
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { CSSProperties, VNodeChild } from "vue";
|
import type { CSSProperties, VNodeChild } from "vue";
|
||||||
import { createTypes, VueTypeValidableDef, VueTypesInterface } from "vue-types";
|
import {
|
||||||
|
createTypes,
|
||||||
|
toValidableType,
|
||||||
|
VueTypesInterface,
|
||||||
|
VueTypeValidableDef
|
||||||
|
} from "vue-types";
|
||||||
|
|
||||||
export type VueNode = VNodeChild | JSX.Element;
|
export type VueNode = VNodeChild | JSX.Element;
|
||||||
|
|
||||||
@@ -8,7 +13,7 @@ type PropTypes = VueTypesInterface & {
|
|||||||
readonly VNodeChild: VueTypeValidableDef<VueNode>;
|
readonly VNodeChild: VueTypeValidableDef<VueNode>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const propTypes = createTypes({
|
const newPropTypes = createTypes({
|
||||||
func: undefined,
|
func: undefined,
|
||||||
bool: undefined,
|
bool: undefined,
|
||||||
string: undefined,
|
string: undefined,
|
||||||
@@ -17,18 +22,18 @@ const propTypes = createTypes({
|
|||||||
integer: undefined
|
integer: undefined
|
||||||
}) as PropTypes;
|
}) as PropTypes;
|
||||||
|
|
||||||
propTypes.extend([
|
// 从 vue-types v5.0 开始,extend()方法已经废弃,当前已改为官方推荐的ES6+方法 https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
|
||||||
{
|
export default class propTypes extends newPropTypes {
|
||||||
name: "style",
|
// a native-like validator that supports the `.validable` method
|
||||||
getter: true,
|
static get style() {
|
||||||
type: [String, Object],
|
return toValidableType("style", {
|
||||||
default: undefined
|
type: [String, Object]
|
||||||
},
|
});
|
||||||
{
|
|
||||||
name: "VNodeChild",
|
|
||||||
getter: true,
|
|
||||||
type: undefined
|
|
||||||
}
|
}
|
||||||
]);
|
|
||||||
|
|
||||||
export { propTypes };
|
static get VNodeChild() {
|
||||||
|
return toValidableType("VNodeChild", {
|
||||||
|
type: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
import { App } from "vue";
|
import { App } from "vue";
|
||||||
import Storage from "responsive-storage";
|
import Storage from "responsive-storage";
|
||||||
import { routerArrays } from "@/layout/types";
|
import { routerArrays } from "@/layout/types";
|
||||||
|
import { responsiveStorageNameSpace } from "@/config";
|
||||||
const nameSpace = "responsive-";
|
|
||||||
|
|
||||||
export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
|
export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
|
||||||
|
const nameSpace = responsiveStorageNameSpace();
|
||||||
const configObj = Object.assign(
|
const configObj = Object.assign(
|
||||||
{
|
{
|
||||||
// layout模式以及主题
|
// layout模式以及主题
|
||||||
@@ -27,7 +27,7 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
|
|||||||
},
|
},
|
||||||
config.MultiTagsCache
|
config.MultiTagsCache
|
||||||
? {
|
? {
|
||||||
// 默认显示首页tag
|
// 默认显示顶级菜单tag
|
||||||
tags: Storage.getData("tags", nameSpace) ?? routerArrays
|
tags: Storage.getData("tags", nameSpace) ?? routerArrays
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
import noAccess from "@/assets/status/403.svg?component";
|
import noAccess from "@/assets/status/403.svg?component";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "403"
|
name: "403"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -46,7 +49,7 @@ defineOptions({
|
|||||||
</p>
|
</p>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="$router.push('/')"
|
@click="router.push('/')"
|
||||||
v-motion
|
v-motion
|
||||||
:initial="{
|
:initial="{
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
import noExist from "@/assets/status/404.svg?component";
|
import noExist from "@/assets/status/404.svg?component";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "404"
|
name: "404"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -46,7 +49,7 @@ defineOptions({
|
|||||||
</p>
|
</p>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="$router.push('/')"
|
@click="router.push('/')"
|
||||||
v-motion
|
v-motion
|
||||||
:initial="{
|
:initial="{
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
import noServer from "@/assets/status/500.svg?component";
|
import noServer from "@/assets/status/500.svg?component";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "500"
|
name: "500"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -46,7 +49,7 @@ defineOptions({
|
|||||||
</p>
|
</p>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="$router.push('/')"
|
@click="router.push('/')"
|
||||||
v-motion
|
v-motion
|
||||||
:initial="{
|
:initial="{
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user