Compare commits

..

9 Commits
v2.9.0 ... 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
45 changed files with 1579 additions and 1590 deletions

View File

@@ -37,7 +37,7 @@ module.exports = {
"eslint:recommended", "eslint:recommended",
"@vue/typescript/recommended", "@vue/typescript/recommended",
"@vue/prettier", "@vue/prettier",
"@vue/prettier/@typescript-eslint" "@vue/eslint-config-typescript"
], ],
parser: "vue-eslint-parser", parser: "vue-eslint-parser",
parserOptions: { parserOptions: {
@@ -50,6 +50,10 @@ module.exports = {
} }
}, },
rules: { 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 "@typescript-eslint/no-explicit-any": "off", // any
"no-debugger": "off", "no-debugger": "off",
"@typescript-eslint/explicit-module-boundary-types": "off", // setup() "@typescript-eslint/explicit-module-boundary-types": "off", // setup()

View File

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

View File

@@ -1,3 +1,13 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- Added mix navigation
### 🐞 Bug fixes
- Fix tab page bug
# 2.9.0 (2022-2-5) # 2.9.0 (2022-2-5)
### 🎫 Feat ### 🎫 Feat

View File

@@ -1,3 +1,13 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- Added mix navigation
### 🐞 Bug fixes
- Fix tab page bug
# 2.9.0 (2022-2-5) # 2.9.0 (2022-2-5)
### 🎫 Feat ### 🎫 Feat

View File

@@ -1,3 +1,13 @@
# 3.0 (2022-2-14)
### 🎫 Feat
- 添加混合导航
### 🐞 Bug fixes
- 修复标签页 bug
# 2.9.0(2022-2-5) # 2.9.0(2022-2-5)
### 🎫 Feat ### 🎫 Feat

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" /> <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 ## 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) [MIT © xiaoxian521-2020](./LICENSE)
## Backers ## 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 | | 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 ## 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> <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" /> <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) [MIT © xiaoxian521-2020](./LICENSE)
## 捐赠者 ## 捐赠者
非常感谢你们的支持,相信项目会越来越好:heart: 非常感谢你们的支持,相信项目会越来越好 :heart:
| xueyuheng | taolei1990 | hang-kim | madwolfcrazy | limuen | | 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> <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

@@ -1,6 +1,6 @@
{ {
"name": "vue-pure-admin", "name": "vue-pure-admin",
"version": "2.9.0", "version": "3.0",
"private": true, "private": true,
"engines": { "engines": {
"node": ">= 16", "node": ">= 16",
@@ -33,17 +33,17 @@
"@ctrl/tinycolor": "^3.4.0", "@ctrl/tinycolor": "^3.4.0",
"@logicflow/core": "0.7.1", "@logicflow/core": "0.7.1",
"@logicflow/extension": "0.7.1", "@logicflow/extension": "0.7.1",
"@vueuse/core": "^7.5.5", "@vueuse/core": "^7.6.2",
"@vueuse/motion": "^2.0.0-beta.9", "@vueuse/motion": "^2.0.0-beta.9",
"@vueuse/shared": "^7.5.5", "@vueuse/shared": "^7.6.2",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^0.25.0", "axios": "^0.25.0",
"cropperjs": "^1.5.11", "cropperjs": "^1.5.11",
"css-color-function": "^1.3.3", "css-color-function": "^1.3.3",
"dayjs": "^1.10.7", "dayjs": "^1.10.7",
"driver.js": "^0.9.8", "driver.js": "^0.9.8",
"echarts": "^5.2.1", "echarts": "^5.3.0",
"element-plus": "1.3.0-beta.1", "element-plus": "^2.0.2",
"element-resize-detector": "^1.2.3", "element-resize-detector": "^1.2.3",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
@@ -57,7 +57,7 @@
"responsive-storage": "^1.0.11", "responsive-storage": "^1.0.11",
"rgb-hex": "^4.0.0", "rgb-hex": "^4.0.0",
"v-contextmenu": "3.0.0", "v-contextmenu": "3.0.0",
"vue": "^3.2.29", "vue": "^3.2.31",
"vue-i18n": "^9.2.0-beta.30", "vue-i18n": "^9.2.0-beta.30",
"vue-json-pretty": "^2.0.2", "vue-json-pretty": "^2.0.2",
"vue-router": "^4.0.12", "vue-router": "^4.0.12",
@@ -82,43 +82,47 @@
"@types/node": "14.14.14", "@types/node": "14.14.14",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "4.31.0", "@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "4.31.0", "@typescript-eslint/parser": "^5.10.2",
"@vitejs/plugin-legacy": "^1.6.4", "@vitejs/plugin-legacy": "^1.7.0",
"@vitejs/plugin-vue": "^2.1.0", "@vitejs/plugin-vue": "^2.2.0",
"@vitejs/plugin-vue-jsx": "^1.3.3", "@vitejs/plugin-vue-jsx": "^1.3.4",
"@vue/eslint-config-prettier": "6.0.0", "@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "7.0.0", "@vue/eslint-config-typescript": "^10.0.0",
"@zougt/vite-plugin-theme-preprocessor": "^1.4.4", "@zougt/vite-plugin-theme-preprocessor": "^1.4.4",
"autoprefixer": "^10.4.2", "autoprefixer": "^10.4.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "7.30.0", "eslint": "^8.8.0",
"eslint-plugin-prettier": "3.4.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "7.17.0", "eslint-plugin-vue": "^8.4.1",
"husky": "7.0.2", "husky": "^7.0.4",
"lint-staged": "11.1.2", "lint-staged": "11.1.2",
"postcss": "8.2.6", "postcss": "^8.4.6",
"postcss-html": "^1.3.0",
"postcss-import": "14.0.0", "postcss-import": "14.0.0",
"prettier": "2.3.2", "postcss-scss": "^4.0.3",
"prettier": "^2.5.1",
"pretty-quick": "3.1.1", "pretty-quick": "3.1.1",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"rollup-plugin-visualizer": "^5.5.4", "rollup-plugin-visualizer": "^5.5.4",
"sass": "^1.49.7", "sass": "^1.49.7",
"sass-loader": "^12.4.0", "sass-loader": "^12.4.0",
"stylelint": "13.13.1", "stylelint": "^14.3.0",
"stylelint-config-prettier": "8.0.2", "stylelint-config-html": "^1.0.0",
"stylelint-config-standard": "22.0.0", "stylelint-config-prettier": "^9.0.3",
"stylelint-order": "4.1.0", "stylelint-config-recommended": "^6.0.0",
"stylelint-config-standard": "^24.0.0",
"stylelint-order": "^5.0.0",
"typescript": "^4.5.5", "typescript": "^4.5.5",
"unplugin-element-plus": "^0.2.0", "unplugin-element-plus": "^0.2.0",
"vite": "^2.7.13", "vite": "2.7.13",
"vite-plugin-live-reload": "^2.1.0", "vite-plugin-live-reload": "^2.1.0",
"vite-plugin-mock": "^2.9.6", "vite-plugin-mock": "^2.9.6",
"vite-plugin-remove-console": "^0.0.6", "vite-plugin-remove-console": "^0.0.6",
"vite-plugin-style-import": "^1.4.1", "vite-plugin-style-import": "1.4.1",
"vite-plugin-windicss": "^1.6.1", "vite-plugin-windicss": "^1.7.0",
"vite-svg-loader": "2.2.0", "vite-svg-loader": "2.2.0",
"vue-eslint-parser": "7.10.0", "vue-eslint-parser": "^8.2.0",
"windicss": "^3.4.3" "windicss": "^3.4.3"
}, },
"repository": "git@github.com:xiaoxian521/vue-pure-admin.git", "repository": "git@github.com:xiaoxian521/vue-pure-admin.git",

1936
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
background: #fff; background: #fff;
font-size: 66px; font-size: 66px;
color: #fff; 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; text-align: center;
font-family: "Helvetica Neue"; font-family: "Helvetica Neue";
} }
@@ -58,7 +58,7 @@
.m-flipper.down.go .front::before { .m-flipper.down.go .front::before {
transform-origin: 50% 100%; transform-origin: 50% 100%;
animation: frontFlipDown 0.6s ease-in-out both; 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; backface-visibility: hidden;
} }
@@ -85,7 +85,7 @@
.m-flipper.up.go .front::after { .m-flipper.up.go .front::after {
transform-origin: 50% 0; transform-origin: 50% 0;
animation: frontFlipUp 0.6s ease-in-out both; 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; backface-visibility: hidden;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -61,6 +61,7 @@ let themeColors = ref<Array<themeColorsType>>([
const verticalRef = templateRef<HTMLElement | null>("verticalRef", null); const verticalRef = templateRef<HTMLElement | null>("verticalRef", null);
const horizontalRef = templateRef<HTMLElement | null>("horizontalRef", null); const horizontalRef = templateRef<HTMLElement | null>("horizontalRef", null);
const mixRef = templateRef<HTMLElement | null>("mixRef", null);
let layoutTheme = let layoutTheme =
ref(storageLocal.getItem("responsive-layout")) || 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; const storageConfigure = instance.configure;
storageConfigure[key] = val; storageConfigure[key] = val;
instance.configure = storageConfigure; instance.configure = storageConfigure;
@@ -117,7 +118,7 @@ function toggleClass(flag: boolean, clsName: string, target?: HTMLElement) {
// 灰色模式设置 // 灰色模式设置
const greyChange = (value): void => { const greyChange = (value): void => {
toggleClass(settings.greyVal, "html-grey", document.querySelector("html")); toggleClass(settings.greyVal, "html-grey", document.querySelector("html"));
changeStorageConfigure("grey", value); storageConfigureChange("grey", value);
}; };
// 色弱模式设置 // 色弱模式设置
@@ -127,29 +128,30 @@ const weekChange = (value): void => {
"html-weakness", "html-weakness",
document.querySelector("html") document.querySelector("html")
); );
changeStorageConfigure("weak", value); storageConfigureChange("weak", value);
}; };
const tagsChange = () => { const tagsChange = () => {
let showVal = settings.tabsVal; let showVal = settings.tabsVal;
changeStorageConfigure("hideTabs", showVal); storageConfigureChange("hideTabs", showVal);
emitter.emit("tagViewsChange", showVal); emitter.emit("tagViewsChange", showVal);
}; };
const multiTagsCacheChange = () => { const multiTagsCacheChange = () => {
let multiTagsCache = settings.multiTagsCache; let multiTagsCache = settings.multiTagsCache;
changeStorageConfigure("multiTagsCache", multiTagsCache); storageConfigureChange("multiTagsCache", multiTagsCache);
useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache); useMultiTagsStoreHook().multiTagsCacheChange(multiTagsCache);
}; };
// 清空缓存并返回登录页 // 清空缓存并返回登录页
function onReset() { function onReset() {
toggleClass(getConfig().Grey, "html-grey", document.querySelector("html")); router.push("/login");
toggleClass( const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig();
getConfig().Weak, useAppStoreHook().setLayout(Layout);
"html-weakness", useEpThemeStoreHook().setEpThemeColor(EpThemeColor);
document.querySelector("html") useMultiTagsStoreHook().multiTagsCacheChange(MultiTagsCache);
); toggleClass(Grey, "html-grey", document.querySelector("html"));
toggleClass(Weak, "html-weakness", document.querySelector("html"));
useMultiTagsStoreHook().handleTags("equal", [ useMultiTagsStoreHook().handleTags("equal", [
{ {
path: "/welcome", path: "/welcome",
@@ -161,23 +163,20 @@ function onReset() {
} }
} }
]); ]);
useMultiTagsStoreHook().multiTagsCacheChange(getConfig().MultiTagsCache);
useEpThemeStoreHook().setEpThemeColor(getConfig().EpThemeColor);
storageLocal.clear(); storageLocal.clear();
storageSession.clear(); storageSession.clear();
router.push("/login");
} }
function onChange(label) { function onChange(label) {
changeStorageConfigure("showModel", label); storageConfigureChange("showModel", label);
emitter.emit("tagViewsShowModel", label); emitter.emit("tagViewsShowModel", label);
} }
// 侧边栏Logo // 侧边栏Logo
function logoChange() { function logoChange() {
unref(logoVal) unref(logoVal)
? changeStorageConfigure("showLogo", true) ? storageConfigureChange("showLogo", true)
: changeStorageConfigure("showLogo", false); : storageConfigureChange("showLogo", false);
emitter.emit("logoChange", unref(logoVal)); emitter.emit("logoChange", unref(logoVal));
} }
@@ -192,10 +191,17 @@ watch(instance, ({ layout }) => {
case "vertical": case "vertical":
toggleClass(true, isSelect, unref(verticalRef)); toggleClass(true, isSelect, unref(verticalRef));
debounce(setFalse([horizontalRef]), 50); debounce(setFalse([horizontalRef]), 50);
debounce(setFalse([mixRef]), 50);
break; break;
case "horizontal": case "horizontal":
toggleClass(true, isSelect, unref(horizontalRef)); toggleClass(true, isSelect, unref(horizontalRef));
debounce(setFalse([verticalRef]), 50); 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; break;
} }
}); });
@@ -315,7 +321,7 @@ 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 class="item" content="左侧模式" placement="bottom">
<li <li
:class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''" :class="layoutTheme.layout === 'vertical' ? $style.isSelect : ''"
ref="verticalRef" ref="verticalRef"
@@ -326,7 +332,7 @@ nextTick(() => {
</li> </li>
</el-tooltip> </el-tooltip>
<el-tooltip class="item" content="顶部菜单模式" placement="bottom"> <el-tooltip class="item" content="顶部模式" placement="bottom">
<li <li
:class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''" :class="layoutTheme.layout === 'horizontal' ? $style.isSelect : ''"
ref="horizontalRef" ref="horizontalRef"
@@ -336,6 +342,17 @@ nextTick(() => {
<div></div> <div></div>
</li> </li>
</el-tooltip> </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> </ul>
<el-divider v-show="!dataTheme">主题色</el-divider> <el-divider v-show="!dataTheme">主题色</el-divider>
@@ -481,15 +498,14 @@ nextTick(() => {
.pure-theme { .pure-theme {
margin-top: 25px; margin-top: 25px;
width: 100%; width: 100%;
height: 100px; height: 50px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-around; justify-content: space-around;
li { li {
margin: 10px; width: 18%;
width: 36%; height: 45px;
height: 70px;
background: #f0f2f5; background: #f0f2f5;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
@@ -527,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,126 +1,61 @@
<script setup lang="ts"> <script setup lang="ts">
import {
computed,
unref,
watch,
nextTick,
onMounted,
getCurrentInstance
} from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { emitter } from "/@/utils/mitt"; import { useNav } from "../../hooks/nav";
import Notice from "../notice/index.vue"; import Notice from "../notice/index.vue";
import { templateRef } from "@vueuse/core"; import { templateRef } from "@vueuse/core";
import SidebarItem from "./sidebarItem.vue"; import SidebarItem from "./sidebarItem.vue";
import avatars from "/@/assets/avatars.jpg"; import avatars from "/@/assets/avatars.jpg";
import screenfull from "../screenfull/index.vue"; import screenfull from "../screenfull/index.vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { storageSession } from "/@/utils/storage";
import { deviceDetection } from "/@/utils/deviceDetection"; import { deviceDetection } from "/@/utils/deviceDetection";
import { watch, nextTick, onMounted, getCurrentInstance } from "vue";
import { usePermissionStoreHook } from "/@/store/modules/permission"; import { usePermissionStoreHook } from "/@/store/modules/permission";
import globalization from "/@/assets/svg/globalization.svg?component"; 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 = const instance =
getCurrentInstance().appContext.config.globalProperties.$storage; getCurrentInstance().appContext.config.globalProperties.$storage;
const title = const title =
getCurrentInstance().appContext.config.globalProperties.$config?.Title; getCurrentInstance().appContext.config.globalProperties.$config?.Title;
const menuRef = templateRef<ElRef | null>("menu", null); const {
const route = useRoute(); logout,
const router = useRouter(); backHome,
const routers = useRouter().options.routes; onPanel,
let usename = storageSession.getItem("info")?.username; changeTitle,
const { locale, t } = useI18n(); handleResize,
menuSelect,
usename,
getDropdownItemStyle
} = useNav();
const getDropdownItemStyle = computed(() => { onMounted(() => {
return t => { nextTick(() => {
return { handleResize(menuRef.value);
background: locale.value === t ? "#1b2a47" : "", });
color: locale.value === t ? "#f4f4f5" : "#000"
};
};
}); });
watch( watch(
() => locale.value, () => locale.value,
() => { () => {
//@ts-ignore changeTitle(route.meta);
// 动态title
document.title = t(unref(route.meta.title));
} }
); );
// 退出登录
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(routers);
};
function backHome() {
router.push("/welcome");
}
function handleResize() {
// @ts-ignore
menuRef.value.handleResize();
}
// 简体中文
function translationCh() { function translationCh() {
instance.locale = { locale: "zh" }; instance.locale = { locale: "zh" };
locale.value = "zh"; locale.value = "zh";
handleResize(); handleResize(menuRef.value);
} }
// English
function translationEn() { function translationEn() {
instance.locale = { locale: "en" }; instance.locale = { locale: "en" };
locale.value = "en"; locale.value = "en";
handleResize(); handleResize(menuRef.value);
} }
onMounted(() => {
nextTick(() => {
handleResize();
});
});
</script> </script>
<template> <template>
@@ -135,12 +70,11 @@ onMounted(() => {
</div> </div>
<el-menu <el-menu
ref="menu" ref="menu"
:default-active="activeMenu"
unique-opened
router
class="horizontal-header-menu" class="horizontal-header-menu"
mode="horizontal" mode="horizontal"
@select="menuSelect" :default-active="route.path"
router
@select="indexPath => menuSelect(indexPath, routers)"
> >
<sidebar-item <sidebar-item
v-for="route in usePermissionStoreHook().wholeMenus" v-for="route in usePermissionStoreHook().wholeMenus"
@@ -160,14 +94,14 @@ onMounted(() => {
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="translation"> <el-dropdown-menu class="translation">
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle('zh')" :style="getDropdownItemStyle(locale, 'zh')"
@click="translationCh" @click="translationCh"
><el-icon class="check-zh" v-show="locale === 'zh'" ><el-icon class="check-zh" v-show="locale === 'zh'"
><IconifyIconOffline icon="check" /></el-icon ><IconifyIconOffline icon="check" /></el-icon
>简体中文</el-dropdown-item >简体中文</el-dropdown-item
> >
<el-dropdown-item <el-dropdown-item
:style="getDropdownItemStyle('en')" :style="getDropdownItemStyle(locale, 'en')"
@click="translationEn" @click="translationEn"
><el-icon class="check-en" v-show="locale === 'en'" ><el-icon class="check-en" v-show="locale === 'en'"
><IconifyIconOffline icon="check" /></el-icon ><IconifyIconOffline icon="check" /></el-icon
@@ -207,14 +141,8 @@ onMounted(() => {
<style lang="scss" scoped> <style lang="scss" scoped>
.translation { .translation {
.el-dropdown-menu__item { ::v-deep(.el-dropdown-menu__item) {
padding: 5px 40px !important; padding: 5px 40px;
}
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
} }
.check-zh { .check-zh {
@@ -231,16 +159,10 @@ onMounted(() => {
.logout { .logout {
max-width: 120px; max-width: 120px;
.el-dropdown-menu__item { ::v-deep(.el-dropdown-menu__item) {
min-width: 100%; min-width: 100%;
display: inline-flex; display: inline-flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.el-dropdown-menu__item:focus,
.el-dropdown-menu__item:not(.is-disabled):hover {
color: #606266;
background: #f0f0f0;
}
} }
</style> </style>

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,21 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { import { ref, PropType, nextTick, computed, CSSProperties } from "vue";
ref,
PropType,
nextTick,
computed,
CSSProperties,
getCurrentInstance
} from "vue";
import path from "path"; import path from "path";
import { useNav } from "../../hooks/nav";
import { childrenType } from "../../types"; import { childrenType } from "../../types";
import { transformI18n } from "/@/plugins/i18n"; import { transformI18n } from "/@/plugins/i18n";
import { useAppStoreHook } from "/@/store/modules/app"; import { useAppStoreHook } from "/@/store/modules/app";
import { useRenderIcon } from "/@/components/ReIcon/src/hooks"; import { useRenderIcon } from "/@/components/ReIcon/src/hooks";
const instance = getCurrentInstance().appContext.app.config.globalProperties; const { pureApp } = useNav();
const menuMode = instance.$storage.layout?.layout === "vertical"; const menuMode = ["vertical", "mix"].includes(pureApp.layout);
const pureApp = useAppStoreHook();
const props = defineProps({ const props = defineProps({
item: { item: {
@@ -105,7 +98,6 @@ function hoverMenu(key) {
: Object.assign(key, { : Object.assign(key, {
showTooltip: false showTooltip: false
}); });
hoverMenuMap.set(key, true); hoverMenuMap.set(key, true);
}); });
} }
@@ -132,8 +124,8 @@ function hasOneShowingChild(
function resolvePath(routePath) { function resolvePath(routePath) {
const httpReg = /^http(s?):\/\//; const httpReg = /^http(s?):\/\//;
if (httpReg.test(routePath)) { if (httpReg.test(routePath) || httpReg.test(props.basePath)) {
return props.basePath + "/" + routePath; return routePath || props.basePath;
} else { } else {
return path.resolve(props.basePath, routePath); return path.resolve(props.basePath, routePath);
} }
@@ -162,6 +154,18 @@ function resolvePath(routePath) {
" "
></component> ></component>
</el-icon> </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> <template #title>
<div :style="getDivStyle"> <div :style="getDivStyle">
<span v-if="!menuMode">{{ <span v-if="!menuMode">{{

View File

@@ -1,60 +1,56 @@
<script setup lang="ts"> <script setup lang="ts">
import Logo from "./logo.vue"; import Logo from "./logo.vue";
import { emitter } from "/@/utils/mitt"; import { emitter } from "/@/utils/mitt";
import { useNav } from "../../hooks/nav";
import SidebarItem from "./sidebarItem.vue"; import SidebarItem from "./sidebarItem.vue";
import { storageLocal } from "/@/utils/storage"; import { storageLocal } from "/@/utils/storage";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { computed, ref, onBeforeMount } from "vue"; import { ref, computed, watch, onBeforeMount } from "vue";
import { useAppStoreHook } from "/@/store/modules/app"; 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 pureApp = useAppStoreHook(); const routers = useRouter().options.routes;
const router = useRouter().options.routes;
const showLogo = ref( const showLogo = ref(
storageLocal.getItem("responsive-configure")?.showLogo ?? true storageLocal.getItem("responsive-configure")?.showLogo ?? true
); );
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus; const { pureApp, isCollapse, menuSelect } = useNav();
});
const activeMenu = computed((): string => { let subMenuData = ref([]);
const { meta, path } = route;
if (meta.activeMenu) { const menuData = computed(() => {
// @ts-ignore return pureApp.layout === "mix"
return meta.activeMenu; ? subMenuData.value
} : usePermissionStoreHook().wholeMenus;
return path;
}); });
const menuSelect = (indexPath: string): void => { function getSubMenuData(path) {
let parentPath = ""; // path的上级路由组成的数组
let parentPathIndex = indexPath.lastIndexOf("/"); const parentPathArr = getParentPaths(
if (parentPathIndex > 0) { path,
parentPath = indexPath.slice(0, parentPathIndex); usePermissionStoreHook().wholeMenus
);
// 当前路由的父级路由信息
const parenetRoute = findRouteByPath(
parentPathArr[0] || path,
usePermissionStoreHook().wholeMenus
);
if (!parenetRoute?.children) return;
subMenuData.value = parenetRoute?.children;
} }
// 找到当前路由的信息 getSubMenuData(route.path);
// 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(router);
};
onBeforeMount(() => { onBeforeMount(() => {
emitter.on("logoChange", key => { emitter.on("logoChange", key => {
showLogo.value = key; showLogo.value = key;
}); });
}); });
watch(
() => route.path,
() => getSubMenuData(route.path)
);
</script> </script>
<template> <template>
@@ -62,21 +58,21 @@ onBeforeMount(() => {
<Logo v-if="showLogo" :collapse="isCollapse" /> <Logo v-if="showLogo" :collapse="isCollapse" />
<el-scrollbar wrap-class="scrollbar-wrapper"> <el-scrollbar wrap-class="scrollbar-wrapper">
<el-menu <el-menu
:default-active="activeMenu" :default-active="route.path"
:collapse="isCollapse" :collapse="isCollapse"
unique-opened unique-opened
router router
:collapse-transition="false" :collapse-transition="false"
mode="vertical" mode="vertical"
class="outer-most" class="outer-most"
@select="menuSelect" @select="indexPath => menuSelect(indexPath, routers)"
> >
<sidebar-item <sidebar-item
v-for="route in usePermissionStoreHook().wholeMenus" v-for="routes in menuData"
:key="route.path" :key="routes.path"
:item="route" :item="routes"
class="outer-most" class="outer-most"
:base-path="route.path" :base-path="routes.path"
/> />
</el-menu> </el-menu>
</el-scrollbar> </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 { @keyframes rotate {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
@@ -80,7 +50,7 @@
.scroll-item { .scroll-item {
border-radius: 3px 3px 0 0; border-radius: 3px 3px 0 0;
padding: 0 6px 0 6px; padding: 0 6px;
box-shadow: 0 0 1px #888; box-shadow: 0 0 1px #888;
position: relative; position: relative;
margin-right: 4px; margin-right: 4px;
@@ -92,7 +62,7 @@
.el-icon-close { .el-icon-close {
font-size: 10px; font-size: 10px;
color: #1890ff; color: var(--el-color-primary);
cursor: pointer; cursor: pointer;
position: absolute; position: absolute;
top: 50%; top: 50%;
@@ -123,7 +93,7 @@
a { a {
text-decoration: none; text-decoration: none;
color: #666; color: #666;
padding: 0 4px 0 4px; padding: 0 4px;
} }
.scroll-container { .scroll-container {
@@ -190,7 +160,8 @@
align-items: center; align-items: center;
&:hover { &:hover {
background: #eee; background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
} }
svg { svg {
@@ -236,7 +207,7 @@
} }
.scroll-item.is-active { .scroll-item.is-active {
background-color: #eaf4fe; background-color: var(--el-color-primary-light-9);
position: relative; position: relative;
color: #fff; color: #fff;
@@ -249,7 +220,7 @@
} }
a { a {
color: #1890ff; color: var(--el-color-primary);
} }
} }
@@ -288,10 +259,10 @@
/* 卡片模式下鼠标移入显示蓝色边框 */ /* 卡片模式下鼠标移入显示蓝色边框 */
.card-in { .card-in {
color: #1890ff; color: var(--el-color-primary);
a { a {
color: #1890ff; color: var(--el-color-primary);
} }
} }
@@ -312,7 +283,7 @@
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
background: #1890ff; background: var(--el-color-primary);
} }
/* 灵动模式下鼠标移入显示蓝色进度条 */ /* 灵动模式下鼠标移入显示蓝色进度条 */
@@ -322,7 +293,7 @@
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
background: #1890ff; background: var(--el-color-primary);
animation: scheduleInWidth 400ms ease-in; animation: scheduleInWidth 400ms ease-in;
} }
@@ -333,14 +304,11 @@
position: absolute; position: absolute;
left: 0; left: 0;
bottom: 0; bottom: 0;
background: #1890ff; background: var(--el-color-primary);
animation: scheduleOutWidth 400ms ease-in; animation: scheduleOutWidth 400ms ease-in;
} }
/* 刷新按钮动画效果 */ /* 刷新按钮动画效果 */
.refresh-button { .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; animation: rotate 600ms linear infinite;
} }

View File

@@ -106,7 +106,11 @@ const iconIsActive = computed(() => {
const dynamicTagView = () => { const dynamicTagView = () => {
const index = multiTags.value.findIndex(item => { const index = multiTags.value.findIndex(item => {
if (item?.query) {
return isEqual(route?.query, item?.query);
} else {
return item.path === route.path; return item.path === route.path;
}
}); });
moveToView(index); moveToView(index);
}; };
@@ -423,6 +427,11 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
}); });
} }
function handleCommand(command: object) {
const { key, item } = command;
onClickDrop(key, item);
}
// 触发右键中菜单的点击事件 // 触发右键中菜单的点击事件
function selectTag(key, item) { function selectTag(key, item) {
onClickDrop(key, item, currentSelect.value); onClickDrop(key, item, currentSelect.value);
@@ -709,7 +718,11 @@ const getContextMenuStyle = computed((): CSSProperties => {
</el-icon> </el-icon>
</li> </li>
<li> <li>
<el-dropdown trigger="click" placement="bottom-end"> <el-dropdown
trigger="click"
placement="bottom-end"
@command="handleCommand"
>
<el-icon> <el-icon>
<IconifyIconOffline icon="arrow-down" /> <IconifyIconOffline icon="arrow-down" />
</el-icon> </el-icon>
@@ -718,11 +731,15 @@ const getContextMenuStyle = computed((): CSSProperties => {
<el-dropdown-item <el-dropdown-item
v-for="(item, key) in tagsViews" v-for="(item, key) in tagsViews"
:key="key" :key="key"
:command="{ key, item }"
:divided="item.divided" :divided="item.divided"
:disabled="item.disabled" :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) }} {{ $t(item.text) }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </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: () => [ default: () => [
!pureSetting.hiddenSideBar && layout.value.includes("vertical") !pureSetting.hiddenSideBar &&
(layout.value.includes("vertical") || layout.value.includes("mix"))
? h(navbar) ? h(navbar)
: h("div"), : h("div"),
!pureSetting.hiddenSideBar && layout.value.includes("horizontal") !pureSetting.hiddenSideBar && layout.value.includes("horizontal")
@@ -213,7 +214,10 @@ const layoutHeader = defineComponent({
@click="useAppStoreHook().toggleSideBar()" @click="useAppStoreHook().toggleSideBar()"
/> />
<Vertical <Vertical
v-show="!pureSetting.hiddenSideBar && layout.includes('vertical')" v-show="
!pureSetting.hiddenSideBar &&
(layout.includes('vertical') || layout.includes('mix'))
"
/> />
<div <div
:class="[ :class="[

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,11 +11,7 @@ export const routerArrays: Array<RouteConfigs> = [
} }
]; ];
export type RouteConfigs = { export type routeMetaType = {
path?: string;
parentPath?: string;
query?: object;
meta?: {
title?: string; title?: string;
i18n?: boolean; i18n?: boolean;
icon?: string; icon?: string;
@@ -23,6 +19,12 @@ export type RouteConfigs = {
savedPosition?: boolean; savedPosition?: boolean;
authority?: Array<string>; authority?: Array<string>;
}; };
export type RouteConfigs = {
path?: string;
parentPath?: string;
query?: object;
meta?: routeMetaType;
children?: RouteConfigs[]; children?: RouteConfigs[];
name?: string; name?: string;
}; };
@@ -71,6 +73,8 @@ export type childrenType = {
}; };
}; };
showTooltip?: boolean; showTooltip?: boolean;
parentId?: number;
pathList?: number[];
}; };
export type themeColorsType = { export type themeColorsType = {

View File

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

View File

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

View File

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

View File

@@ -8,16 +8,29 @@ export const useEpThemeStore = defineStore({
state: () => ({ state: () => ({
epThemeColor: epThemeColor:
storageLocal.getItem("responsive-layout")?.epThemeColor ?? storageLocal.getItem("responsive-layout")?.epThemeColor ??
getConfig().EpThemeColor getConfig().EpThemeColor,
epTheme:
storageLocal.getItem("responsive-layout")?.theme ?? getConfig().Theme
}), }),
getters: { getters: {
getEpThemeColor() { getEpThemeColor() {
return this.epThemeColor; 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: { actions: {
setEpThemeColor(newColor) { setEpThemeColor(newColor) {
const layout = storageLocal.getItem("responsive-layout"); const layout = storageLocal.getItem("responsive-layout");
this.epTheme = layout?.theme;
this.epThemeColor = newColor; this.epThemeColor = newColor;
layout.epThemeColor = newColor; layout.epThemeColor = newColor;
storageLocal.setItem("responsive-layout", layout); storageLocal.setItem("responsive-layout", layout);

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ body {
padding: 0; padding: 0;
-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, font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB,
Microsoft YaHei, Arial, sans-serif; Microsoft YaHei, Arial, sans-serif;
} }
@@ -74,19 +74,11 @@ ul {
/* 灰色模式 */ /* 灰色模式 */
.html-grey { .html-grey {
filter: grayscale(100%); filter: grayscale(100%);
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
} }
/* 色弱模式 */ /* 色弱模式 */
.html-weakness { .html-weakness {
filter: invert(80%); filter: invert(80%);
-webkit-filter: invert(80%);
-moz-filter: invert(80%);
-ms-filter: invert(80%);
-o-filter: invert(80%);
} }
.pc-spacing { .pc-spacing {

View File

@@ -502,9 +502,7 @@
background-color: $menuActiveBefore; background-color: $menuActiveBefore;
content: ""; content: "";
clear: both; clear: both;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
-webkit-transform: translateY(0);
transform: translateY(0); transform: translateY(0);
} }
@@ -517,9 +515,7 @@
background-color: $menuActiveBefore; background-color: $menuActiveBefore;
content: ""; content: "";
clear: both; clear: both;
-webkit-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
-webkit-transform: translateY(0);
transform: translateY(0); transform: translateY(0);
} }
@@ -530,7 +526,6 @@
position: absolute; position: absolute;
height: 0; height: 0;
width: 3px; width: 3px;
-webkit-transform: translateY(-50%);
transform: translateY(-50%); transform: translateY(-50%);
top: 50%; top: 50%;
} }
@@ -643,3 +638,64 @@ body[layout="horizontal"] {
transition: none !important; 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

@@ -20,7 +20,7 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
theme: config.Theme ?? "default", theme: config.Theme ?? "default",
darkMode: config.DarkMode ?? false, darkMode: config.DarkMode ?? false,
sidebarStatus: config.SidebarStatus ?? true, sidebarStatus: config.SidebarStatus ?? true,
epThemeColor: config.EpThemeColor ?? "409EFF" epThemeColor: config.EpThemeColor ?? "#409EFF"
} }
}, },
configure: { configure: {

View File

@@ -46,3 +46,22 @@ export function deleteChildren(menuTree, pathList = []) {
} }
return menuTree; 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 { .item-cut {
font-size: 1.5em; font-size: 1.5em;
height: 77px; height: 77px;
line-height: 77px;
text-align: center; text-align: center;
border: 1px solid #e5e4e9; border: 1px solid #e5e4e9;
cursor: move; cursor: move;

View File

@@ -1,18 +1,20 @@
module.exports = { module.exports = {
root: true, root: true,
plugins: ["stylelint-order"], plugins: ["stylelint-order"],
customSyntax: "postcss-html",
extends: ["stylelint-config-standard", "stylelint-config-prettier"], extends: ["stylelint-config-standard", "stylelint-config-prettier"],
rules: { rules: {
"selector-class-pattern": null,
"selector-pseudo-class-no-unknown": [ "selector-pseudo-class-no-unknown": [
true, true,
{ {
ignorePseudoClasses: ["deep"] ignorePseudoClasses: ["global"]
} }
], ],
"selector-pseudo-element-no-unknown": [ "selector-pseudo-element-no-unknown": [
true, true,
{ {
ignorePseudoElements: ["v-deep", ":deep"] ignorePseudoElements: ["v-deep"]
} }
], ],
"at-rule-no-unknown": [ "at-rule-no-unknown": [
@@ -65,5 +67,26 @@ module.exports = {
{ severity: "warning" } { 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"]
}
]
}
}
]
}; };