123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- <template>
- <!-- 经典布局 -->
- <a-layout v-if="layout === 'classical'">
- <a-layout-sider
- v-if="!isMobile"
- v-model:collapsed="menuIsCollapse"
- :trigger="null"
- collapsible
- :theme="sideTheme"
- width="210"
- >
- <header id="snowyHeaderLogo" class="snowy-header-logo">
- <div class="snowy-header-left">
- <div class="logo-bar">
- <img class="logo" :src="sysBaseConfig.SNOWY_SYS_LOGO" />
- <span>{{ sysBaseConfig.SNOWY_SYS_NAME }}</span>
- </div>
- </div>
- </header>
- <div :class="menuIsCollapse ? 'admin-ui-side isCollapse' : 'admin-ui-side'">
- <div class="admin-ui-side-scroll">
- <a-menu
- v-model:openKeys="openKeys"
- v-model:selectedKeys="selectedKeys"
- :theme="sideTheme"
- mode="inline"
- @select="onSelect"
- @openChange="onOpenChange"
- >
- <NavMenu :nav-menus="menu" />
- </a-menu>
- </div>
- </div>
- </a-layout-sider>
- <!-- 手机端情况下的左侧菜单 -->
- <Side-m v-if="isMobile" />
- <!-- 右侧布局 -->
- <a-layout>
- <div id="snowyHeader" class="snowy-header">
- <div class="snowy-header-left" style="padding-left: 0px">
- <div v-if="!isMobile" class="panel-item hidden-sm-and-down" @click="menuIsCollapseClick">
- <MenuUnfoldOutlined v-if="menuIsCollapse" />
- <MenuFoldOutlined v-else />
- </div>
- <moduleMenu v-if="moduleMenuShow" @switchModule="switchModule" />
- <top-bar v-if="!isMobile && breadcrumbOpen" />
- </div>
- <div class="snowy-header-right">
- <user-bar />
- </div>
- </div>
- <!-- 多标签 -->
- <Tags v-if="!isMobile && layoutTagsOpen" />
- <a-layout-content class="main-content-wrapper">
- <div id="admin-ui-main" class="admin-ui-main">
- <router-view v-slot="{ Component }">
- <keep-alive :include="kStore.keepLiveRoute">
- <component :is="Component" v-if="kStore.routeShow" :key="route.name" />
- </keep-alive>
- </router-view>
- <iframe-view />
- <div class="main-bottom-wrapper">
- <a style="color: #a0a0a0" :href="sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL" target="_blank">{{
- sysBaseConfig.SNOWY_SYS_COPYRIGHT
- }}</a>
- </div>
- </div>
- </a-layout-content>
- </a-layout>
- </a-layout>
- <!-- 双排菜单布局 -->
- <a-layout v-else-if="layout === 'doublerow'">
- <a-layout-sider v-if="!isMobile" width="80" :theme="sideTheme" :trigger="null" collapsible>
- <header id="snowyHeaderLogo" class="snowy-header-logo">
- <div class="snowy-header-left">
- <div class="logo-bar">
- <router-link to="/">
- <img class="logo" :title="sysBaseConfig.SNOWY_SYS_NAME" :src="sysBaseConfig.SNOWY_SYS_LOGO" />
- </router-link>
- </div>
- </div>
- </header>
- <a-menu
- v-model:selectedKeys="doublerowSelectedKey"
- :theme="sideTheme"
- class="snowy-doublerow-layout-menu"
- v-for="item in menu"
- :key="item.path"
- >
- <a-menu-item
- :key="item.path"
- style="
- text-align: center;
- border-radius: 2px;
- height: auto;
- line-height: 20px;
- flex: none;
- display: block;
- padding: 12px 0 !important;
- "
- @click="showMenu(item)"
- v-if="!item.meta.hidden"
- >
- <a v-if="item.meta && item.meta.type === 'link'" :href="item.path" target="_blank" @click.stop="() => {}" />
- <template #icon>
- <component :is="item.meta.icon" style="padding-left: 10px" />
- </template>
- <div class="snowy-doublerow-layout-menu-item-fort-div">
- <span class="snowy-doublerow-layout-menu-item-fort-div-span">
- {{ item.meta.title }}
- </span>
- </div>
- </a-menu-item>
- </a-menu>
- </a-layout-sider>
- <a-layout-sider
- v-if="!isMobile"
- v-show="layoutSiderDowbleMenu"
- v-model:collapsed="menuIsCollapse"
- :trigger="null"
- width="170"
- collapsible
- :theme="secondMenuSideTheme"
- >
- <div v-if="!menuIsCollapse" id="snowyDoublerowSideTop" class="snowy-doublerow-side-top">
- <h2 class="snowy-title">{{ pMenu.meta.title }}</h2>
- </div>
- <a-menu
- v-model:collapsed="menuIsCollapse"
- v-model:openKeys="openKeys"
- v-model:selectedKeys="selectedKeys"
- mode="inline"
- :theme="secondMenuSideTheme"
- @select="onSelect"
- >
- <NavMenu :nav-menus="nextMenu" />
- </a-menu>
- </a-layout-sider>
- <!-- 手机端情况下的左侧菜单 -->
- <Side-m v-if="isMobile" />
- <a-layout>
- <div id="snowyHeader" class="snowy-header">
- <div class="snowy-header-left" style="padding-left: 0px">
- <moduleMenu v-if="moduleMenuShow" @switchModule="switchModule" />
- <top-bar v-if="!isMobile && breadcrumbOpen" />
- </div>
- <div class="snowy-header-right">
- <user-bar />
- </div>
- </div>
- <!-- 多标签 -->
- <Tags v-if="!isMobile && layoutTagsOpen" />
- <a-layout-content class="main-content-wrapper">
- <div id="admin-ui-main" class="admin-ui-main">
- <router-view v-slot="{ Component }">
- <keep-alive :include="kStore.keepLiveRoute">
- <component :is="Component" v-if="kStore.routeShow" :key="route.name" />
- </keep-alive>
- </router-view>
- <iframe-view />
- <div class="main-bottom-wrapper">
- <a style="color: #a0a0a0" :href="sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL" target="_blank">{{
- sysBaseConfig.SNOWY_SYS_COPYRIGHT
- }}</a>
- </div>
- </div>
- </a-layout-content>
- </a-layout>
- </a-layout>
- <!-- 双列菜单布局 -->
- <div class="doublecol" v-else-if="layout === 'doublecol'">
- <div id="snowyHeader" class="doublecol-header" style="height: 100px">
- <div class="snowy-header-left" style="padding-left: 0px; margin-left: 10px">
- <div
- class="logo"
- style="
- background: url('/img/SYS_LOGO.png') no-repeat center;
- background-size: cover;
- width: 324px;
- height: 39px;
- "
- ></div>
- <top-bar v-if="!isMobile && breadcrumbOpen" />
- </div>
- <div class="snowy-header-right">
- <ColModuleMenu v-if="moduleMenuShow" @switchModule="switchModule" />
- <ColUserBar />
- </div>
- </div>
- <a-layout id="admin-ui-main" class="admin-ui-main" style="padding: 11px 0 0 0; height: calc(100% - 100px)">
- <a-layout-sider v-if="!isMobile" width="80" :theme="sideTheme" :trigger="null" collapsible>
- <a-menu
- v-model:selectedKeys="doublerowSelectedKey"
- :theme="sideTheme"
- class="snowy-doublerow-layout-menu"
- v-for="item in menu"
- :key="item.path"
- >
- <a-menu-item
- :key="item.path"
- style="
- text-align: center;
- border-radius: 2px;
- height: auto;
- line-height: 20px;
- flex: none;
- display: block;
- padding: 12px 0 !important;
- "
- @click="showMenu(item)"
- v-if="!item.meta.hidden"
- >
- <a v-if="item.meta && item.meta.type === 'link'" :href="item.path" target="_blank" @click.stop="() => {}" />
- <template #icon>
- <component :is="item.meta.icon" style="padding-left: 10px" />
- </template>
- <div class="snowy-doublerow-layout-menu-item-fort-div">
- <span class="snowy-doublerow-layout-menu-item-fort-div-span">
- {{ item.meta.title }}
- </span>
- </div>
- </a-menu-item>
- </a-menu>
- </a-layout-sider>
- <a-layout-sider
- v-if="!isMobile"
- v-show="layoutSiderDowbleMenu"
- v-model:collapsed="menuIsCollapse"
- :trigger="null"
- width="170"
- collapsible
- :theme="secondMenuSideTheme"
- >
- <div v-if="!menuIsCollapse" id="snowyDoublerowSideTop" class="snowy-doublerow-side-top">
- <h2 class="snowy-title">{{ pMenu.meta.title }}</h2>
- </div>
- <a-menu
- v-model:collapsed="menuIsCollapse"
- v-model:openKeys="openKeys"
- v-model:selectedKeys="selectedKeys"
- mode="inline"
- :theme="secondMenuSideTheme"
- @select="onSelect"
- >
- <NavMenu :nav-menus="nextMenu" />
- </a-menu>
- </a-layout-sider>
- <!-- 手机端情况下的左侧菜单 -->
- <Side-m v-if="isMobile" />
- <a-layout-content style="margin: 0 10px; overflow: hidden auto">
- <!-- 多标签 -->
- <Tags v-if="!isMobile && layoutTagsOpen" />
- <router-view v-slot="{ Component }">
- <keep-alive :include="kStore.keepLiveRoute">
- <component :is="Component" v-if="kStore.routeShow" :key="route.name" />
- </keep-alive>
- </router-view>
- <iframe-view />
- <div class="main-bottom-wrapper">
- <a style="color: #a0a0a0" :href="sysBaseConfig.SNOWY_SYS_COPYRIGHT_URL" target="_blank">{{
- sysBaseConfig.SNOWY_SYS_COPYRIGHT
- }}</a>
- </div>
- </a-layout-content>
- </a-layout>
- </div>
- <!-- 退出最大化 -->
- <div class="main-maximize-exit" @click="exitMaximize">
- <fullscreen-exit-outlined style="color: #fff" />
- </div>
- </template>
- <script setup>
- import ColUserBar from '@/layout/components/doublecol_userbar.vue'
- import UserBar from '@/layout/components/userbar.vue'
- import Tags from '@/layout/components/tags.vue'
- import SideM from '@/layout/components/sideM.vue'
- import NavMenu from '@/layout/components/NavMenu.vue'
- import ModuleMenu from '@/layout/components/moduleMenu.vue'
- import ColModuleMenu from '@/layout/components/doublecol_moduleMenu.vue'
- import IframeView from '@/layout/components/iframeView.vue'
- import TopBar from '@/layout/components/topbar.vue'
- import { globalStore, keepAliveStore } from '@/store'
- import { ThemeModeEnum } from '@/utils/enum'
- import { useRoute, useRouter } from 'vue-router'
- import tool from '@/utils/tool'
- import { message } from 'ant-design-vue'
- const store = globalStore()
- const kStore = keepAliveStore()
- const route = useRoute()
- const router = useRouter()
- const menu = ref([])
- const pMenu = ref({})
- const nextMenu = ref([])
- const selectedKeys = ref([])
- const openKeys = ref([])
- const onSelectTag = ref(false)
- const moduleMenuData = ref([])
- const moduleMenuShow = ref(true)
- const doublerowSelectedKey = ref([])
- const layoutSiderDowbleMenu = ref(false)
- // computed计算方法 - start
- const layout = computed(() => {
- return store.layout
- })
- const isMobile = computed(() => {
- return store.isMobile
- })
- const menuIsCollapse = computed(() => {
- return store.menuIsCollapse
- })
- const theme = computed(() => {
- return store.theme
- })
- const layoutTagsOpen = computed(() => {
- // 当关闭多标签时,清理keepAlive的缓存
- if (!store.layoutTagsOpen) {
- kStore.keepLiveRoute = []
- }
- return store.layoutTagsOpen
- })
- const breadcrumbOpen = computed(() => {
- return store.breadcrumbOpen
- })
- const topHeaderThemeColorOpen = computed(() => {
- return store.topHeaderThemeColorOpen
- })
- const topHeaderThemeColorSpread = computed(() => {
- return store.topHeaderThemeColorSpread
- })
- const sideUniqueOpen = computed(() => {
- return store.sideUniqueOpen
- })
- const sysBaseConfig = computed(() => {
- return store.sysBaseConfig
- })
- const module = computed(() => {
- return store.module
- })
- const sideTheme = computed(() => {
- return theme.value === ThemeModeEnum.REAL_DARK ? ThemeModeEnum.DARK : theme.value
- })
- const secondMenuSideTheme = computed(() => {
- return theme.value === ThemeModeEnum.REAL_DARK ? ThemeModeEnum.DARK : ThemeModeEnum.LIGHT
- })
- // 路由监听高亮
- const showThis = () => {
- pMenu.value = route.meta.breadcrumb ? route.meta.breadcrumb[0] : {}
- // 展开的
- nextTick(() => {
- // 取得默认路由地址并设置展开
- let active = route.meta.active || route.path
- // 如果是目录,必须往下找
- if (route.meta.type === 'catalog') {
- active = traverseChild(pMenu.value.children, active).path
- }
- selectedKeys.value = new Array(active)
- const pidKey = getParentKeys(pMenu.value.children, active)
- // 判断是隐藏的路由,找其上级
- if (route.meta.hidden && pidKey) {
- if (pidKey.length > 1) {
- selectedKeys.value = new Array(pidKey[1])
- }
- }
- const nextTickMenu = pMenu.value.children
- if (pidKey) {
- const modelPidKey = getParentKeys(moduleMenuData.value, route.path)
- moduleMenuData.value.forEach((item) => {
- if (modelPidKey.includes(item.path)) {
- tagSwitchModule(item.id)
- }
- })
- const parentPath = pidKey[pidKey.length - 1]
- if (layout.value === 'doublerow') {
- // 这一串操作下来只为取到最上面的路由的孩子们,最后成为双排菜单的第二排
- const nextMenuTemp = nextTickMenu.filter((item) => item.path === parentPath)[0].children
- if (nextMenuTemp) {
- nextMenu.value = nextTickMenu.filter((item) => item.path === parentPath)[0].children
- }
- }
- }
- if (!onSelectTag.value || sideUniqueOpen.value) {
- openKeys.value = pidKey
- }
- // 双排菜单下
- if (layout.value === 'doublerow') {
- setDoubleRowSelectedKey()
- }
- })
- }
- // 执行-start
- moduleMenuData.value = router.getMenu()
- // 获取缓存中的菜单模块是哪个
- const menuModuleId = tool.data.get('SNOWY_MENU_MODULE_ID')
- if (menuModuleId) {
- // 防止切换一个无此应用的人
- const module = router.getMenu().filter((item) => item.id === menuModuleId)
- if (module.length > 0) {
- menu.value = module[0].children
- } else {
- menu.value = router.getMenu()[0].children
- }
- } else {
- menu.value = router.getMenu()[0].children
- }
- showThis()
- onMounted(() => {
- onLayoutResize()
- window.addEventListener('resize', onLayoutResize)
- switchoverTopHeaderThemeColor()
- })
- watch(route, (newValue) => {
- // 清理选中的
- selectedKeys.value = []
- showThis()
- })
- // 监听是否开启了顶栏颜色
- watch(layout, (newValue) => {
- document.body.setAttribute('data-layout', newValue)
- if (newValue.includes('doublerow')) {
- showThis()
- setDoubleRowSelectedKey()
- }
- nextTick(() => {
- // 顶栏主题色
- switchoverTopHeaderThemeColor()
- })
- })
- watch(topHeaderThemeColorOpen, (newValue) => {
- switchoverTopHeaderThemeColor()
- })
- watch(topHeaderThemeColorSpread, (newValue) => {
- switchoverTopHeaderThemeColor()
- })
- const menuIsCollapseClick = () => {
- store.toggleConfig('menuIsCollapse')
- }
- // 切换顶栏颜色
- const switchoverTopHeaderThemeColor = () => {
- // 界面顶栏设置颜色
- const header = document.getElementById('snowyHeader')
- topHeaderThemeColorOpen.value
- ? header.classList.add('snowy-header-primary-color')
- : header.classList.remove('snowy-header-primary-color')
- // 判断是否开启了通栏
- const headerLogin = document.getElementById('snowyHeaderLogo')
- try {
- topHeaderThemeColorSpread.value
- ? headerLogin.classList.add('snowy-header-logo-primary-color')
- : headerLogin.classList.remove('snowy-header-logo-primary-color')
- // eslint-disable-next-line no-empty
- } catch (e) {}
- // 如果是双排菜单,吧第二排的也给渲染了
- if (layout.value === 'doublerow') {
- const snowyDoublerowSideTop = document.getElementById('snowyDoublerowSideTop')
- try {
- topHeaderThemeColorSpread.value
- ? snowyDoublerowSideTop.classList.add('snowy-doublerow-side-top-primary-color')
- : snowyDoublerowSideTop.classList.remove('snowy-doublerow-side-top-primary-color')
- // eslint-disable-next-line no-empty
- } catch (e) {}
- }
- }
- // 设置双排菜单下的首列默认选中
- const setDoubleRowSelectedKey = () => {
- const pidKey = getParentKeys(menu.value, selectedKeys.value.toString())
- nextTick(() => {
- const pidKeyArray = []
- for (const key in pidKey) {
- pidKeyArray.push(key)
- }
- layoutSiderDowbleMenu.value = pidKeyArray.length > 1
- })
- // 设置第一排选中的
- menu.value.forEach((item) => {
- if (pidKey !== undefined) {
- if (pidKey[pidKey.length - 1].toString() === item.path) {
- doublerowSelectedKey.value = [item.path]
- }
- }
- })
- }
- // 菜单展开/关闭的回调
- const onOpenChange = (keys) => {
- if (sideUniqueOpen.value) {
- // 获取最新的
- const openKey = keys[keys.length - 1]
- if (keys.length > 1) {
- // 获取上级
- openKeys.value = getParentKeys(menu.value, openKey)
- } else {
- openKeys.value = Array.of(openKey) // new Array(openKey);
- }
- } else {
- openKeys.value = keys
- }
- }
- // 获取上级keys
- const getParentKeys = (data, val) => {
- const traverse = (array, val) => {
- // 递归父级key
- for (const element of array) {
- if (element.path === val) {
- return [element.path]
- }
- if (element.children) {
- const far = traverse(element.children, val)
- if (far) {
- return far.concat(element.path)
- }
- }
- }
- }
- return traverse(data, val)
- }
- // 双排菜单下点击显示右侧分栏
- const showMenu = (route) => {
- pMenu.value = route
- if (pMenu.value.children) {
- nextMenu.value = pMenu.value.children
- }
- if (!route.children || route.children.length === 0) {
- layoutSiderDowbleMenu.value = false
- router.push({ path: route.path })
- } else {
- if (route.children) {
- let hidden = 0
- route.children.forEach((item) => {
- if (item.meta.hidden && item.meta.hidden === true) {
- hidden++
- }
- })
- // 如果全部都隐藏了,就跳转这个,不展开另一排
- if (hidden === route.children.length) {
- layoutSiderDowbleMenu.value = false
- router.push({ path: route.path })
- } else {
- layoutSiderDowbleMenu.value = true
- }
- } else {
- layoutSiderDowbleMenu.value = false
- }
- }
- if (layout.value === 'doublerow') {
- doublerowSelectedKey.value = [route.path]
- }
- }
- // 当菜单被选中时
- const onSelect = (obj) => {
- onSelectTag.value = true
- const pathLength = obj.keyPath.length
- const path = obj.keyPath[pathLength - 1]
- router.push({ path })
- // 设置选中
- selectedKeys.value = obj.selectedKeys
- }
- const onLayoutResize = () => {
- const clientWidth = document.body.clientWidth
- store.setIsMobile(clientWidth < 992)
- }
- // 切换应用
- const switchModule = (id) => {
- if (moduleMenuData.value.length > 0) {
- showThis()
- const menus = moduleMenuData.value.filter((item) => item.id === id)[0].children
- if (menus.length > 0) {
- // 正儿八百的菜单
- menu.value = menus
- const firstMenu = traverseChild(menu.value)
- const path = firstMenu.path
- // 如果是外链
- if (firstMenu.menuType === 'LINK') {
- window.open(path)
- } else {
- // 将此模块的唯一值加入缓存
- tool.data.set('SNOWY_MENU_MODULE_ID', id)
- // 然后将其跳转至指定界面,默认始终取排序第一的
- router.push({ path })
- }
- } else {
- message.warning('该模块下无任何菜单')
- }
- }
- }
- // 通过标签切换应用
- const tagSwitchModule = (id) => {
- // 将此模块的唯一值加入缓存
- tool.data.set('SNOWY_MENU_MODULE_ID', id)
- store.setModule(id)
- // 正儿八百的菜单
- menu.value = moduleMenuData.value.filter((item) => item.id === id)[0].children
- }
- // 遍历获取子集
- const traverseChild = (menu) => {
- if (menu[0] && menu[0].children !== undefined) {
- if (menu[0].children.length > 0) {
- if (menu[0].children[0] && menu[0].children[0].meta.hidden && menu[0].children[0].meta.hidden === true) {
- return menu[0]
- } else {
- return traverseChild(menu[0].children)
- }
- }
- } else {
- return menu[0]
- }
- }
- // 退出最大化
- const exitMaximize = () => {
- document.getElementById('app').classList.remove('main-maximize')
- moduleMenuShow.value = false
- nextTick(() => {
- moduleMenuShow.value = true
- })
- }
- </script>
- <style lang="less">
- .doublecol {
- height: 100%;
- .ant-menu-item {
- &.ant-menu-item-selected {
- background: #3870d0;
- color: #fff;
- a {
- color: #fff;
- }
- }
- }
- }
- </style>
|