perf: 同步完整版分支代码

This commit is contained in:
xiaoxian521 2022-02-15 23:16:15 +08:00
parent 5300781d05
commit e161102495
21 changed files with 1240 additions and 756 deletions

View File

@ -40,3 +40,7 @@ bilibili: https://www.bilibili.com/video/BV1534y1S7HV/
## ⚠️ Note ## ⚠️ Note
The lite version does not accept any issues and prs. If you have any questions, please go to the full version https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose to mention it, thank you! ! ! The lite version does not accept any issues and prs. If you have any questions, please go to the full version https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose to mention it, thank you! ! !
## License
In principle, no fees and copyrights are charged, and you can use it with confidence, but if you need secondary open source, please contact the author for permission!

View File

@ -52,3 +52,7 @@ pnpm remove 包名
## ⚠️ 注意 ## ⚠️ 注意
精简版不接受任何 issues 和 pr如果有问题请到完整版 https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose 去提,谢谢!!! 精简版不接受任何 issues 和 pr如果有问题请到完整版 https://github.com/xiaoxian521/vue-pure-admin/issues/new/choose 去提,谢谢!!!
## 许可证
原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!

View File

@ -1,6 +1,6 @@
{ {
"name": "pure-admin-thin", "name": "pure-admin-thin",
"version": "2.9.0", "version": "3.0",
"private": true, "private": true,
"engines": { "engines": {
"node": ">= 16", "node": ">= 16",
@ -30,13 +30,13 @@
], ],
"dependencies": { "dependencies": {
"@ctrl/tinycolor": "^3.4.0", "@ctrl/tinycolor": "^3.4.0",
"@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",
"css-color-function": "^1.3.3", "css-color-function": "^1.3.3",
"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",
@ -49,7 +49,7 @@
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"responsive-storage": "^1.0.11", "responsive-storage": "^1.0.11",
"rgb-hex": "^4.0.0", "rgb-hex": "^4.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-router": "^4.0.12", "vue-router": "^4.0.12",
"vue-types": "^4.1.1" "vue-types": "^4.1.1"
@ -70,9 +70,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.1",
"@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.7",
"@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",
@ -83,7 +83,7 @@
"eslint-plugin-vue": "^8.4.1", "eslint-plugin-vue": "^8.4.1",
"husky": "7.0.2", "husky": "7.0.2",
"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",
@ -101,12 +101,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.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.3", "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"

1011
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

@ -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 {
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
} );
// //
// 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

@ -10,13 +10,14 @@ import {
formatTwoStageRoutes, formatTwoStageRoutes,
formatFlatteningRoutes formatFlatteningRoutes
} from "../utils"; } from "../utils";
import { buildHierarchyTree } from "/@/utils/tree";
// 原始静态路由(未做任何处理) // 原始静态路由(未做任何处理)
const routes = [homeRouter, errorRouter, externalLink]; const routes = [homeRouter, errorRouter, externalLink];
// 导出处理后的静态路由(三级及以上的路由全部拍成二级) // 导出处理后的静态路由(三级及以上的路由全部拍成二级)
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;
}
}
}
}

67
src/utils/tree.ts Normal file
View File

@ -0,0 +1,67 @@
/**
* uniqueId
* @param {Array} {menuTree }
* @param {return}} expandedPaths uniqueId组成的数组
*/
const expandedPaths = [];
export function extractPathList(menuTree) {
if (!Array.isArray(menuTree)) {
console.warn("menuTree must be an array");
return;
}
if (!menuTree || menuTree.length === 0) return;
for (const node of menuTree) {
const hasChildren = node.children && node.children.length > 0;
if (hasChildren) {
extractPathList(node.children);
}
expandedPaths.push(node.uniqueId);
}
return expandedPaths;
}
/**
* children的length为1children并自动组建唯一uniqueId
* @param {Array} {menuTree }
* @param {Array} {pathList id组成的数组}
* @param {return}}
*/
export function deleteChildren(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()) {
if (node.children && node.children.length === 1) delete node.children;
node.id = key;
node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
node.pathList = [...pathList, node.id];
node.uniqueId =
node.pathList.length > 1 ? node.pathList.join("-") : node.pathList[0];
const hasChildren = node.children && node.children.length > 0;
if (hasChildren) {
deleteChildren(node.children, node.pathList);
}
}
return menuTree;
}
// 创建层级关系
export function buildHierarchyTree(menuTree, pathList = []) {
if (!Array.isArray(menuTree)) {
console.warn("menuTree must be an array");
return;
}
if (!menuTree || menuTree.length === 0) return;
for (const [key, node] of menuTree.entries()) {
node.id = key;
node.parentId = pathList.length ? pathList[pathList.length - 1] : null;
node.pathList = [...pathList, node.id];
const hasChildren = node.children && node.children.length > 0;
if (hasChildren) {
buildHierarchyTree(node.children, node.pathList);
}
}
return menuTree;
}