feat: 导航混合
Former-commit-id: 5942884a809813c87b6e49cefd4343aa07169b91
This commit is contained in:
41
src/layout/components/Sidebar/LeftMenu.vue
Normal file
41
src/layout/components/Sidebar/LeftMenu.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
import SidebarItem from "./SidebarItem.vue";
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
|
import { useAppStore } from "@/store/modules/app";
|
||||||
|
import variables from "@/styles/variables.module.scss";
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const currRoute = useRoute();
|
||||||
|
const layout = computed(() => settingsStore.layout);
|
||||||
|
const props = defineProps({
|
||||||
|
menuList: {
|
||||||
|
required: true,
|
||||||
|
default: () => {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
type: Array<any>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<el-menu
|
||||||
|
:default-active="layout === 'top' ? '-' : currRoute.path"
|
||||||
|
:collapse="!appStore.sidebar.opened"
|
||||||
|
:background-color="variables.menuBg"
|
||||||
|
:text-color="variables.menuText"
|
||||||
|
:active-text-color="variables.menuActiveText"
|
||||||
|
:unique-opened="false"
|
||||||
|
:collapse-transition="false"
|
||||||
|
:mode="layout === 'top' ? 'horizontal' : 'vertical'"
|
||||||
|
>
|
||||||
|
<sidebar-item
|
||||||
|
v-for="route in menuList"
|
||||||
|
:key="route.path"
|
||||||
|
:item="route"
|
||||||
|
:base-path="route.path"
|
||||||
|
:is-collapse="!appStore.sidebar.opened"
|
||||||
|
/>
|
||||||
|
</el-menu>
|
||||||
|
</template>
|
||||||
52
src/layout/components/Sidebar/TopMenu.vue
Normal file
52
src/layout/components/Sidebar/TopMenu.vue
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
|
import variables from "@/styles/variables.module.scss";
|
||||||
|
import { useAppStore } from "@/store/modules/app";
|
||||||
|
import { translateRouteTitleI18n } from "@/utils/i18n";
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const tPath = ref();
|
||||||
|
const selectMenu = (index: string) => {
|
||||||
|
appStore.changeTopActive(index);
|
||||||
|
};
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
|
const topMenu = ref<any[]>([]);
|
||||||
|
onMounted(() => {
|
||||||
|
topMenu.value = permissionStore.routes.filter((el) => !el.meta?.hidden);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<el-scrollbar>
|
||||||
|
<el-menu
|
||||||
|
mode="horizontal"
|
||||||
|
class="el-menu-demo"
|
||||||
|
:default-active="tPath"
|
||||||
|
:background-color="variables.menuBg"
|
||||||
|
:text-color="variables.menuText"
|
||||||
|
:active-text-color="variables.menuActiveText"
|
||||||
|
@select="selectMenu"
|
||||||
|
>
|
||||||
|
<el-menu-item
|
||||||
|
v-for="route in topMenu"
|
||||||
|
:key="route.path"
|
||||||
|
:index="route.path"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<svg-icon
|
||||||
|
v-if="route.meta && route.meta.icon"
|
||||||
|
:icon-class="route.meta.icon"
|
||||||
|
/>
|
||||||
|
<span v-if="route.meta && route.meta.title">{{
|
||||||
|
translateRouteTitleI18n(route.meta.title)
|
||||||
|
}}</span>
|
||||||
|
</template>
|
||||||
|
</el-menu-item>
|
||||||
|
<!-- <sidebar-item
|
||||||
|
v-for="route in topMenu"
|
||||||
|
:key="route.path"
|
||||||
|
:item="route"
|
||||||
|
:base-path="route.path"
|
||||||
|
:is-collapse="false"
|
||||||
|
/> -->
|
||||||
|
</el-menu>
|
||||||
|
</el-scrollbar>
|
||||||
|
</template>
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute } from "vue-router";
|
import TopMenu from "./TopMenu.vue";
|
||||||
import SidebarItem from "./SidebarItem.vue";
|
import LeftMenu from "./LeftMenu.vue";
|
||||||
import Logo from "./Logo.vue";
|
import Logo from "./Logo.vue";
|
||||||
|
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
import { usePermissionStore } from "@/store/modules/permission";
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
import { useAppStore } from "@/store/modules/app";
|
import { useAppStore } from "@/store/modules/app";
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import variables from "@/styles/variables.module.scss";
|
|
||||||
|
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const permissionStore = usePermissionStore();
|
const permissionStore = usePermissionStore();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const currRoute = useRoute();
|
|
||||||
const { sidebarLogo } = storeToRefs(settingsStore);
|
const { sidebarLogo } = storeToRefs(settingsStore);
|
||||||
const layout = computed(() => settingsStore.layout);
|
const layout = computed(() => settingsStore.layout);
|
||||||
const showContent = ref(true);
|
const showContent = ref(true);
|
||||||
@@ -28,41 +25,86 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="{ 'has-logo': sidebarLogo }" class="menu-wrap">
|
<div
|
||||||
|
:class="{ 'has-logo': sidebarLogo }"
|
||||||
|
class="menu-wrap"
|
||||||
|
v-if="layout !== 'mix'"
|
||||||
|
>
|
||||||
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
||||||
<el-scrollbar v-if="showContent">
|
<el-scrollbar v-if="showContent">
|
||||||
<el-menu
|
<LeftMenu :menu-list="permissionStore.routes" />
|
||||||
:default-active="layout === 'top' ? '-' : currRoute.path"
|
|
||||||
:collapse="!appStore.sidebar.opened"
|
|
||||||
:background-color="variables.menuBg"
|
|
||||||
:text-color="variables.menuText"
|
|
||||||
:active-text-color="variables.menuActiveText"
|
|
||||||
:unique-opened="false"
|
|
||||||
:collapse-transition="false"
|
|
||||||
:mode="layout === 'top' ? 'horizontal' : 'vertical'"
|
|
||||||
>
|
|
||||||
<sidebar-item
|
|
||||||
v-for="route in permissionStore.routes"
|
|
||||||
:key="route.path"
|
|
||||||
:item="route"
|
|
||||||
:base-path="route.path"
|
|
||||||
:is-collapse="!appStore.sidebar.opened"
|
|
||||||
/>
|
|
||||||
</el-menu>
|
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
<NavRight v-if="layout === 'top'" />
|
<NavRight v-if="layout === 'top'" />
|
||||||
</div>
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div :class="{ 'has-logo': sidebarLogo }" class="menu-wrap">
|
||||||
|
<div class="header">
|
||||||
|
<logo v-if="sidebarLogo" :collapse="!appStore.sidebar.opened" />
|
||||||
|
<TopMenu />
|
||||||
|
<NavRight />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.setting-container) {
|
:deep(.setting-container) {
|
||||||
.setting-item {
|
.setting-item {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
margin-right: 0px;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.isMix {
|
||||||
|
.menu-wrap {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 50px;
|
||||||
|
background-color: $menuBg;
|
||||||
|
|
||||||
|
:deep(.header) {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
// 顶部模式全局变量修改
|
||||||
|
--el-menu-item-height: 50px;
|
||||||
|
|
||||||
|
.logo-wrap {
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
background-color: $menuBg;
|
||||||
|
|
||||||
|
.el-menu-item {
|
||||||
|
color: $menuText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-scrollbar {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-menu {
|
||||||
|
display: inline-block;
|
||||||
|
width: 210px;
|
||||||
|
background-color: $menuBg;
|
||||||
|
|
||||||
|
:deep(.el-menu) {
|
||||||
|
background-color: $menuBg;
|
||||||
|
|
||||||
|
.el-menu-item {
|
||||||
|
color: $menuText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Main from "./main.vue";
|
||||||
import { computed, watchEffect } from "vue";
|
import { computed, watchEffect } from "vue";
|
||||||
import { useWindowSize } from "@vueuse/core";
|
import { useWindowSize } from "@vueuse/core";
|
||||||
import { AppMain, Navbar, Settings, TagsView } from "./components/index";
|
|
||||||
import Sidebar from "./components/Sidebar/index.vue";
|
import Sidebar from "./components/Sidebar/index.vue";
|
||||||
import RightPanel from "@/components/RightPanel/index.vue";
|
import LeftMenu from "./components/Sidebar/LeftMenu.vue";
|
||||||
|
|
||||||
import { useAppStore } from "@/store/modules/app";
|
import { useAppStore } from "@/store/modules/app";
|
||||||
import { useSettingsStore } from "@/store/modules/settings";
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
|
import { usePermissionStore } from "@/store/modules/permission";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
const permissionStore = usePermissionStore();
|
||||||
const { width } = useWindowSize();
|
const { width } = useWindowSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,9 +24,31 @@ const WIDTH = 992;
|
|||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
const activeTopMenu = computed(() => {
|
||||||
const showTagsView = computed(() => settingsStore.tagsView);
|
return appStore.activeTopMenu;
|
||||||
const showSettings = computed(() => settingsStore.showSettings);
|
});
|
||||||
|
// 混合模式左侧菜单
|
||||||
|
const mixLeftMenu = ref<any[]>([]);
|
||||||
|
const router = useRouter();
|
||||||
|
watch(
|
||||||
|
() => activeTopMenu.value,
|
||||||
|
(newVal) => {
|
||||||
|
permissionStore.routes.forEach((item) => {
|
||||||
|
if (item.path === newVal) {
|
||||||
|
mixLeftMenu.value = item.children || [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(" mixLeftMenu.value ", mixLeftMenu.value);
|
||||||
|
// if (mixLeftMenu.value.length) {
|
||||||
|
// router.push({
|
||||||
|
// path: mixLeftMenu.value[0].path,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
const layout = computed(() => settingsStore.layout);
|
const layout = computed(() => settingsStore.layout);
|
||||||
|
|
||||||
const classObj = computed(() => ({
|
const classObj = computed(() => ({
|
||||||
@@ -33,6 +57,7 @@ const classObj = computed(() => ({
|
|||||||
withoutAnimation: appStore.sidebar.withoutAnimation,
|
withoutAnimation: appStore.sidebar.withoutAnimation,
|
||||||
mobile: appStore.device === "mobile",
|
mobile: appStore.device === "mobile",
|
||||||
isTop: layout.value === "top",
|
isTop: layout.value === "top",
|
||||||
|
isMix: layout.value === "mix",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
@@ -66,21 +91,14 @@ function handleOutsideClick() {
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<Sidebar class="sidebar-container" />
|
<Sidebar class="sidebar-container" />
|
||||||
|
<template v-if="layout === 'mix'">
|
||||||
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
<div class="mix-wrap">
|
||||||
<div :class="{ 'fixed-header': fixedHeader }">
|
<!-- :menu-list="mixLeftMenu -->
|
||||||
<navbar v-if="layout !== 'top'" />
|
<LeftMenu :menu-list="permissionStore.routes" />
|
||||||
<tags-view v-if="showTagsView" />
|
<Main />
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
<!--主页面-->
|
<Main v-else />
|
||||||
<app-main />
|
|
||||||
|
|
||||||
<!-- 设置面板 -->
|
|
||||||
<RightPanel v-if="showSettings">
|
|
||||||
<settings />
|
|
||||||
</RightPanel>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -155,4 +173,26 @@ function handleOutsideClick() {
|
|||||||
// 顶部模式全局变量修改
|
// 顶部模式全局变量修改
|
||||||
--el-menu-item-height: 50px;
|
--el-menu-item-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.isMix {
|
||||||
|
:deep(.main-container) {
|
||||||
|
display: inline-block;
|
||||||
|
width: calc(100% - 210px);
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mix-wrap {
|
||||||
|
display: flex;
|
||||||
|
padding-top: 50px;
|
||||||
|
|
||||||
|
.el-menu {
|
||||||
|
width: 210px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
57
src/layout/main.vue
Normal file
57
src/layout/main.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, watchEffect } from "vue";
|
||||||
|
import { useWindowSize } from "@vueuse/core";
|
||||||
|
import { AppMain, Navbar, Settings, TagsView } from "./components/index";
|
||||||
|
import RightPanel from "@/components/RightPanel/index.vue";
|
||||||
|
|
||||||
|
import { useAppStore } from "@/store/modules/app";
|
||||||
|
import { useSettingsStore } from "@/store/modules/settings";
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应式布局容器固定宽度
|
||||||
|
*
|
||||||
|
* 大屏(>=1200px)
|
||||||
|
* 中屏(>=992px)
|
||||||
|
* 小屏(>=768px)
|
||||||
|
*/
|
||||||
|
const WIDTH = 992;
|
||||||
|
|
||||||
|
const appStore = useAppStore();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
|
||||||
|
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||||
|
const showTagsView = computed(() => settingsStore.tagsView);
|
||||||
|
const showSettings = computed(() => settingsStore.showSettings);
|
||||||
|
const layout = computed(() => settingsStore.layout);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (width.value < WIDTH) {
|
||||||
|
appStore.toggleDevice("mobile");
|
||||||
|
appStore.closeSideBar(true);
|
||||||
|
} else {
|
||||||
|
appStore.toggleDevice("desktop");
|
||||||
|
|
||||||
|
if (width.value >= 1200) {
|
||||||
|
//大屏
|
||||||
|
appStore.openSideBar(true);
|
||||||
|
} else {
|
||||||
|
appStore.closeSideBar(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div :class="{ hasTagsView: showTagsView }" class="main-container">
|
||||||
|
<div :class="{ 'fixed-header': fixedHeader }">
|
||||||
|
<navbar v-if="layout === 'left'" />
|
||||||
|
<tags-view v-if="showTagsView" />
|
||||||
|
</div>
|
||||||
|
<!--主页面-->
|
||||||
|
<app-main />
|
||||||
|
<!-- 设置面板 -->
|
||||||
|
<RightPanel v-if="showSettings">
|
||||||
|
<settings />
|
||||||
|
</RightPanel>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -14,11 +14,12 @@ export const useAppStore = defineStore("app", () => {
|
|||||||
const language = useStorage("language", defaultSettings.language);
|
const language = useStorage("language", defaultSettings.language);
|
||||||
|
|
||||||
const sidebarStatus = useStorage("sidebarStatus", "closed");
|
const sidebarStatus = useStorage("sidebarStatus", "closed");
|
||||||
|
|
||||||
const sidebar = reactive({
|
const sidebar = reactive({
|
||||||
opened: sidebarStatus.value !== "closed",
|
opened: sidebarStatus.value !== "closed",
|
||||||
withoutAnimation: false,
|
withoutAnimation: false,
|
||||||
});
|
});
|
||||||
|
const activeTopMenu = useStorage("activeTop", "");
|
||||||
/**
|
/**
|
||||||
* 根据语言标识读取对应的语言包
|
* 根据语言标识读取对应的语言包
|
||||||
*/
|
*/
|
||||||
@@ -68,18 +69,25 @@ export const useAppStore = defineStore("app", () => {
|
|||||||
function changeLanguage(val: string) {
|
function changeLanguage(val: string) {
|
||||||
language.value = val;
|
language.value = val;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 混合模式顶部切换
|
||||||
|
*/
|
||||||
|
function changeTopActive(val: string) {
|
||||||
|
activeTopMenu.value = val;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
device,
|
device,
|
||||||
sidebar,
|
sidebar,
|
||||||
language,
|
language,
|
||||||
locale,
|
locale,
|
||||||
size,
|
size,
|
||||||
|
activeTopMenu,
|
||||||
toggleDevice,
|
toggleDevice,
|
||||||
changeSize,
|
changeSize,
|
||||||
changeLanguage,
|
changeLanguage,
|
||||||
toggleSidebar,
|
toggleSidebar,
|
||||||
closeSideBar,
|
closeSideBar,
|
||||||
openSideBar,
|
openSideBar,
|
||||||
|
changeTopActive,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user