feat: add mix nav (#187)

feat: add mix nav
This commit is contained in:
啝裳 2022-02-14 22:09:39 +08:00 committed by GitHub
parent a2dde02994
commit ef05b2b614
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1313 additions and 843 deletions

View File

@ -126,13 +126,13 @@ If you think this project is helpful to you, you can help the author buy a cup o
## 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 |
| :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: | | :--------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------: |
@ -140,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

@ -132,13 +132,13 @@ pnpm build
## 许可证 ## 许可证
原则上不收取任何费用及版权,可以放心使用 原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
[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

@ -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": "^2.0.0", "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",
@ -84,9 +84,9 @@
"@types/qs": "^6.9.7", "@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2", "@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": "^7.0.0", "@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.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",
@ -97,7 +97,7 @@
"eslint-plugin-vue": "^8.4.1", "eslint-plugin-vue": "^8.4.1",
"husky": "^7.0.4", "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-html": "^1.3.0",
"postcss-import": "14.0.0", "postcss-import": "14.0.0",
"postcss-scss": "^4.0.3", "postcss-scss": "^4.0.3",
@ -115,12 +115,12 @@
"stylelint-order": "^5.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.8.1",
"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": "^8.2.0", "vue-eslint-parser": "^8.2.0",
"windicss": "^3.4.3" "windicss": "^3.4.3"

1216
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

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

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

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 wholeMenus = usePermissionStoreHook().wholeMenus;
const {
logout,
onPanel,
changeTitle,
toggleSideBar,
handleResize,
menuSelect,
resolvePath,
pureApp,
usename,
getDropdownItemStyle
} = useNav();
let defaultActive = ref(null);
function getDefaultActive(routePath) {
//
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 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
} );
// //
// eslint-disable-next-line no-inner-declarations const parenetRoute = findRouteByPath(
function findCurrentRoute(routes) { parentPathArr[0] || path,
return routes.map(item => { usePermissionStoreHook().wholeMenus
if (item.path === indexPath) { );
// if (!parenetRoute?.children) return;
emitter.emit("changLayoutRoute", { subMenuData.value = parenetRoute?.children;
indexPath, }
parentPath getSubMenuData(route.path);
});
} 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

@ -106,7 +106,11 @@ const iconIsActive = computed(() => {
const dynamicTagView = () => { const dynamicTagView = () => {
const index = multiTags.value.findIndex(item => { 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); 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,9 +731,9 @@ 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 <component
:is="item.icon" :is="item.icon"

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

@ -11,18 +11,20 @@ export const routerArrays: Array<RouteConfigs> = [
} }
]; ];
export type routeMetaType = {
title?: string;
i18n?: boolean;
icon?: string;
showLink?: boolean;
savedPosition?: boolean;
authority?: Array<string>;
};
export type RouteConfigs = { export type RouteConfigs = {
path?: string; path?: string;
parentPath?: string; parentPath?: string;
query?: object; query?: object;
meta?: { meta?: routeMetaType;
title?: string;
i18n?: boolean;
icon?: string;
showLink?: boolean;
savedPosition?: boolean;
authority?: Array<string>;
};
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

@ -638,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

@ -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;