Compare commits

..

12 Commits
v2.8.5 ... v3.0

Author SHA1 Message Date
xiaoxian521
3e8dfb2bcb release: update 3.0 2022-02-14 23:19:32 +08:00
xiaoxian521
6fb0f7ef4d fix: mixNav route 2022-02-14 23:12:04 +08:00
xiaoxian521
08983fdbc1 fix: 固定vite@2.7.13,2.8+打包有问题 2022-02-14 22:51:36 +08:00
啝裳
ef05b2b614 feat: add mix nav (#187)
feat: add mix nav
2022-02-14 22:09:39 +08:00
xiaoxian521
a2dde02994 docs: update 2022-02-10 13:13:38 +08:00
rich1e
7544cf058e fix: epThemeColor error
Closes #183
2022-02-10 12:54:16 +08:00
xiaoxian521
43193fd2b6 docs: update 2022-02-10 12:01:36 +08:00
xiaoxian521
25c55c5d1d chore: update eslint@8.8.0 2022-02-07 15:59:04 +08:00
xiaoxian521
217258b972 chore: update element-plus@2.0.0 2022-02-07 13:43:54 +08:00
xiaoxian521
dbe0815ac4 feat: add vxe-table/lib/locale/lang optimizeDeps 2022-02-05 17:36:21 +08:00
xiaoxian521
fd4cad8d4c release: update 2.9.0 2022-02-05 17:34:01 +08:00
xiaoxian521
e33bdb52f3 fix: vite@2.7.0-beta.8 build incompatible template 2022-01-21 18:25:29 +08:00
79 changed files with 2279 additions and 2337 deletions

View File

@@ -37,7 +37,7 @@ module.exports = {
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint"
"@vue/eslint-config-typescript"
],
parser: "vue-eslint-parser",
parserOptions: {
@@ -50,6 +50,10 @@ module.exports = {
}
},
rules: {
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"vue/require-explicit-emits": "off",
"vue/multi-word-component-names": "off",
"@typescript-eslint/no-explicit-any": "off", // any
"no-debugger": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", // setup()

View File

@@ -1,7 +1,6 @@
module.exports = {
bracketSpacing: true,
jsxBracketSameLine: true,
singleQuote: false,
arrowParens: 'avoid',
trailingComma: 'none'
arrowParens: "avoid",
trailingComma: "none"
};

View File

@@ -1,3 +1,24 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- Added mix navigation
### 🐞 Bug fixes
- Fix tab page bug
# 2.9.0 (2022-2-5)
### 🎫 Feat
- Added package size analysis, command `pnpm report`
### 🍏 Perf
- Use `iconify` to introduce icons on demand, optimize icon size, and reduce network requests
- Optimize the route, the route can not pass `showLink: true`, it is displayed by default
# 2.8.5 (2022-1-21)
### 🎫 Feat

View File

@@ -1,3 +1,24 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- Added mix navigation
### 🐞 Bug fixes
- Fix tab page bug
# 2.9.0 (2022-2-5)
### 🎫 Feat
- Added package size analysis, command `pnpm report`
### 🍏 Perf
- Use `iconify` to introduce icons on demand, optimize icon size, and reduce network requests
- Optimize the route, the route can not pass `showLink: true`, it is displayed by default
# 2.8.5 (2022-1-21)
### 🎫 Feat

View File

@@ -1,3 +1,24 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- 添加混合导航
### 🐞 Bug fixes
- 修复标签页 bug
# 2.9.0(2022-2-5)
### 🎫 Feat
- 添加打包大小分析,命令`pnpm report`
### 🍏 Perf
- 采用`iconify`按需引入图标,优化图标大小,减少网络请求
- 优化路由,路由可不传`showLink: true`,默认显示
# 2.8.5(2022-1-21)
### 🎫 Feat

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021 啝裳
Copyright (c) 2022 啝裳
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -124,21 +124,15 @@ If you think this project is helpful to you, you can help the author buy a cup o
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
## WeChat Exchange Group
For the better development of the project, you can choose to donate 10 yuan and add the following WeChat to pull you into the group. After adding, please consciously send a screenshot of the donation
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" />
## License
In principle, no fees and copyrights are charged, so you can use it with confidence
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 © xiaoxian521-2020](./LICENSE)
## Backers
Thank you very much for your support, I believe the project will get better and better! ! ! :heart:
Thank you very much for your support, I believe the project will get better and better :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
@@ -146,6 +140,6 @@ Thank you very much for your support, I believe the project will get better and
## Contributors
This project exists thanks to all the people who contribute!!! :heart:
This project exists thanks to all the people who contribute :heart:
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>

View File

@@ -124,21 +124,21 @@ pnpm build
<img src="http://yiming_chang.gitee.io/manages/pay.jpg" width="150px" height="150px" />
## 微信交流群
## QQ 交流群
为了项目更好的发展,你可选择捐赠 10 元后添加下图微信拉你进群,添加后请自觉发捐赠截图
群里严禁`黄``赌``毒``vpn`等违法行为!
<img src="http://yiming_chang.gitee.io/manages/kf.jpg" width="150px" height="195px" />
<img src="http://yiming_chang.gitee.io/manages/qq.jpg" width="150px" height="225px" />
## 许可证
原则上不收取任何费用及版权,可以放心使用
原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
[MIT © xiaoxian521-2020](./LICENSE)
## 捐赠者
非常感谢你们的支持,相信项目会越来越好:heart:
非常感谢你们的支持,相信项目会越来越好 :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
@@ -146,6 +146,6 @@ pnpm build
## 贡献者
这个项目的存在感谢所有做出贡献的人:heart:
这个项目的存在感谢所有做出贡献的人 :heart:
<a href="https://github.com/xiaoxian521/vue-pure-admin/graphs/contributors"><img src="https://contrib.rocks/image?repo=xiaoxian521/vue-pure-admin" /></a>

121
build/plugins.ts Normal file
View File

@@ -0,0 +1,121 @@
import vue from "@vitejs/plugin-vue";
import svgLoader from "vite-svg-loader";
import legacy from "@vitejs/plugin-legacy";
import vueJsx from "@vitejs/plugin-vue-jsx";
import WindiCSS from "vite-plugin-windicss";
import { viteMockServe } from "vite-plugin-mock";
import liveReload from "vite-plugin-live-reload";
import styleImport from "vite-plugin-style-import";
import ElementPlus from "unplugin-element-plus/vite";
import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console";
import themePreprocessorPlugin from "@zougt/vite-plugin-theme-preprocessor";
export function getPluginsList(command, VITE_LEGACY) {
const prodMock = true;
const lifecycle = process.env.npm_lifecycle_event;
return [
vue(),
// jsx、tsx语法支持
vueJsx(),
WindiCSS(),
// 线上环境删除console
removeConsole(),
// 修改layout文件夹下的文件时自动重载浏览器 解决 https://github.com/xiaoxian521/vue-pure-admin/issues/170
liveReload(["src/layout/**/*", "src/router/**/*"]),
// 自定义主题
themePreprocessorPlugin({
scss: {
multipleScopeVars: [
{
scopeName: "layout-theme-default",
path: "src/layout/theme/default-vars.scss"
},
{
scopeName: "layout-theme-light",
path: "src/layout/theme/light-vars.scss"
},
{
scopeName: "layout-theme-dusk",
path: "src/layout/theme/dusk-vars.scss"
},
{
scopeName: "layout-theme-volcano",
path: "src/layout/theme/volcano-vars.scss"
},
{
scopeName: "layout-theme-yellow",
path: "src/layout/theme/yellow-vars.scss"
},
{
scopeName: "layout-theme-mingQing",
path: "src/layout/theme/mingQing-vars.scss"
},
{
scopeName: "layout-theme-auroraGreen",
path: "src/layout/theme/auroraGreen-vars.scss"
},
{
scopeName: "layout-theme-pink",
path: "src/layout/theme/pink-vars.scss"
},
{
scopeName: "layout-theme-saucePurple",
path: "src/layout/theme/saucePurple-vars.scss"
}
],
// 默认取 multipleScopeVars[0].scopeName
defaultScopeName: "",
// 在生产模式是否抽取独立的主题css文件extract为true以下属性有效
extract: true,
// 独立主题css文件的输出路径默认取 viteConfig.build.assetsDir 相对于 (viteConfig.build.outDir)
outputDir: "",
// 会选取defaultScopeName对应的主题css文件在html添加link
themeLinkTagId: "head",
// "head"||"head-prepend" || "body" ||"body-prepend"
themeLinkTagInjectTo: "head",
// 是否对抽取的css文件内对应scopeName的权重类名移除
removeCssScopeName: false,
// 可以自定义css文件名称的函数
customThemeCssFileName: scopeName => scopeName
}
}),
// svg组件化支持
svgLoader(),
// 按需加载vxe-table
styleImport({
libs: [
{
libraryName: "vxe-table",
esModule: true,
ensureStyleFile: true,
resolveComponent: name => `vxe-table/es/${name}`,
resolveStyle: name => `vxe-table/es/${name}/style.css`
}
]
}),
ElementPlus({}),
// mock支持
viteMockServe({
mockPath: "mock",
localEnabled: command === "serve",
prodEnabled: command !== "serve" && prodMock,
injectCode: `
import { setupProdMockServer } from './mockProdServer';
setupProdMockServer();
`,
logger: true
}),
// 是否为打包后的文件提供传统浏览器兼容性支持
VITE_LEGACY
? legacy({
targets: ["ie >= 11"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"]
})
: null,
// 打包分析
lifecycle === "report"
? visualizer({ open: true, brotliSize: true, filename: "report.html" })
: null
];
}

View File

@@ -3,7 +3,6 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/iconfont.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue-pure-admin</title>
<script src="/sortable.min.js"></script>

View File

@@ -10,7 +10,6 @@ const systemRouter = {
icon: "setting",
title: "menus.hssysManagement",
i18n: true,
showLink: true,
rank: 6
},
children: [
@@ -19,8 +18,7 @@ const systemRouter = {
name: "user",
meta: {
title: "menus.hsBaseinfo",
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -29,7 +27,6 @@ const systemRouter = {
meta: {
title: "menus.hsDict",
i18n: true,
showLink: true,
keepAlive: true
}
}
@@ -44,7 +41,6 @@ const permissionRouter = {
title: "menus.permission",
icon: "lollipop",
i18n: true,
showLink: true,
rank: 3
},
children: [
@@ -53,8 +49,7 @@ const permissionRouter = {
name: "permissionPage",
meta: {
title: "menus.permissionPage",
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -63,7 +58,6 @@ const permissionRouter = {
meta: {
title: "menus.permissionButton",
i18n: true,
showLink: true,
authority: []
}
}
@@ -78,7 +72,6 @@ const tabsRouter = {
icon: "IF-team-icontabs",
title: "menus.hstabs",
i18n: true,
showLink: true,
rank: 8
},
children: [
@@ -87,7 +80,6 @@ const tabsRouter = {
name: "reTabs",
meta: {
title: "menus.hstabs",
showLink: true,
i18n: true
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "vue-pure-admin",
"version": "2.8.5",
"version": "3.0",
"private": true,
"engines": {
"node": ">= 16",
@@ -10,6 +10,7 @@
"dev": "cross-env --max_old_space_size=4096 vite",
"serve": "pnpm dev",
"build": "rimraf dist && cross-env vite build",
"report": "rimraf dist && cross-env vite build",
"preview": "vite preview",
"preview:build": "pnpm build && vite preview",
"clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
@@ -30,39 +31,34 @@
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
"@ctrl/tinycolor": "^3.4.0",
"@fortawesome/fontawesome-svg-core": "^1.2.36",
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/vue-fontawesome": "^3.0.0-5",
"@logicflow/core": "0.7.1",
"@logicflow/extension": "0.7.1",
"@vueuse/core": "^7.5.3",
"@vueuse/core": "^7.6.2",
"@vueuse/motion": "^2.0.0-beta.9",
"@vueuse/shared": "^7.5.3",
"@vueuse/shared": "^7.6.2",
"animate.css": "^4.1.1",
"axios": "^0.25.0",
"cropperjs": "^1.5.11",
"css-color-function": "^1.3.3",
"dayjs": "^1.10.7",
"driver.js": "^0.9.8",
"echarts": "^5.2.1",
"element-plus": "1.3.0-beta.1",
"echarts": "^5.3.0",
"element-plus": "^2.0.2",
"element-resize-detector": "^1.2.3",
"font-awesome": "^4.7.0",
"js-cookie": "^3.0.1",
"lodash-es": "^4.17.21",
"mitt": "^3.0.0",
"mockjs": "^1.1.0",
"nprogress": "^0.2.0",
"path": "^0.12.7",
"pinia": "^2.0.9",
"pinia": "^2.0.11",
"qs": "^6.10.1",
"remixicon": "^2.5.0",
"resize-observer-polyfill": "^1.5.1",
"responsive-storage": "^1.0.11",
"rgb-hex": "^4.0.0",
"v-contextmenu": "3.0.0",
"vue": "^3.2.27",
"vue-i18n": "^9.2.0-beta.26",
"vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.30",
"vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.12",
"vue-types": "^4.1.1",
@@ -76,49 +72,57 @@
"@commitlint/cli": "13.1.0",
"@commitlint/config-conventional": "13.1.0",
"@iconify-icons/ep": "^1.1.3",
"@iconify/vue": "^3.1.2",
"@iconify-icons/fa": "^1.1.1",
"@iconify-icons/fa-solid": "^1.1.2",
"@iconify-icons/ri": "^1.1.1",
"@iconify/vue": "^3.1.3",
"@types/element-resize-detector": "1.1.3",
"@types/js-cookie": "^3.0.1",
"@types/mockjs": "1.0.3",
"@types/node": "14.14.14",
"@types/nprogress": "0.2.0",
"@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "4.31.0",
"@typescript-eslint/parser": "4.31.0",
"@vitejs/plugin-legacy": "^1.6.4",
"@vitejs/plugin-vue": "^2.0.1",
"@vitejs/plugin-vue-jsx": "^1.3.3",
"@vue/eslint-config-prettier": "6.0.0",
"@vue/eslint-config-typescript": "7.0.0",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"@vitejs/plugin-legacy": "^1.7.0",
"@vitejs/plugin-vue": "^2.2.0",
"@vitejs/plugin-vue-jsx": "^1.3.4",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"@zougt/vite-plugin-theme-preprocessor": "^1.4.4",
"autoprefixer": "10.2.4",
"autoprefixer": "^10.4.2",
"cross-env": "7.0.3",
"eslint": "7.30.0",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-vue": "7.17.0",
"husky": "7.0.2",
"eslint": "^8.8.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.4.1",
"husky": "^7.0.4",
"lint-staged": "11.1.2",
"postcss": "8.2.6",
"postcss": "^8.4.6",
"postcss-html": "^1.3.0",
"postcss-import": "14.0.0",
"prettier": "2.3.2",
"postcss-scss": "^4.0.3",
"prettier": "^2.5.1",
"pretty-quick": "3.1.1",
"rimraf": "3.0.2",
"sass": "^1.45.0",
"sass-loader": "^12.3.0",
"stylelint": "13.13.1",
"stylelint-config-prettier": "8.0.2",
"stylelint-config-standard": "22.0.0",
"stylelint-order": "4.1.0",
"rollup-plugin-visualizer": "^5.5.4",
"sass": "^1.49.7",
"sass-loader": "^12.4.0",
"stylelint": "^14.3.0",
"stylelint-config-html": "^1.0.0",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recommended": "^6.0.0",
"stylelint-config-standard": "^24.0.0",
"stylelint-order": "^5.0.0",
"typescript": "^4.5.5",
"unplugin-element-plus": "^0.2.0",
"vite": "^2.7.13",
"vite": "2.7.13",
"vite-plugin-live-reload": "^2.1.0",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-remove-console": "^0.0.6",
"vite-plugin-style-import": "^1.4.1",
"vite-plugin-windicss": "^1.6.1",
"vite-plugin-style-import": "1.4.1",
"vite-plugin-windicss": "^1.7.0",
"vite-svg-loader": "2.2.0",
"vue-eslint-parser": "7.10.0",
"vue-eslint-parser": "^8.2.0",
"windicss": "^3.4.3"
},
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git",

2435
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
@font-face {
font-family: "iconfont"; /* project id 1098500 */
src: url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.eot");
src: url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.eot?#iefix")
format("embedded-opentype"),
url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.woff2") format("woff2"),
url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.woff") format("woff"),
url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.ttf") format("truetype"),
url("//at.alicdn.com/t/font_1098500_3d6un9zwltz.svg#iconfont") format("svg");
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@@ -1,5 +1,5 @@
{
"Version": "2.8.5",
"Version": "3.0",
"Title": "PureAdmin",
"FixedHeader": true,
"HiddenSideBar": false,

View File

@@ -169,7 +169,8 @@ export default defineComponent({
style={{
color: props.color,
fontSize: props.fontSize
}}>
}}
>
{state.displayValue}
</span>
</>

View File

@@ -43,7 +43,8 @@ export default defineComponent({
<div
class="scroll-num"
// @ts-ignore
style={{ "--i": props.i, "--delay": props.delay }}>
style={{ "--i": props.i, "--delay": props.delay }}
>
<ul ref="ul" style={{ fontSize: "32px" }}>
<li>0</li>
<li>1</li>

View File

@@ -133,7 +133,8 @@ export default defineComponent({
<>
<div
class={useAttrs({ excludeListeners: true, excludeKeys: ["class"] })}
style={this.getWrapperStyle}>
style={this.getWrapperStyle}
>
<img
ref="imgElRef"
src={this.props.src}

View File

@@ -9,7 +9,7 @@
background: #fff;
font-size: 66px;
color: #fff;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
box-shadow: 0 0 6px rgb(0 0 0 / 50%);
text-align: center;
font-family: "Helvetica Neue";
}
@@ -58,7 +58,7 @@
.m-flipper.down.go .front::before {
transform-origin: 50% 100%;
animation: frontFlipDown 0.6s ease-in-out both;
box-shadow: 0 -2px 6px rgba(255, 255, 255, 0.3);
box-shadow: 0 -2px 6px rgb(255 255 255 / 30%);
backface-visibility: hidden;
}
@@ -85,7 +85,7 @@
.m-flipper.up.go .front::after {
transform-origin: 50% 0;
animation: frontFlipUp 0.6s ease-in-out both;
box-shadow: 0 2px 6px rgba(255, 255, 255, 0.3);
box-shadow: 0 2px 6px rgb(255 255 255 / 30%);
backface-visibility: hidden;
}

View File

@@ -1,103 +1,13 @@
import { App, defineComponent } from "vue";
import icon from "./src/Icon.vue";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import iconifyIconOffline from "./src/iconifyIconOffline";
import iconifyIconOnline from "./src/iconifyIconOnline";
/**
* find icon component
* @param icon icon图标
* @returns component
*/
export function findIconReg(icon: string) {
// fontawesome4
const fa4Reg = /^fa-/;
// fontawesome5+
const fa5Reg = /^FA-/;
// iconfont
const iFReg = /^IF-/;
// remixicon
const riReg = /^RI-/;
// typeof icon === "function" 属于SVG
if (fa5Reg.test(icon)) {
const text = icon.split(fa5Reg)[1];
return findIcon(
text.slice(0, text.indexOf(" ") == -1 ? text.length : text.indexOf(" ")),
"FA",
text.slice(text.indexOf(" ") + 1, text.length)
);
} else if (fa4Reg.test(icon)) {
return findIcon(icon.split(fa4Reg)[1], "fa");
} else if (iFReg.test(icon)) {
return findIcon(icon.split(iFReg)[1], "IF");
} else if (typeof icon === "function") {
return findIcon(icon, "SVG");
} else if (riReg.test(icon)) {
return findIcon(icon.split(riReg)[1], "RI");
} else {
return findIcon(icon, "EL");
}
}
// 支持fontawesome、iconfont、remixicon、element-plus/icons、自定义svg
export function findIcon(icon: String, type = "EL", property?: string) {
if (type === "FA") {
return defineComponent({
name: "FaIcon",
setup() {
return { icon, property };
},
components: { FontAwesomeIcon },
template: `<font-awesome-icon :icon="icon" v-bind:[property]="true" />`
});
} else if (type === "fa") {
return defineComponent({
name: "faIcon",
data() {
return { icon: `fa ${icon}` };
},
template: `<i :class="icon" />`
});
} else if (type === "IF") {
return defineComponent({
name: "IfIcon",
data() {
return { icon: `iconfont ${icon}` };
},
template: `<i :class="icon" />`
});
} else if (type === "RI") {
return defineComponent({
name: "RiIcon",
data() {
return { icon: `ri-${icon}` };
},
template: `<i :class="icon" />`
});
} else if (type === "EL") {
return defineComponent({
name: "ElIcon",
data() {
return { icon };
},
template: `<IconifyIconOffline :icon="icon" />`
});
} else if (type === "SVG") {
return icon;
}
}
export const Icon = Object.assign(icon, {
install(app: App) {
app.component(icon.name, icon);
}
});
import fontIcon from "./src/iconfont";
export const IconifyIconOffline = iconifyIconOffline;
export const IconifyIconOnline = iconifyIconOnline;
export const FontIcon = fontIcon;
export default {
Icon,
IconifyIconOffline,
IconifyIconOnline
IconifyIconOnline,
FontIcon
};

View File

@@ -1,97 +0,0 @@
<script lang="ts">
export default {
name: "Icon"
};
</script>
<script setup lang="ts">
import { ref, computed } from "vue";
const props = defineProps({
content: {
type: String,
default: ""
},
size: {
type: Number,
default: 18
},
width: {
type: Number,
default: 20
},
height: {
type: Number,
default: 20
},
color: {
type: String,
default: ""
},
svg: {
type: Boolean,
default: false
}
});
const emit = defineEmits<{
(e: "click"): void;
}>();
let text = ref("");
let className = computed(() => {
if (props.content.indexOf("fa-") > -1) {
return props.content.indexOf("fa ") === 0
? props.content
: ["fa", props.content];
} else if (props.content.indexOf("el-icon-") > -1) {
return props.content;
} else if (props.content.indexOf("#") > -1) {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
text.value = props.content;
return "iconfont";
} else {
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
text.value = props.content;
return "";
}
});
let iconStyle = computed(() => {
return (
"font-size: " +
props.size +
"px; color: " +
props.color +
"; width: " +
props.width +
"px; height: " +
props.height +
"px; font-style: normal;"
);
});
const clickHandle = () => {
emit("click");
};
</script>
<template>
<i
v-if="!props.svg"
:class="className"
:style="iconStyle"
v-html="text"
@click="clickHandle"
></i>
<svg
class="icon-svg"
v-if="props.svg"
aria-hidden="true"
:style="iconStyle"
@click="clickHandle"
>
<use :xlink:href="`#${props.content}`" />
</svg>
</template>

View File

@@ -0,0 +1,39 @@
import { h, defineComponent, Component } from "vue";
import { IconifyIconOffline, FontIcon } from "../index";
// 支持fontawesome4、5+、iconfont、remixicon、element-plus的icons、自定义svg
export function useRenderIcon(icon: string): Component {
// iconfont
const ifReg = /^IF-/;
// typeof icon === "function" 属于SVG
if (ifReg.test(icon)) {
// iconfont
const name = icon.split(ifReg)[1];
const iconName = name.slice(
0,
name.indexOf(" ") == -1 ? name.length : name.indexOf(" ")
);
const iconType = name.slice(name.indexOf(" ") + 1, name.length);
return defineComponent({
name: "FontIcon",
render() {
return h(FontIcon, {
icon: iconName,
iconType
});
}
});
} else if (typeof icon === "function") {
// svg
return icon;
} else {
return defineComponent({
name: "Icon",
render() {
return h(IconifyIconOffline, {
icon: icon
});
}
});
}
}

View File

@@ -0,0 +1,48 @@
import { h, defineComponent } from "vue";
// 封装iconfont组件默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code
export default defineComponent({
name: "fontIcon",
props: {
icon: {
type: String,
default: ""
}
},
render() {
const attrs = this.$attrs;
if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") {
return h(
"i",
{
class: "iconfont",
...attrs
},
this.icon
);
} else if (
Object.keys(attrs).includes("svg") ||
attrs?.iconType === "svg"
) {
return h(
"svg",
{
class: "icon-svg",
"aria-hidden": true
},
{
default: () => [
h("use", {
"xlink:href": `#${this.icon}`
})
]
}
);
} else {
return h("i", {
class: `iconfont ${this.icon}`,
...attrs
});
}
}
});

View File

@@ -1,5 +1,7 @@
import { h, defineComponent } from "vue";
import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline";
// element-plus icon
import Check from "@iconify-icons/ep/check";
import Menu from "@iconify-icons/ep/menu";
import HomeFilled from "@iconify-icons/ep/home-filled";
@@ -45,8 +47,25 @@ addIcon("tickets", Tickets);
addIcon("office-building", OfficeBuilding);
addIcon("notebook", Notebook);
// Iconify Icon在Vue里离线使用用于内网环境
// https://docs.iconify.design/icon-components/vue/offline.html
// 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 nodeTree from "@iconify-icons/ri/node-tree";
addIcon("arrow-right-s-line", arrowRightSLine);
addIcon("arrow-left-s-line", arrowLeftSLine);
addIcon("logout-circle-r-line", logoutCircleRLine);
addIcon("node-tree", nodeTree);
// Font Awesome 4
import faUser from "@iconify-icons/fa/user";
import faLock from "@iconify-icons/fa/lock";
import faSignOut from "@iconify-icons/fa/sign-out";
addIcon("fa-user", faUser);
addIcon("fa-lock", faLock);
addIcon("fa-sign-out", faSignOut);
// Iconify Icon在Vue里离线使用用于内网环境https://docs.iconify.design/icon-components/vue/offline.html
export default defineComponent({
name: "IconifyIcon",
components: { IconifyIcon },
@@ -57,10 +76,12 @@ export default defineComponent({
}
},
render() {
const attrs = this.$attrs;
return h(
IconifyIcon,
{
icon: `${this.icon}`
icon: `${this.icon}`,
...attrs
},
{
default: () => []

View File

@@ -1,8 +1,7 @@
import { h, defineComponent } from "vue";
import { Icon as IconifyIcon } from "@iconify/vue";
// 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({
name: "IconifyIcon",
components: { IconifyIcon },
@@ -11,16 +10,19 @@ export default defineComponent({
type: String,
default: ""
},
// default element plus icon
type: {
type: String,
default: "ep:"
}
},
render() {
const attrs = this.$attrs;
return h(
IconifyIcon,
{
icon: `${this.type}${this.icon}`
icon: `${this.type}${this.icon}`,
...attrs
},
{
default: () => []

View File

@@ -299,10 +299,12 @@ export default defineComponent({
cursor: unref(rateDisabled) ? "auto" : "pointer",
textAlign: "center"
}}
key={key}>
key={key}
>
<div
ref={`hsdiv${props.HsKey}${key}`}
class={`hs-item ${[unref(classes)[key] + key]}`}>
class={`hs-item ${[unref(classes)[key] + key]}`}
>
<span>{item}</span>
</div>
</td>

View File

@@ -111,20 +111,24 @@ export default defineComponent({
class="vue-splitter-container clearfix"
style={(unref(cursor), unref(userSelect))}
onMouseup={() => onMouseUp()}
onMousemove={() => onMouseMove(event)}>
onMousemove={() => onMouseMove(event)}
>
<div
class={unref(leftClass)}
style={{ [unref(type)]: unref(percent) + "%" }}>
style={{ [unref(type)]: unref(percent) + "%" }}
>
{ctx.slots.paneL()}
</div>
<resizer
style={`${unref([resizeType])}:${unref(percent)}%`}
split={props.splitSet?.split}
onMousedown={() => onMouseDown()}
onClick={() => onClick()}></resizer>
onClick={() => onClick()}
></resizer>
<div
class={unref(rightClass)}
style={{ [unref(type)]: 100 - unref(percent) + "%" }}>
style={{ [unref(type)]: 100 - unref(percent) + "%" }}
>
{ctx.slots.paneR()}
</div>
<div v-show={unref(active)} class="vue-splitter-container-mask"></div>

View File

@@ -1,21 +1,18 @@
.splitter-pane-resizer {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
background: #000;
position: absolute;
opacity: 0.2;
z-index: 1;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding;
background-clip: padding-box;
}
.splitter-pane-resizer.horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
border-top: 5px solid rgb(255 255 255 / 0%);
border-bottom: 5px solid rgb(255 255 255 / 0%);
cursor: row-resize;
width: 100%;
}
@@ -24,7 +21,7 @@
width: 11px;
height: 100%;
margin-left: -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
border-left: 5px solid rgb(255 255 255 / 0%);
border-right: 5px solid rgb(255 255 255 / 0%);
cursor: col-resize;
}

View File

@@ -1,71 +1,43 @@
<script setup lang="ts">
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt";
import { useNav } from "../hooks/nav";
import { useRoute } from "vue-router";
import Notice from "./notice/index.vue";
import mixNav from "./sidebar/mixNav.vue";
import avatars from "/@/assets/avatars.jpg";
import { transformI18n } from "/@/plugins/i18n";
import Hamburger from "./sidebar/hamBurger.vue";
import { useRouter, useRoute } from "vue-router";
import { storageSession } from "/@/utils/storage";
import { watch, getCurrentInstance } from "vue";
import Breadcrumb from "./sidebar/breadCrumb.vue";
import { useAppStoreHook } from "/@/store/modules/app";
import { unref, watch, getCurrentInstance } from "vue";
import { deviceDetection } from "/@/utils/deviceDetection";
import screenfull from "../components/screenfull/index.vue";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
import globalization from "/@/assets/svg/globalization.svg?component";
const route = useRoute();
const { locale } = useI18n();
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
const pureApp = useAppStoreHook();
const router = useRouter();
const route = useRoute();
let usename = storageSession.getItem("info")?.username;
const { locale } = useI18n();
const getDropdownItemStyle = computed(() => {
return t => {
return {
background: locale.value === t ? useEpThemeStoreHook().epThemeColor : "",
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
});
const {
logout,
onPanel,
changeTitle,
toggleSideBar,
pureApp,
usename,
getDropdownItemStyle
} = useNav();
watch(
() => locale.value,
() => {
//@ts-ignore
document.title = transformI18n(
//@ts-ignore
unref(route.meta.title),
unref(route.meta.i18n)
); // 动态title
changeTitle(route.meta);
}
);
// 退出登录
const logout = (): void => {
storageSession.removeItem("info");
router.push("/login");
};
function onPanel() {
emitter.emit("openPanel");
}
function toggleSideBar() {
pureApp.toggleSideBar();
}
// 简体中文
function translationCh() {
instance.locale = { locale: "zh" };
locale.value = "zh";
}
// English
function translationEn() {
instance.locale = { locale: "en" };
locale.value = "en";
@@ -75,14 +47,17 @@ function translationEn() {
<template>
<div class="navbar">
<Hamburger
v-if="pureApp.layout !== 'mix'"
:is-active="pureApp.sidebar.opened"
class="hamburger-container"
@toggleClick="toggleSideBar"
/>
<Breadcrumb class="breadcrumb-container" />
<Breadcrumb v-if="pureApp.layout !== 'mix'" class="breadcrumb-container" />
<div class="vertical-header-right">
<mixNav v-if="pureApp.layout === 'mix'" />
<div v-if="pureApp.layout === 'vertical'" class="vertical-header-right">
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 全屏 -->
@@ -93,7 +68,7 @@ function translationEn() {
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle('zh')"
:style="getDropdownItemStyle(locale, 'zh')"
@click="translationCh"
><IconifyIconOffline
class="check-zh"
@@ -102,7 +77,7 @@ function translationEn() {
/>简体中文</el-dropdown-item
>
<el-dropdown-item
:style="getDropdownItemStyle('en')"
:style="getDropdownItemStyle(locale, 'en')"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><IconifyIconOffline icon="check" /></el-icon
@@ -120,8 +95,10 @@ function translationEn() {
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<i class="ri-logout-circle-r-line"></i
>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
<IconifyIconOffline
icon="logout-circle-r-line"
style="margin: 5px"
/>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
@@ -232,14 +209,8 @@ function translationEn() {
}
.translation {
.el-dropdown-menu__item {
padding: 5px 40px !important;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
@@ -256,16 +227,10 @@ function translationEn() {
.logout {
max-width: 120px;
.el-dropdown-menu__item {
::v-deep(.el-dropdown-menu__item) {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
}
}
</style>

View File

@@ -108,7 +108,7 @@ export const noticesData: TabItem[] = [
{
avatar: "",
title: "任务名称",
description: "任务需要在 2021-11-16 20:00 前启动",
description: "任务需要在 2022-11-16 20:00 前启动",
datetime: "",
extra: "未开始",
status: "info",
@@ -118,7 +118,7 @@ export const noticesData: TabItem[] = [
avatar: "",
title: "第三方紧急代码变更",
description:
"一拳提交于 2021-11-16需在 2021-11-18 前完成代码变更任务",
"一拳提交于 2022-11-16需在 2022-11-18 前完成代码变更任务",
datetime: "",
extra: "马上到期",
status: "danger",
@@ -127,7 +127,7 @@ export const noticesData: TabItem[] = [
{
avatar: "",
title: "信息安全考试",
description: "指派小仙于 2021-12-12 前完成更新并发布",
description: "指派小仙于 2022-12-12 前完成更新并发布",
datetime: "",
extra: "已耗时 8 天",
status: "warning",

View File

@@ -125,7 +125,7 @@ emitter.on("openPanel", () => {
&:hover {
cursor: pointer;
color: red;
color: var(--el-color-primary);
}
}
}

View File

@@ -5,18 +5,14 @@ const { isFullscreen, toggle } = useFullscreen();
<template>
<div class="screen-full" @click="toggle">
<i
<FontIcon
:title="
isFullscreen
? $t('buttons.hsexitfullscreen')
: $t('buttons.hsfullscreen')
"
:class="
isFullscreen
? 'iconfont team-iconexit-fullscreen'
: 'iconfont team-iconfullscreen'
"
></i>
:icon="isFullscreen ? 'team-iconexit-fullscreen' : 'team-iconfullscreen'"
/>
</div>
</template>

View File

@@ -61,6 +61,7 @@ let themeColors = ref<Array<themeColorsType>>([
const verticalRef = templateRef<HTMLElement | null>("verticalRef", null);
const horizontalRef = templateRef<HTMLElement | null>("horizontalRef", null);
const mixRef = templateRef<HTMLElement | null>("mixRef", null);
let layoutTheme =
ref(storageLocal.getItem("responsive-layout")) ||
@@ -101,7 +102,7 @@ const getThemeColorStyle = computed(() => {
};
});
function changeStorageConfigure(key, val) {
function storageConfigureChange<T>(key: string, val: T): void {
const storageConfigure = instance.configure;
storageConfigure[key] = val;
instance.configure = storageConfigure;
@@ -117,7 +118,7 @@ function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
// 灰色模式设置
const greyChange = (value): void => {
toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
changeStorageConfigure("grey", value);
storageConfigureChange("grey", value);
};
// 色弱模式设置
@@ -127,29 +128,30 @@ const weekChange = (value): void => {
"html-weakness",
document.querySelector("html")
);
changeStorageConfigure("weak", value);
storageConfigureChange("weak", value);
};
const tagsChange = () => {
let showVal = settings.tabsVal;
changeStorageConfigure("hideTabs", showVal);
storageConfigureChange("hideTabs", showVal);
emitter.emit("tagViewsChange", showVal);
};
const multiTagsCacheChange = () => {
let multiTagsCache = settings.multiTagsCache;
changeStorageConfigure("multiTagsCache", multiTagsCache);
storageConfigureChange("multiTagsCache", multiTagsCache);
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
};
// 清空缓存并返回登录页
function onReset() {
toggleClass(getConfig().Grey, "html-grey", document.querySelector("html"));
toggleClass(
getConfig().Weak,
"html-weakness",
document.querySelector("html")
);
router.push("/login");
const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
useAppStoreHook().setLayout(Layout);
useEpThemeStoreHook().setEpThemeColor(EpThemeColor);
useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
toggleClass(Grey, "html-grey", document.querySelector("html"));
toggleClass(Weak, "html-weakness", document.querySelector("html"));
useMultiTagsStoreHook().handleTags("equal", [
{
path: "/welcome",
@@ -157,28 +159,24 @@ function onReset() {
meta: {
title: "menus.hshome",
icon: "home-filled",
i18n: true,
showLink: true
i18n: true
}
}
]);
useMultiTagsStoreHook().multiTagsCacheChange(getConfig().MultiTagsCache);
useEpThemeStoreHook().setEpThemeColor(getConfig().EpThemeColor);
storageLocal.clear();
storageSession.clear();
router.push("/login");
}
function onChange(label) {
changeStorageConfigure("showModel", label);
storageConfigureChange("showModel", label);
emitter.emit("tagViewsShowModel", label);
}
// 侧边栏Logo
function logoChange() {
unref(logoVal)
? changeStorageConfigure("showLogo", true)
: changeStorageConfigure("showLogo", false);
? storageConfigureChange("showLogo", true)
: storageConfigureChange("showLogo", false);
emitter.emit("logoChange", unref(logoVal));
}
@@ -193,10 +191,17 @@ watch(instance, ({ 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;
}
});
@@ -316,7 +321,7 @@ nextTick(() => {
<el-divider>导航栏模式</el-divider>
<ul class="pure-theme">
<el-tooltip class="item" content="左侧菜单模式" placement="bottom">
<el-tooltip class="item" content="左侧模式" placement="bottom">
<li
:class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''"
ref="verticalRef"
@@ -327,7 +332,7 @@ nextTick(() => {
</li>
</el-tooltip>
<el-tooltip class="item" content="顶部菜单模式" placement="bottom">
<el-tooltip class="item" content="顶部模式" placement="bottom">
<li
:class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''"
ref="horizontalRef"
@@ -337,6 +342,17 @@ nextTick(() => {
<div></div>
</li>
</el-tooltip>
<el-tooltip class="item" content="混合模式" placement="bottom">
<li
:class="layoutTheme.layout === 'mix' ? $style.isSelect : ''"
ref="mixRef"
@click="setLayoutModel('mix')"
>
<div></div>
<div></div>
</li>
</el-tooltip>
</ul>
<el-divider v-show="!dataTheme">主题色</el-divider>
@@ -437,7 +453,12 @@ nextTick(() => {
style="width: 90%; margin: 24px 15px"
@click="onReset"
>
<i class="fa fa-sign-out"></i>
<IconifyIconOffline
icon="fa-sign-out"
width="15"
height="15"
style="margin-right: 4px"
/>
清空缓存并返回登录页</el-button
>
</panel>
@@ -477,15 +498,14 @@ nextTick(() => {
.pure-theme {
margin-top: 25px;
width: 100%;
height: 100px;
height: 50px;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
li {
margin: 10px;
width: 36%;
height: 70px;
width: 18%;
height: 45px;
background: #f0f2f5;
position: relative;
overflow: hidden;
@@ -523,6 +543,27 @@ nextTick(() => {
}
}
}
&:nth-child(3) {
div {
&:nth-child(1) {
width: 100%;
height: 30%;
background: #1b2a47;
box-shadow: 0 0 1px #888;
}
&:nth-child(2) {
width: 30%;
height: 70%;
bottom: 0;
left: 0;
background: #fff;
box-shadow: 0 0 1px #888;
position: absolute;
}
}
}
}
}

View File

@@ -1,144 +1,80 @@
<script setup lang="ts">
import {
computed,
unref,
watch,
nextTick,
onMounted,
getCurrentInstance
} from "vue";
import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt";
import { useNav } from "../../hooks/nav";
import Notice from "../notice/index.vue";
import { templateRef } from "@vueuse/core";
import SidebarItem from "./sidebarItem.vue";
import avatars from "/@/assets/avatars.jpg";
import { algorithm } from "/@/utils/algorithm";
import screenfull from "../screenfull/index.vue";
import { useRoute, useRouter } from "vue-router";
import { storageSession } from "/@/utils/storage";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { deviceDetection } from "/@/utils/deviceDetection";
import { watch, nextTick, onMounted, getCurrentInstance } from "vue";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import globalization from "/@/assets/svg/globalization.svg?component";
const route = useRoute();
const { locale } = useI18n();
const routers = useRouter().options.routes;
const menuRef = templateRef<ElRef | null>("menu", null);
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
const title =
getCurrentInstance().appContext.config.globalProperties.$config?.Title;
const menuRef = templateRef<ElRef | null>("menu", null);
const route = useRoute();
const router = useRouter();
const routers = useRouter().options.routes;
let usename = storageSession.getItem("info")?.username;
const { locale, t } = useI18n();
const {
logout,
backHome,
onPanel,
changeTitle,
handleResize,
menuSelect,
usename,
getDropdownItemStyle
} = useNav();
const getDropdownItemStyle = computed(() => {
return t => {
return {
background: locale.value === t ? "#1b2a47" : "",
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
onMounted(() => {
nextTick(() => {
handleResize(menuRef.value);
});
});
watch(
() => locale.value,
() => {
//@ts-ignore
// 动态title
document.title = t(unref(route.meta.title));
changeTitle(route.meta);
}
);
// 退出登录
const logout = (): void => {
storageSession.removeItem("info");
router.push("/login");
};
function onPanel() {
emitter.emit("openPanel");
}
const activeMenu = computed((): string => {
const { meta, path } = route;
if (meta.activeMenu) {
// @ts-ignore
return meta.activeMenu;
}
return path;
});
const menuSelect = (indexPath: string): void => {
let parentPath = "";
let parentPathIndex = indexPath.lastIndexOf("/");
if (parentPathIndex > 0) {
parentPath = indexPath.slice(0, parentPathIndex);
}
// 找到当前路由的信息
function findCurrentRoute(routes) {
return routes.map(item => {
if (item.path === indexPath) {
// 切换左侧菜单 通知标签页
emitter.emit("changLayoutRoute", {
indexPath,
parentPath
});
} else {
if (item.children) findCurrentRoute(item.children);
}
});
}
findCurrentRoute(algorithm.increaseIndexes(routers));
};
function backHome() {
router.push("/welcome");
}
function handleResize() {
// @ts-ignore
menuRef.value.handleResize();
}
// 简体中文
function translationCh() {
instance.locale = { locale: "zh" };
locale.value = "zh";
handleResize();
handleResize(menuRef.value);
}
// English
function translationEn() {
instance.locale = { locale: "en" };
locale.value = "en";
handleResize();
handleResize(menuRef.value);
}
onMounted(() => {
nextTick(() => {
handleResize();
});
});
</script>
<template>
<div class="horizontal-header">
<div class="horizontal-header-left" @click="backHome">
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<FontIcon
icon="team-iconlogo"
svg
style="width: 35px; height: 35px"
></FontIcon>
<h4>{{ title }}</h4>
</div>
<el-menu
ref="menu"
:default-active="activeMenu"
unique-opened
router
class="horizontal-header-menu"
mode="horizontal"
@select="menuSelect"
:default-active="route.path"
router
@select="indexPath => menuSelect(indexPath, routers)"
>
<sidebar-item
v-for="route in usePermissionStoreHook().wholeMenus"
@@ -158,14 +94,14 @@ onMounted(() => {
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle('zh')"
:style="getDropdownItemStyle(locale, 'zh')"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><IconifyIconOffline icon="check" /></el-icon
>简体中文</el-dropdown-item
>
<el-dropdown-item
:style="getDropdownItemStyle('en')"
:style="getDropdownItemStyle(locale, 'en')"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><IconifyIconOffline icon="check" /></el-icon
@@ -183,8 +119,11 @@ onMounted(() => {
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<i class="ri-logout-circle-r-line"></i
>{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
<IconifyIconOffline
icon="logout-circle-r-line"
style="margin: 5px"
/>
{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
@@ -202,14 +141,8 @@ onMounted(() => {
<style lang="scss" scoped>
.translation {
.el-dropdown-menu__item {
padding: 5px 40px !important;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
@@ -226,16 +159,10 @@ onMounted(() => {
.logout {
max-width: 120px;
.el-dropdown-menu__item {
::v-deep(.el-dropdown-menu__item) {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
}
}
</style>

View File

@@ -1,6 +1,5 @@
<script setup lang="ts">
import { getCurrentInstance } from "vue";
import Icon from "/@/components/ReIcon/src/Icon.vue";
const props = defineProps({
collapse: Boolean
});
@@ -19,7 +18,11 @@ const title =
class="sidebar-logo-link"
to="/"
>
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<FontIcon
icon="team-iconlogo"
svg
style="width: 35px; height: 35px"
></FontIcon>
<span class="sidebar-title">{{ title }}</span>
</router-link>
<router-link
@@ -29,7 +32,11 @@ const title =
class="sidebar-logo-link"
to="/"
>
<Icon svg :width="35" :height="35" content="team-iconlogo" />
<FontIcon
icon="team-iconlogo"
svg
style="width: 35px; height: 35px"
></FontIcon>
<span class="sidebar-title">{{ title }}</span>
</router-link>
</transition>

View File

@@ -0,0 +1,239 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import Notice from "../notice/index.vue";
import { useNav } from "../../hooks/nav";
import { templateRef } from "@vueuse/core";
import avatars from "/@/assets/avatars.jpg";
import { transformI18n } from "/@/plugins/i18n";
import screenfull from "../screenfull/index.vue";
import { useRoute, useRouter } from "vue-router";
import { deviceDetection } from "/@/utils/deviceDetection";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
import { getParentPaths, findRouteByPath } from "/@/router/utils";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import globalization from "/@/assets/svg/globalization.svg?component";
import { ref, watch, nextTick, onMounted, getCurrentInstance } from "vue";
const route = useRoute();
const { locale } = useI18n();
const routers = useRouter().options.routes;
const menuRef = templateRef<ElRef | null>("menu", null);
const instance =
getCurrentInstance().appContext.config.globalProperties.$storage;
const {
logout,
onPanel,
changeTitle,
toggleSideBar,
handleResize,
menuSelect,
resolvePath,
pureApp,
usename,
getDropdownItemStyle
} = useNav();
let defaultActive = ref(null);
function getDefaultActive(routePath) {
const wholeMenus = usePermissionStoreHook().wholeMenus;
// 当前路由的父级路径
const parentRoutes = getParentPaths(routePath, wholeMenus)[0];
defaultActive.value = findRouteByPath(
parentRoutes,
wholeMenus
)?.children[0]?.path;
}
onMounted(() => {
getDefaultActive(route.path);
nextTick(() => {
handleResize(menuRef.value);
});
});
watch(
() => locale.value,
() => {
changeTitle(route.meta);
}
);
watch(
() => route.path,
() => {
getDefaultActive(route.path);
}
);
function translationCh() {
instance.locale = { locale: "zh" };
locale.value = "zh";
handleResize(menuRef.value);
}
function translationEn() {
instance.locale = { locale: "en" };
locale.value = "en";
handleResize(menuRef.value);
}
</script>
<template>
<div class="horizontal-header">
<div
:class="classes.container"
:title="pureApp.sidebar.opened ? '点击折叠' : '点击展开'"
@click="toggleSideBar"
>
<svg
:fill="useEpThemeStoreHook().fill"
:class="[
'hamburger',
pureApp.sidebar.opened ? 'is-active-hamburger' : ''
]"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
>
<path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z"
/>
</svg>
</div>
<el-menu
ref="menu"
class="horizontal-header-menu"
mode="horizontal"
:default-active="defaultActive"
router
@select="indexPath => menuSelect(indexPath, routers)"
>
<el-menu-item
v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path"
:index="resolvePath(route) || route.redirect"
>
<template #title>
<el-icon v-show="route.meta.icon" :class="route.meta.icon">
<component
:is="useRenderIcon(route.meta && route.meta.icon)"
></component>
</el-icon>
<span>{{ transformI18n(route.meta.title, route.meta.i18n) }}</span>
<FontIcon
v-if="route.meta.extraIcon"
width="30px"
height="30px"
style="position: absolute; right: 10px"
:icon="route.meta.extraIcon.name"
:svg="route.meta.extraIcon.svg ? true : false"
></FontIcon>
</template>
</el-menu-item>
</el-menu>
<div class="horizontal-header-right">
<!-- 通知 -->
<Notice id="header-notice" />
<!-- 全屏 -->
<screenfull id="header-screenfull" v-show="!deviceDetection()" />
<!-- 国际化 -->
<el-dropdown id="header-translation" trigger="click">
<globalization />
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'zh')"
@click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'"
><IconifyIconOffline icon="check" /></el-icon
>简体中文</el-dropdown-item
>
<el-dropdown-item
:style="getDropdownItemStyle(locale, 'en')"
@click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'"
><IconifyIconOffline icon="check" /></el-icon
>English</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<!-- 退出登陆 -->
<el-dropdown trigger="click">
<span class="el-dropdown-link">
<img :src="avatars" />
<p>{{ usename }}</p>
</span>
<template #dropdown>
<el-dropdown-menu class="logout">
<el-dropdown-item @click="logout">
<IconifyIconOffline
icon="logout-circle-r-line"
style="margin: 5px"
/>
{{ $t("buttons.hsLoginOut") }}</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-icon
class="el-icon-setting"
:title="$t('buttons.hssystemSet')"
@click="onPanel"
>
<IconifyIconOffline icon="setting" />
</el-icon>
</div>
</div>
</template>
<style module="classes" scoped>
.container {
padding: 0 15px;
}
</style>
<style lang="scss" scoped>
.hamburger {
width: 20px;
height: 20px;
&:hover {
cursor: pointer;
}
}
.is-active-hamburger {
transform: rotate(180deg);
}
.translation {
::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px;
}
.check-zh {
position: absolute;
left: 20px;
}
.check-en {
position: absolute;
left: 20px;
}
}
.logout {
max-width: 120px;
::v-deep(.el-dropdown-menu__item) {
min-width: 100%;
display: inline-flex;
flex-wrap: wrap;
}
}
</style>

View File

@@ -1,22 +1,14 @@
<script setup lang="ts">
import {
ref,
PropType,
nextTick,
computed,
CSSProperties,
getCurrentInstance
} from "vue";
import { ref, PropType, nextTick, computed, CSSProperties } from "vue";
import path from "path";
import { useNav } from "../../hooks/nav";
import { childrenType } from "../../types";
import { transformI18n } from "/@/plugins/i18n";
import { findIconReg } from "/@/components/ReIcon";
import Icon from "/@/components/ReIcon/src/Icon.vue";
import { useAppStoreHook } from "/@/store/modules/app";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
const instance = getCurrentInstance().appContext.app.config.globalProperties;
const menuMode = instance.$storage.layout?.layout === "vertical";
const pureApp = useAppStoreHook();
const { pureApp } = useNav();
const menuMode = ["vertical", "mix"].includes(pureApp.layout);
const props = defineProps({
item: {
@@ -32,6 +24,19 @@ const props = defineProps({
}
});
const getExtraIconStyle = computed((): CSSProperties => {
if (useAppStoreHook().getSidebarStatus) {
return {
position: "absolute",
right: "10px"
};
} else {
return {
position: "static"
};
}
});
const getNoDropdownStyle = computed((): CSSProperties => {
return {
display: "flex",
@@ -93,7 +98,6 @@ function hoverMenu(key) {
: Object.assign(key, {
showTooltip: false
});
hoverMenuMap.set(key, true);
});
}
@@ -120,8 +124,8 @@ function hasOneShowingChild(
function resolvePath(routePath) {
const httpReg = /^http(s?):\/\//;
if (httpReg.test(routePath)) {
return props.basePath + "/" + routePath;
if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
return routePath || props.basePath;
} else {
return path.resolve(props.basePath, routePath);
}
@@ -143,13 +147,25 @@ function resolvePath(routePath) {
<el-icon v-show="props.item.meta.icon">
<component
:is="
findIconReg(
useRenderIcon(
onlyOneChild.meta.icon ||
(props.item.meta && props.item.meta.icon)
)
"
></component>
</el-icon>
<div
v-if="
!pureApp.sidebar.opened &&
pureApp.layout === 'mix' &&
props.item?.pathList?.length === 2
"
:style="getDivStyle"
>
<span :style="getMenuTextStyle">
{{ transformI18n(onlyOneChild.meta.title, onlyOneChild.meta.i18n) }}
</span>
</div>
<template #title>
<div :style="getDivStyle">
<span v-if="!menuMode">{{
@@ -176,11 +192,14 @@ function resolvePath(routePath) {
}}
</span>
</el-tooltip>
<Icon
<FontIcon
v-if="onlyOneChild.meta.extraIcon"
width="30px"
height="30px"
:style="getExtraIconStyle"
:icon="onlyOneChild.meta.extraIcon.name"
:svg="onlyOneChild.meta.extraIcon.svg ? true : false"
:content="`${onlyOneChild.meta.extraIcon.name}`"
/>
></FontIcon>
</div>
</template>
</el-menu-item>
@@ -195,7 +214,7 @@ function resolvePath(routePath) {
<template #title>
<el-icon v-show="props.item.meta.icon" :class="props.item.meta.icon">
<component
:is="findIconReg(props.item.meta && props.item.meta.icon)"
:is="useRenderIcon(props.item.meta && props.item.meta.icon)"
></component>
</el-icon>
<span v-if="!menuMode">{{
@@ -220,11 +239,14 @@ function resolvePath(routePath) {
</span>
</div>
</el-tooltip>
<Icon
<FontIcon
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"
:content="`${props.item.meta.extraIcon.name}`"
/>
></FontIcon>
</template>
<sidebar-item
v-for="child in props.item.children"

View File

@@ -1,61 +1,56 @@
<script setup lang="ts">
import Logo from "./logo.vue";
import { emitter } from "/@/utils/mitt";
import { useNav } from "../../hooks/nav";
import SidebarItem from "./sidebarItem.vue";
import { algorithm } from "/@/utils/algorithm";
import { storageLocal } from "/@/utils/storage";
import { useRoute, useRouter } from "vue-router";
import { computed, ref, onBeforeMount } from "vue";
import { useAppStoreHook } from "/@/store/modules/app";
import { ref, computed, watch, onBeforeMount } from "vue";
import { findRouteByPath, getParentPaths } from "/@/router/utils";
import { usePermissionStoreHook } from "/@/store/modules/permission";
const route = useRoute();
const pureApp = useAppStoreHook();
const router = useRouter().options.routes;
const routers = useRouter().options.routes;
const showLogo = ref(
storageLocal.getItem("responsive-configure")?.showLogo ?? true
);
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
const activeMenu = computed((): string => {
const { meta, path } = route;
if (meta.activeMenu) {
// @ts-ignore
return meta.activeMenu;
}
return path;
const { pureApp, isCollapse, menuSelect } = useNav();
let subMenuData = ref([]);
const menuData = computed(() => {
return pureApp.layout === "mix"
? subMenuData.value
: usePermissionStoreHook().wholeMenus;
});
const menuSelect = (indexPath: string): void => {
let parentPath = "";
let parentPathIndex = indexPath.lastIndexOf("/");
if (parentPathIndex > 0) {
parentPath = indexPath.slice(0, parentPathIndex);
}
// 找到当前路由的信息
// eslint-disable-next-line no-inner-declarations
function findCurrentRoute(routes) {
return routes.map(item => {
if (item.path === indexPath) {
// 切换左侧菜单 通知标签页
emitter.emit("changLayoutRoute", {
indexPath,
parentPath
});
} else {
if (item.children) findCurrentRoute(item.children);
}
});
}
findCurrentRoute(algorithm.increaseIndexes(router));
};
function getSubMenuData(path) {
// path的上级路由组成的数组
const parentPathArr = getParentPaths(
path,
usePermissionStoreHook().wholeMenus
);
// 当前路由的父级路由信息
const parenetRoute = findRouteByPath(
parentPathArr[0] || path,
usePermissionStoreHook().wholeMenus
);
if (!parenetRoute?.children) return;
subMenuData.value = parenetRoute?.children;
}
getSubMenuData(route.path);
onBeforeMount(() => {
emitter.on("logoChange", key => {
showLogo.value = key;
});
});
watch(
() => route.path,
() => getSubMenuData(route.path)
);
</script>
<template>
@@ -63,21 +58,21 @@ onBeforeMount(() => {
<Logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu
:default-active="activeMenu"
:default-active="route.path"
:collapse="isCollapse"
unique-opened
router
:collapse-transition="false"
mode="vertical"
class="outer-most"
@select="menuSelect"
@select="indexPath => menuSelect(indexPath, routers)"
>
<sidebar-item
v-for="route in usePermissionStoreHook().wholeMenus"
:key="route.path"
:item="route"
v-for="routes in menuData"
:key="routes.path"
:item="routes"
class="outer-most"
:base-path="route.path"
:base-path="routes.path"
/>
</el-menu>
</el-scrollbar>

View File

@@ -18,36 +18,6 @@
}
}
@-webkit-keyframes rotate {
from {
-webkit-transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
}
}
@-moz-keyframes rotate {
from {
-moz-transform: rotate(0deg);
}
to {
-moz-transform: rotate(360deg);
}
}
@-o-keyframes rotate {
from {
-o-transform: rotate(0deg);
}
to {
-o-transform: rotate(360deg);
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
@@ -80,7 +50,7 @@
.scroll-item {
border-radius: 3px 3px 0 0;
padding: 0 6px 0 6px;
padding: 0 6px;
box-shadow: 0 0 1px #888;
position: relative;
margin-right: 4px;
@@ -92,7 +62,7 @@
.el-icon-close {
font-size: 10px;
color: #1890ff;
color: var(--el-color-primary);
cursor: pointer;
position: absolute;
top: 50%;
@@ -123,7 +93,7 @@
a {
text-decoration: none;
color: #666;
padding: 0 4px 0 4px;
padding: 0 4px;
}
.scroll-container {
@@ -190,7 +160,8 @@
align-items: center;
&:hover {
background: #eee;
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
svg {
@@ -236,7 +207,7 @@
}
.scroll-item.is-active {
background-color: #eaf4fe;
background-color: var(--el-color-primary-light-9);
position: relative;
color: #fff;
@@ -249,17 +220,27 @@
}
a {
color: #1890ff;
color: var(--el-color-primary);
}
}
.ri-arrow-left-s-line {
.arrow-left,
.arrow-right {
width: 40px;
height: 38px;
line-height: 38px;
text-align: center;
font-size: 20px;
color: #00000073;
position: relative;
svg {
width: 20px;
height: 20px;
position: absolute;
left: 50%;
transform: translate(-50%, 50%);
}
}
.arrow-left {
box-shadow: 5px 0 5px -6px #ccc;
&:hover {
@@ -267,15 +248,9 @@
}
}
.ri-arrow-right-s-line {
width: 40px;
height: 38px;
line-height: 38px;
text-align: center;
font-size: 20px;
border-right: 1px solid #ccc;
color: #00000073;
.arrow-right {
box-shadow: -5px 0 5px -6px #ccc;
border-right: 1px solid #ccc;
&:hover {
cursor: e-resize;
@@ -284,10 +259,10 @@
/* 卡片模式下鼠标移入显示蓝色边框 */
.card-in {
color: #1890ff;
color: var(--el-color-primary);
a {
color: #1890ff;
color: var(--el-color-primary);
}
}
@@ -308,7 +283,7 @@
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
background: var(--el-color-primary);
}
/* 灵动模式下鼠标移入显示蓝色进度条 */
@@ -318,7 +293,7 @@
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
background: var(--el-color-primary);
animation: scheduleInWidth 400ms ease-in;
}
@@ -329,14 +304,11 @@
position: absolute;
left: 0;
bottom: 0;
background: #1890ff;
background: var(--el-color-primary);
animation: scheduleOutWidth 400ms ease-in;
}
/* 刷新按钮动画效果 */
.refresh-button {
-webkit-animation: rotate 600ms linear infinite;
-moz-animation: rotate 600ms linear infinite;
-o-animation: rotate 600ms linear infinite;
animation: rotate 600ms linear infinite;
}

View File

@@ -40,11 +40,11 @@ const activeIndex = ref<number>(-1);
let refreshButton = "refresh-button";
const instance = getCurrentInstance();
const pureSetting = useSettingStoreHook();
const showTags = ref(storageLocal.getItem("tagsVal") || false);
const tabDom = templateRef<HTMLElement | null>("tabDom", null);
const containerDom = templateRef<HTMLElement | null>("containerDom", null);
const scrollbarDom = templateRef<HTMLElement | null>("scrollbarDom", null);
const showTags =
ref(storageLocal.getItem("responsive-configure").hideTabs) ?? "false";
let multiTags: ComputedRef<Array<RouteConfigs>> = computed(() => {
return useMultiTagsStoreHook()?.multiTags;
});
@@ -106,7 +106,11 @@ const iconIsActive = computed(() => {
const dynamicTagView = () => {
const index = multiTags.value.findIndex(item => {
return item.path === route.path;
if (item?.query) {
return isEqual(route?.query, item?.query);
} else {
return item.path === route.path;
}
});
moveToView(index);
};
@@ -129,14 +133,14 @@ const moveToView = (index: number): void => {
return;
}
const tabItemEl = instance.refs["dynamic" + index][0];
const tabItemElOffsetLeft = (tabItemEl as HTMLElement).offsetLeft;
const tabItemOffsetWidth = (tabItemEl as HTMLElement).offsetWidth;
const tabItemElOffsetLeft = (tabItemEl as HTMLElement)?.offsetLeft;
const tabItemOffsetWidth = (tabItemEl as HTMLElement)?.offsetWidth;
// 标签页导航栏可视长度(不包含溢出部分)
const scrollbarDomWidth = scrollbarDom.value
? scrollbarDom.value.offsetWidth
? scrollbarDom.value?.offsetWidth
: 0;
// 已有标签页总长度(包含溢出部分)
const tabDomWidth = tabDom.value ? tabDom.value.offsetWidth : 0;
const tabDomWidth = tabDom.value ? tabDom.value?.offsetWidth : 0;
if (tabDomWidth < scrollbarDomWidth || tabItemElOffsetLeft === 0) {
translateX.value = 0;
@@ -314,8 +318,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
meta: {
title: "menus.hshome",
i18n: true,
icon: "el-icon-s-home",
showLink: true
icon: "home-filled"
}
},
obj
@@ -424,6 +427,11 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
});
}
function handleCommand(command: object) {
const { key, item } = command;
onClickDrop(key, item);
}
// 触发右键中菜单的点击事件
function selectTag(key, item) {
onClickDrop(key, item, currentSelect.value);
@@ -470,7 +478,6 @@ function showMenuModel(
* currentIndex为1时左侧的菜单是首页则不显示关闭左侧标签页
* 如果currentIndex等于routeLength-1右侧没有菜单则不显示关闭右侧标签页
*/
if (currentIndex === 1 && routeLength !== 2) {
// 左侧的菜单是首页,右侧存在别的菜单
tagsViews[2].show = false;
@@ -631,7 +638,9 @@ const getContextMenuStyle = computed((): CSSProperties => {
<template>
<div ref="containerDom" class="tags-view" v-if="!showTags">
<i class="ri-arrow-left-s-line" @click="handleScroll(200)"></i>
<div class="arrow-left">
<IconifyIconOffline icon="arrow-left-s-line" @click="handleScroll(200)" />
</div>
<div ref="scrollbarDom" class="scroll-container">
<div class="tab" ref="tabDom" :style="getTabStyle">
<div
@@ -671,7 +680,12 @@ const getContextMenuStyle = computed((): CSSProperties => {
</div>
</div>
</div>
<i class="ri-arrow-right-s-line" @click="handleScroll(-200)"></i>
<span class="arrow-right">
<IconifyIconOffline
icon="arrow-right-s-line"
@click="handleScroll(-200)"
/>
</span>
<!-- 右键菜单按钮 -->
<transition name="el-zoom-in-top">
<ul
@@ -704,7 +718,11 @@ const getContextMenuStyle = computed((): CSSProperties => {
</el-icon>
</li>
<li>
<el-dropdown trigger="click" placement="bottom-end">
<el-dropdown
trigger="click"
placement="bottom-end"
@command="handleCommand"
>
<el-icon>
<IconifyIconOffline icon="arrow-down" />
</el-icon>
@@ -713,11 +731,15 @@ const getContextMenuStyle = computed((): CSSProperties => {
<el-dropdown-item
v-for="(item, key) in tagsViews"
:key="key"
:command="{ key, item }"
:divided="item.divided"
:disabled="item.disabled"
@click="onClickDrop(key, item)"
>
<component :is="item.icon" :key="key" />
<component
:is="item.icon"
:key="key"
style="margin-right: 6px"
/>
{{ $t(item.text) }}
</el-dropdown-item>
</el-dropdown-menu>

110
src/layout/hooks/nav.ts Normal file
View File

@@ -0,0 +1,110 @@
import { computed } from "vue";
import { router } from "/@/router";
import { emitter } from "/@/utils/mitt";
import { routeMetaType } from "../types";
import { transformI18n } from "/@/plugins/i18n";
import { storageSession } from "/@/utils/storage";
import { useAppStoreHook } from "/@/store/modules/app";
import { Title } from "../../../public/serverConfig.json";
import { useEpThemeStoreHook } from "/@/store/modules/epTheme";
export function useNav() {
const pureApp = useAppStoreHook();
// 用户名
const usename: string = storageSession.getItem("info")?.username;
// 设置国际化选中后的样式
const getDropdownItemStyle = computed(() => {
return (locale, t) => {
return {
background: locale === t ? useEpThemeStoreHook().epThemeColor : "",
color: locale === t ? "#f4f4f5" : "#000"
};
};
});
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
// 动态title
function changeTitle(meta: routeMetaType) {
if (Title)
document.title = `${transformI18n(meta.title, meta.i18n)} | ${Title}`;
else document.title = transformI18n(meta.title, meta.i18n);
}
// 退出登录
function logout() {
storageSession.removeItem("info");
router.push("/login");
}
function backHome() {
router.push("/welcome");
}
function onPanel() {
emitter.emit("openPanel");
}
function toggleSideBar() {
pureApp.toggleSideBar();
}
function handleResize(menuRef) {
menuRef.handleResize();
}
function resolvePath(route) {
const httpReg = /^http(s?):\/\//;
const routeChildPath = route.children[0]?.path;
if (httpReg.test(routeChildPath)) {
return route.path + "/" + routeChildPath;
} else {
return routeChildPath;
}
}
function menuSelect(indexPath: string, routers): void {
let parentPath = "";
const parentPathIndex = indexPath.lastIndexOf("/");
if (parentPathIndex > 0) {
parentPath = indexPath.slice(0, parentPathIndex);
}
// 找到当前路由的信息
function findCurrentRoute(indexPath: string, routes) {
return routes.map(item => {
if (item.path === indexPath) {
if (item.redirect) {
findCurrentRoute(item.redirect, item.children);
} else {
// 切换左侧菜单 通知标签页
emitter.emit("changLayoutRoute", {
indexPath,
parentPath
});
}
} else {
if (item.children) findCurrentRoute(indexPath, item.children);
}
});
}
findCurrentRoute(indexPath, routers);
}
return {
logout,
backHome,
onPanel,
changeTitle,
toggleSideBar,
menuSelect,
handleResize,
resolvePath,
isCollapse,
pureApp,
usename,
getDropdownItemStyle
};
}

View File

@@ -171,7 +171,8 @@ const layoutHeader = defineComponent({
},
{
default: () => [
!pureSetting.hiddenSideBar && layout.value.includes("vertical")
!pureSetting.hiddenSideBar &&
(layout.value.includes("vertical") || layout.value.includes("mix"))
? h(navbar)
: h("div"),
!pureSetting.hiddenSideBar && layout.value.includes("horizontal")
@@ -213,7 +214,10 @@ const layoutHeader = defineComponent({
@click="useAppStoreHook().toggleSideBar()"
/>
<Vertical
v-show="!pureSetting.hiddenSideBar && layout.includes('vertical')"
v-show="
!pureSetting.hiddenSideBar &&
(layout.includes('vertical') || layout.includes('mix'))
"
/>
<div
:class="[

View File

@@ -19,7 +19,7 @@ $subMenuBg: #0f0303 !default;
/* 有无子集的激活菜单背景 */
$subMenuActiveBg: #4091f7 !default;
$navTextColor: #fff !default;
$menuText: rgba(254, 254, 254, 0.65) !default;
$menuText: rgb(254 254 254 / 65%) !default;
/* logo背景颜色 */
$sidebarLogo: #002140 !default;

View File

@@ -5,7 +5,7 @@ $menuHover: #e13c39;
$subMenuBg: #000;
$subMenuActiveBg: #e13c39;
$navTextColor: red;
$menuText: rgba(254, 254, 254, 0.651);
$menuText: rgb(254 254 254 / 65.1%);
$sidebarLogo: #42090c;
$menuTitleHover: #fff;
$menuActiveBefore: #e13c39;

View File

@@ -1,6 +1,6 @@
/* 动态改变element-plus主题色 */
import rgbHex from "rgb-hex";
import color from "css-color-function";
import { convert } from "css-color-function";
import { TinyColor } from "@ctrl/tinycolor";
import epCss from "element-plus/dist/index.css";
@@ -48,7 +48,7 @@ export const createColors = (primary: string) => {
};
Object.keys(formula).forEach(key => {
const value = formula[key].replace(/primary/, primary);
colors[key] = "#" + rgbHex(color.convert(value));
colors[key] = "#" + rgbHex(convert(value));
});
return colors;
};

View File

@@ -5,7 +5,7 @@ $menuHover: #e85f33;
$subMenuBg: #0f0603;
$subMenuActiveBg: #e85f33;
$navTextColor: #fff;
$menuText: rgba(254, 254, 254, 0.65);
$menuText: rgb(254 254 254 / 65%);
$sidebarLogo: #441708;
$menuTitleHover: #fff;
$menuActiveBefore: #e85f33;

View File

@@ -5,7 +5,7 @@ $menuHover: #f6da4d;
$subMenuBg: #0f0603;
$subMenuActiveBg: #f6da4d;
$navTextColor: #fff;
$menuText: rgba(254, 254, 254, 0.65);
$menuText: rgb(254 254 254 / 65%);
$sidebarLogo: #443b05;
$menuTitleHover: #fff;
$menuActiveBefore: #f6da4d;

View File

@@ -6,24 +6,25 @@ export const routerArrays: Array<RouteConfigs> = [
meta: {
title: "menus.hshome",
i18n: true,
icon: "home-filled",
showLink: true
icon: "home-filled"
}
}
];
export type routeMetaType = {
title?: string;
i18n?: boolean;
icon?: string;
showLink?: boolean;
savedPosition?: boolean;
authority?: Array<string>;
};
export type RouteConfigs = {
path?: string;
parentPath?: string;
query?: object;
meta?: {
title?: string;
i18n?: boolean;
icon?: string;
showLink?: boolean;
savedPosition?: boolean;
authority?: Array<string>;
};
meta?: routeMetaType;
children?: RouteConfigs[];
name?: string;
};
@@ -72,6 +73,8 @@ export type childrenType = {
};
};
showTooltip?: boolean;
parentId?: number;
pathList?: number[];
};
export type themeColorsType = {

View File

@@ -6,7 +6,6 @@ import { createApp, Directive } from "vue";
import { usI18n } from "../src/plugins/i18n";
import { MotionPlugin } from "@vueuse/motion";
import { useTable } from "../src/plugins/vxe-table";
import { useFontawesome } from "../src/plugins/fontawesome";
import { useElementPlus } from "../src/plugins/element-plus";
import { injectResponsiveStorage } from "/@/utils/storage/responsive";
@@ -28,9 +27,14 @@ Object.keys(directives).forEach(key => {
});
// 全局注册`@iconify/vue`图标库
import { IconifyIconOffline, IconifyIconOnline } from "./components/ReIcon";
import {
IconifyIconOffline,
IconifyIconOnline,
FontIcon
} from "./components/ReIcon";
app.component("IconifyIconOffline", IconifyIconOffline);
app.component("IconifyIconOnline", IconifyIconOnline);
app.component("FontIcon", FontIcon);
getServerConfig(app).then(async config => {
injectResponsiveStorage(app, config);
@@ -40,8 +44,7 @@ getServerConfig(app).then(async config => {
.use(MotionPlugin)
.use(useElementPlus)
.use(useTable)
.use(usI18n)
.use(useFontawesome);
.use(usI18n);
await router.isReady();
app.mount("#app");
});

View File

@@ -1,21 +0,0 @@
/** 兼容fontawesome4和5版本
* 4版本: www.fontawesome.com.cn/faicons/
* 5版本https://fontawesome.com/v5.15/icons?d=gallery&p=2&m=free
* https://github.com/FortAwesome/vue-fontawesome
*/
import { App } from "vue";
import "font-awesome/css/font-awesome.css";
import { library } from "@fortawesome/fontawesome-svg-core";
import {
faUserSecret,
faCoffee,
faSpinner
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
// github.com/Remix-Design/RemixIcon/blob/master/README_CN.md#%E5%AE%89%E8%A3%85%E5%BC%95%E5%85%A5
import "remixicon/fonts/remixicon.css";
export function useFontawesome(app: App) {
library.add(faUserSecret, faCoffee, faSpinner);
app.component("font-awesome-icon", FontAwesomeIcon);
}

View File

@@ -11,7 +11,10 @@ import { storageLocal } from "/@/utils/storage";
* @param isI18n 如果true,获取对应的消息,否则返回本身
* @returns message
*/
export function transformI18n(message: string | object = "", isI18n = false) {
export function transformI18n(
message: string | unknown | object = "",
isI18n: boolean | unknown = false
) {
if (!message) {
return "";
}

View File

@@ -6,6 +6,7 @@ import { split, findIndex } from "lodash-es";
import { transformI18n } from "/@/plugins/i18n";
import remainingRouter from "./modules/remaining";
import { storageSession } from "/@/utils/storage";
import { Title } from "../../public/serverConfig.json";
import { useMultiTagsStoreHook } from "/@/store/modules/multiTags";
import { usePermissionStoreHook } from "/@/store/modules/permission";
import { Router, RouteMeta, createRouter, RouteRecordName } from "vue-router";
@@ -54,12 +55,13 @@ router.beforeEach((to: toRouteType, _from, next) => {
const externalLink = to?.redirectedFrom?.fullPath;
if (!externalLink)
to.matched.some(item => {
item.meta.title
? (document.title = transformI18n(
item.meta.title as string,
item.meta?.i18n as boolean
))
: "";
if (!item.meta.title) return "";
if (Title)
document.title = `${transformI18n(
item.meta.title,
item.meta?.i18n
)} | ${Title}`;
else document.title = transformI18n(item.meta.title, item.meta?.i18n);
});
if (name) {
if (_from?.name) {

View File

@@ -10,7 +10,6 @@ const componentsRouter = {
icon: "menu",
title: $t("menus.hscomponents"),
i18n: true,
showLink: true,
rank: 4
},
children: [
@@ -20,7 +19,6 @@ const componentsRouter = {
component: () => import("/@/views/components/video/index.vue"),
meta: {
title: $t("menus.hsvideo"),
showLink: true,
i18n: true
}
},
@@ -30,7 +28,6 @@ const componentsRouter = {
component: () => import("/@/views/components/map/index.vue"),
meta: {
title: $t("menus.hsmap"),
showLink: true,
keepAlive: true,
i18n: true,
transition: {
@@ -44,7 +41,6 @@ const componentsRouter = {
component: () => import("/@/views/components/draggable/index.vue"),
meta: {
title: $t("menus.hsdraggable"),
showLink: true,
i18n: true,
transition: {
enterTransition: "animate__zoomIn",
@@ -59,7 +55,6 @@ const componentsRouter = {
component: () => import("/@/views/components/split-pane/index.vue"),
meta: {
title: $t("menus.hssplitPane"),
showLink: true,
i18n: true,
extraIcon: {
svg: true,
@@ -73,8 +68,7 @@ const componentsRouter = {
component: () => import("/@/views/components/button/index.vue"),
meta: {
title: $t("menus.hsbutton"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -83,8 +77,7 @@ const componentsRouter = {
component: () => import("/@/views/components/cropping/index.vue"),
meta: {
title: $t("menus.hscropping"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -93,8 +86,7 @@ const componentsRouter = {
component: () => import("/@/views/components/count-to/index.vue"),
meta: {
title: $t("menus.hscountTo"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -103,8 +95,7 @@ const componentsRouter = {
component: () => import("/@/views/components/selector/index.vue"),
meta: {
title: $t("menus.hsselector"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -113,8 +104,7 @@ const componentsRouter = {
component: () => import("/@/views/components/seamless-scroll/index.vue"),
meta: {
title: $t("menus.hsseamless"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -123,8 +113,7 @@ const componentsRouter = {
component: () => import("/@/views/components/contextmenu/index.vue"),
meta: {
title: $t("menus.hscontextmenu"),
i18n: true,
showLink: true
i18n: true
}
}
]

View File

@@ -10,7 +10,6 @@ const editorRouter = {
icon: "edit",
title: $t("menus.hseditor"),
i18n: true,
showLink: true,
rank: 2
},
children: [
@@ -20,7 +19,6 @@ const editorRouter = {
component: () => import("/@/views/editor/index.vue"),
meta: {
title: $t("menus.hseditor"),
showLink: true,
i18n: true,
keepAlive: true,
extraIcon: {

View File

@@ -9,7 +9,6 @@ const errorRouter = {
meta: {
icon: "position",
title: $t("menus.hserror"),
showLink: true,
i18n: true,
rank: 7
},
@@ -20,8 +19,7 @@ const errorRouter = {
component: () => import("/@/views/error/401.vue"),
meta: {
title: $t("menus.hsfourZeroOne"),
i18n: true,
showLink: true
i18n: true
}
},
{
@@ -30,8 +28,7 @@ const errorRouter = {
component: () => import("/@/views/error/404.vue"),
meta: {
title: $t("menus.hsfourZeroFour"),
i18n: true,
showLink: true
i18n: true
}
}
]

View File

@@ -8,7 +8,6 @@ const externalLink = {
meta: {
icon: "link",
title: $t("menus.externalLink"),
showLink: true,
i18n: true,
rank: 190
},
@@ -17,7 +16,6 @@ const externalLink = {
path: "https://github.com/xiaoxian521/vue-pure-admin",
meta: {
title: $t("menus.externalLink"),
showLink: true,
i18n: true,
rank: 191
}

View File

@@ -9,7 +9,6 @@ const flowChartRouter = {
meta: {
icon: "set-up",
title: $t("menus.hsflowChart"),
showLink: true,
i18n: true,
rank: 1
},
@@ -20,8 +19,7 @@ const flowChartRouter = {
component: () => import("/@/views/flow-chart/index.vue"),
meta: {
title: $t("menus.hsflowChart"),
i18n: true,
showLink: true
i18n: true
}
}
]

View File

@@ -10,7 +10,6 @@ const guideRouter = {
icon: "guide",
title: $t("menus.hsguide"),
i18n: true,
showLink: true,
rank: 10
},
children: [
@@ -20,7 +19,6 @@ const guideRouter = {
component: () => import("/@/views/guide/index.vue"),
meta: {
title: $t("menus.hsguide"),
showLink: true,
i18n: true
}
}

View File

@@ -9,7 +9,6 @@ const homeRouter = {
meta: {
icon: "home-filled",
title: $t("menus.hshome"),
showLink: true,
i18n: true,
rank: 0
},
@@ -20,8 +19,7 @@ const homeRouter = {
component: () => import("/@/views/welcome.vue"),
meta: {
title: $t("menus.hshome"),
i18n: true,
showLink: true
i18n: true
}
}
]

View File

@@ -16,6 +16,7 @@ import {
formatTwoStageRoutes,
formatFlatteningRoutes
} from "../utils";
import { buildHierarchyTree } from "/@/utils/tree";
// 原始静态路由(未做任何处理)
const routes = [
@@ -32,7 +33,7 @@ const routes = [
// 导出处理后的静态路由(三级及以上的路由全部拍成二级)
export const constantRoutes: Array<RouteRecordRaw> = formatTwoStageRoutes(
formatFlatteningRoutes(ascending(routes))
formatFlatteningRoutes(buildHierarchyTree(ascending(routes)))
);
// 用于渲染菜单,保持原始层级

View File

@@ -7,10 +7,9 @@ const menuTreeRouter = {
component: Layout,
redirect: "/menuTree/index",
meta: {
icon: "RI-node-tree",
icon: "node-tree",
title: $t("menus.hsMenuTree"),
i18n: true,
showLink: true,
rank: 9
},
children: [
@@ -20,7 +19,6 @@ const menuTreeRouter = {
component: () => import("/@/views/menu-tree/index.vue"),
meta: {
title: $t("menus.hsMenuTree"),
showLink: true,
i18n: true
}
}

View File

@@ -9,7 +9,6 @@ const nestedRouter = {
meta: {
title: $t("menus.hsmenus"),
icon: "histogram",
showLink: true,
i18n: true,
rank: 5
},
@@ -20,7 +19,6 @@ const nestedRouter = {
name: "Menu1",
meta: {
title: $t("menus.hsmenu1"),
showLink: true,
i18n: true,
keepAlive: true
},
@@ -32,7 +30,6 @@ const nestedRouter = {
name: "Menu1-1",
meta: {
title: $t("menus.hsmenu1-1"),
showLink: true,
i18n: true,
keepAlive: true
}
@@ -44,7 +41,6 @@ const nestedRouter = {
redirect: "/nested/menu1/menu1-2/menu1-2-1",
meta: {
title: $t("menus.hsmenu1-2"),
showLink: true,
i18n: true,
keepAlive: true
},
@@ -56,7 +52,6 @@ const nestedRouter = {
name: "Menu1-2-1",
meta: {
title: $t("menus.hsmenu1-2-1"),
showLink: true,
i18n: true,
keepAlive: true
}
@@ -68,7 +63,6 @@ const nestedRouter = {
name: "Menu1-2-2",
meta: {
title: $t("menus.hsmenu1-2-2"),
showLink: true,
keepAlive: true,
i18n: true,
extraIcon: {
@@ -85,7 +79,6 @@ const nestedRouter = {
name: "Menu1-3",
meta: {
title: $t("menus.hsmenu1-3"),
showLink: true,
i18n: true,
keepAlive: true
}
@@ -98,7 +91,6 @@ const nestedRouter = {
component: () => import("/@/views/nested/menu2/index.vue"),
meta: {
title: $t("menus.hsmenu2"),
showLink: true,
i18n: true,
keepAlive: true
}

View File

@@ -30,7 +30,7 @@ function ascending(arr: any[]) {
// 过滤meta中showLink为false的路由
function filterTree(data: RouteComponent[]) {
const newTree = data.filter(
(v: { meta: { showLink: boolean } }) => v.meta.showLink
(v: { meta: { showLink: boolean } }) => v.meta?.showLink !== false
);
newTree.forEach(
(v: { children }) => v.children && (v.children = filterTree(v.children))

View File

@@ -8,16 +8,29 @@ export const useEpThemeStore = defineStore({
state: () => ({
epThemeColor:
storageLocal.getItem("responsive-layout")?.epThemeColor ??
getConfig().EpThemeColor
getConfig().EpThemeColor,
epTheme:
storageLocal.getItem("responsive-layout")?.theme ?? getConfig().Theme
}),
getters: {
getEpThemeColor() {
return this.epThemeColor;
},
// 用于mix导航模式下hamburger-svg的fill属性
fill() {
if (this.epTheme === "light") {
return "#409eff";
} else if (this.epTheme === "yellow") {
return "#d25f00";
} else {
return "#fff";
}
}
},
actions: {
setEpThemeColor(newColor) {
const layout = storageLocal.getItem("responsive-layout");
this.epTheme = layout?.theme;
this.epThemeColor = newColor;
layout.epThemeColor = newColor;
storageLocal.setItem("responsive-layout", layout);

View File

@@ -17,8 +17,7 @@ export const useMultiTagsStore = defineStore({
meta: {
title: "menus.hshome",
icon: "home-filled",
i18n: true,
showLink: true
i18n: true
}
}
],

View File

@@ -60,8 +60,7 @@ export const useUserStore = defineStore({
meta: {
title: "menus.hshome",
icon: "home-filled",
i18n: true,
showLink: true
i18n: true
}
}
]);

View File

@@ -19,7 +19,7 @@
}
.el-overlay {
background-color: rgba(0, 0, 0, 0.05) !important;
background-color: rgb(0 0 0 / 5%) !important;
}
.el-drawer {

View File

@@ -25,7 +25,7 @@
}
.el-dropdown-menu {
padding: 2px 0 2px 0 !important;
padding: 2px 0 !important;
}
.el-range-separator {

View File

@@ -11,7 +11,7 @@ body {
padding: 0;
-moz-osx-font-smoothing: grayscale;
-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;
}
@@ -74,19 +74,11 @@ ul {
/* 灰色模式 */
.html-grey {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
}
/* 色弱模式 */
.html-weakness {
filter: invert(80%);
-webkit-filter: invert(80%);
-moz-filter: invert(80%);
-ms-filter: invert(80%);
-o-filter: invert(80%);
}
.pc-spacing {

View File

@@ -85,7 +85,7 @@
align-items: center;
}
.icon i {
.icon svg {
color: #d9d9d9;
transition: 0.5s;
}
@@ -107,7 +107,7 @@
padding: 0;
}
.input-group.focus .icon i {
.input-group.focus .icon svg {
color: #5392f0;
}

View File

@@ -306,6 +306,10 @@
height: 48px;
line-height: 48px;
background: $menuBg;
svg {
position: static !important;
}
}
.is-active > .el-sub-menu__title,
@@ -498,9 +502,7 @@
background-color: $menuActiveBefore;
content: "";
clear: both;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
-webkit-transform: translateY(0);
transform: translateY(0);
}
@@ -513,9 +515,7 @@
background-color: $menuActiveBefore;
content: "";
clear: both;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
-webkit-transform: translateY(0);
transform: translateY(0);
}
@@ -526,7 +526,6 @@
position: absolute;
height: 0;
width: 3px;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
top: 50%;
}
@@ -600,16 +599,6 @@ body[layout="vertical"] {
}
}
.el-sub-menu {
overflow: hidden;
& > .el-sub-menu__title {
.el-sub-menu__icon-arrow {
display: none;
}
}
}
/* 菜单折叠 */
.el-menu--collapse {
.el-sub-menu {
@@ -649,3 +638,64 @@ body[layout="horizontal"] {
transition: none !important;
}
}
body[layout="mix"] {
$sideBarWidth: 210px;
@include merge-style($sideBarWidth);
.el-menu {
--el-menu-hover-bg-color: transparent !important;
}
.hideSidebar {
.fixed-header {
width: calc(100% - 54px);
transition: width 0.28s;
}
.sidebar-container {
width: 54px !important;
}
.main-container {
margin-left: 54px;
}
.submenu-title-noDropdown {
padding: 0 !important;
position: relative;
.el-tooltip {
padding: 0 !important;
}
}
/* 菜单折叠 */
.el-menu--collapse {
.el-sub-menu {
& > .el-sub-menu__title {
& > span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
/* 无子菜单 */
.el-menu-item [class^="el-icon"] {
right: 5px;
}
.el-sub-menu__title [class^="el-icon"] {
right: 2px;
}
.submenu-title-noDropdown {
background: transparent !important;
}
}
}
}

View File

@@ -1,21 +0,0 @@
interface ProxyAlgorithm {
increaseIndexes<T>(val: Array<T>): Array<T>;
}
class algorithmProxy implements ProxyAlgorithm {
constructor() {}
// 数组每一项添加索引字段
public increaseIndexes<T>(val: Array<T>): Array<T> {
return Object.keys(val)
.map(v => {
return {
...val[v],
key: v
};
})
.filter(v => v.meta && v.meta.showLink);
}
}
export const algorithm = new algorithmProxy();

View File

@@ -20,7 +20,7 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
theme: config.Theme ?? "default",
darkMode: config.DarkMode ?? false,
sidebarStatus: config.SidebarStatus ?? true,
epThemeColor: config.EpThemeColor ?? "409EFF"
epThemeColor: config.EpThemeColor ?? "#409EFF"
}
},
configure: {
@@ -47,8 +47,7 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
meta: {
title: "menus.hshome",
i18n: true,
icon: "home-filled",
showLink: true
icon: "home-filled"
}
}
]

View File

@@ -46,3 +46,22 @@ export function deleteChildren(menuTree, pathList = []) {
}
return menuTree;
}
// 创建层级关系
export function buildHierarchyTree(menuTree, pathList = []) {
if (!Array.isArray(menuTree)) {
console.warn("menuTree must be an array");
return;
}
if (!menuTree || menuTree.length === 0) return;
for (const [key, node] of menuTree.entries()) {
node.id = key;
node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
node.pathList = [...pathList, node.id];
const hasChildren = node.children && node.children.length > 0;
if (hasChildren) {
buildHierarchyTree(node.children, node.pathList);
}
}
return menuTree;
}

View File

@@ -140,6 +140,7 @@ onMounted(() => {
.item-cut {
font-size: 1.5em;
height: 77px;
line-height: 77px;
text-align: center;
border: 1px solid #e5e4e9;
cursor: move;

View File

@@ -110,7 +110,7 @@ function onPwdBlur() {
}"
>
<div class="icon">
<i class="fa fa-user"></i>
<IconifyIconOffline icon="fa-user" width="14" height="14" />
</div>
<div>
<h5>用户名</h5>
@@ -139,7 +139,7 @@ function onPwdBlur() {
}"
>
<div class="icon">
<i class="fa fa-lock"></i>
<IconifyIconOffline icon="fa-lock" width="14" height="14" />
</div>
<div>
<h5>密码</h5>

View File

@@ -1,18 +1,20 @@
module.exports = {
root: true,
plugins: ["stylelint-order"],
customSyntax: "postcss-html",
extends: ["stylelint-config-standard", "stylelint-config-prettier"],
rules: {
"selector-class-pattern": null,
"selector-pseudo-class-no-unknown": [
true,
{
ignorePseudoClasses: ["deep"]
ignorePseudoClasses: ["global"]
}
],
"selector-pseudo-element-no-unknown": [
true,
{
ignorePseudoElements: ["v-deep", ":deep"]
ignorePseudoElements: ["v-deep"]
}
],
"at-rule-no-unknown": [
@@ -65,5 +67,26 @@ module.exports = {
{ severity: "warning" }
]
},
ignoreFiles: ["**/*.js", "**/*.jsx", "**/*.tsx", "**/*.ts", "**/*.json"]
ignoreFiles: ["**/*.js", "**/*.jsx", "**/*.tsx", "**/*.ts", "**/*.json"],
overrides: [
{
files: ["*.vue", "**/*.vue", "*.html", "**/*.html"],
extends: ["stylelint-config-recommended", "stylelint-config-html"],
rules: {
"keyframes-name-pattern": null,
"selector-pseudo-class-no-unknown": [
true,
{
ignorePseudoClasses: ["deep", "global"]
}
],
"selector-pseudo-element-no-unknown": [
true,
{
ignorePseudoElements: ["v-deep", "v-global", "v-slotted"]
}
]
}
}
]
};

2
types/global.d.ts vendored
View File

@@ -11,6 +11,7 @@ declare module "vue" {
export interface GlobalComponents {
IconifyIconOffline: typeof import("../src/components/ReIcon")["IconifyIconOffline"];
IconifyIconOnline: typeof import("../src/components/ReIcon")["IconifyIconOnline"];
FontIcon: typeof import("../src/components/ReIcon")["FontIcon"];
}
}
@@ -22,7 +23,6 @@ declare global {
mozCancelAnimationFrame: (handle: number) => void;
oCancelAnimationFrame: (handle: number) => void;
msCancelAnimationFrame: (handle: number) => void;
webkitRequestAnimationFrame: (callback: FrameRequestCallback) => number;
mozRequestAnimationFrame: (callback: FrameRequestCallback) => number;
oRequestAnimationFrame: (callback: FrameRequestCallback) => number;

View File

@@ -1,17 +1,7 @@
import { resolve } from "path";
import vue from "@vitejs/plugin-vue";
import svgLoader from "vite-svg-loader";
import legacy from "@vitejs/plugin-legacy";
import vueJsx from "@vitejs/plugin-vue-jsx";
import WindiCSS from "vite-plugin-windicss";
import { warpperEnv, regExps } from "./build";
import liveReload from "vite-plugin-live-reload";
import { viteMockServe } from "vite-plugin-mock";
import styleImport from "vite-plugin-style-import";
import ElementPlus from "unplugin-element-plus/vite";
import removeConsole from "vite-plugin-remove-console";
import { getPluginsList } from "./build/plugins";
import { UserConfigExport, ConfigEnv, loadEnv } from "vite";
import themePreprocessorPlugin from "@zougt/vite-plugin-theme-preprocessor";
// 当前执行node命令时文件夹的地址工作目录
const root: string = process.cwd();
@@ -37,7 +27,6 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
VITE_PROXY_DOMAIN,
VITE_PROXY_DOMAIN_REAL
} = warpperEnv(loadEnv(mode, root));
const prodMock = true;
return {
base: VITE_PUBLIC_PATH,
root,
@@ -81,114 +70,16 @@ export default ({ command, mode }: ConfigEnv): UserConfigExport => {
}
: null
},
plugins: [
vue(),
// jsx、tsx语法支持
vueJsx(),
WindiCSS(),
// 线上环境删除console
removeConsole(),
// 修改layout文件夹下的文件时自动重载浏览器 解决 https://github.com/xiaoxian521/vue-pure-admin/issues/170
liveReload(["src/layout/**/*"]),
// 自定义主题
themePreprocessorPlugin({
scss: {
multipleScopeVars: [
{
scopeName: "layout-theme-default",
path: pathResolve("src/layout/theme/default-vars.scss")
},
{
scopeName: "layout-theme-light",
path: pathResolve("src/layout/theme/light-vars.scss")
},
{
scopeName: "layout-theme-dusk",
path: pathResolve("src/layout/theme/dusk-vars.scss")
},
{
scopeName: "layout-theme-volcano",
path: pathResolve("src/layout/theme/volcano-vars.scss")
},
{
scopeName: "layout-theme-yellow",
path: pathResolve("src/layout/theme/yellow-vars.scss")
},
{
scopeName: "layout-theme-mingQing",
path: pathResolve("src/layout/theme/mingQing-vars.scss")
},
{
scopeName: "layout-theme-auroraGreen",
path: pathResolve("src/layout/theme/auroraGreen-vars.scss")
},
{
scopeName: "layout-theme-pink",
path: pathResolve("src/layout/theme/pink-vars.scss")
},
{
scopeName: "layout-theme-saucePurple",
path: pathResolve("src/layout/theme/saucePurple-vars.scss")
}
],
// 默认取 multipleScopeVars[0].scopeName
defaultScopeName: "",
// 在生产模式是否抽取独立的主题css文件extract为true以下属性有效
extract: true,
// 独立主题css文件的输出路径默认取 viteConfig.build.assetsDir 相对于 (viteConfig.build.outDir)
outputDir: "",
// 会选取defaultScopeName对应的主题css文件在html添加link
themeLinkTagId: "head",
// "head"||"head-prepend" || "body" ||"body-prepend"
themeLinkTagInjectTo: "head",
// 是否对抽取的css文件内对应scopeName的权重类名移除
removeCssScopeName: false,
// 可以自定义css文件名称的函数
customThemeCssFileName: scopeName => scopeName
}
}),
// svg组件化支持
svgLoader(),
// 按需加载vxe-table
styleImport({
libs: [
{
libraryName: "vxe-table",
esModule: true,
ensureStyleFile: true,
resolveComponent: name => `vxe-table/es/${name}`,
resolveStyle: name => `vxe-table/es/${name}/style.css`
}
]
}),
ElementPlus({}),
// mock支持
viteMockServe({
mockPath: "mock",
localEnabled: command === "serve",
prodEnabled: command !== "serve" && prodMock,
injectCode: `
import { setupProdMockServer } from './mockProdServer';
setupProdMockServer();
`,
logger: true
}),
// 是否为打包后的文件提供传统浏览器兼容性支持
VITE_LEGACY
? legacy({
targets: ["ie >= 11"],
additionalLegacyPolyfills: ["regenerator-runtime/runtime"]
})
: null
],
plugins: getPluginsList(command, VITE_LEGACY),
optimizeDeps: {
include: [
"pinia",
"vue-i18n",
"lodash-es",
"@vueuse/core",
"element-plus/lib/locale/lang/zh-cn",
"@iconify/vue",
"element-plus/lib/locale/lang/en",
"element-plus/lib/locale/lang/zh-cn",
"vxe-table/lib/locale/lang/zh-CN",
"vxe-table/lib/locale/lang/en-US"
],