snowy/snowy-admin-web/src/layout/index.vue

486 lines
14 KiB
Vue

<template>
<!-- 经典布局 -->
<template v-if="layout === 'classical'">
<a-layout>
<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 ? 'aminui-side isCollapse' : 'aminui-side'">
<div class="adminui-side-scroll">
<a-menu
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
:theme="sideTheme"
mode="inline"
@select="onSelect"
@openChange="onOpenChange"
>
<NavMenu :nav-menus="menu"></NavMenu>
</a-menu>
</div>
</div>
</a-layout-sider>
<!-- 手机端情况下的左侧菜单 -->
<Side-m v-if="ismobile"></Side-m>
<!-- 右侧布局 -->
<a-layout>
<div id="snowyHeader" class="snowy-header">
<div class="snowy-header-left" style="padding-left: 0px">
<moduleMenu @switchModule="switchModule" />
<Topbar v-if="!ismobile && breadcrumbOpen" />
</div>
<div class="snowy-header-right">
<userbar />
</div>
</div>
<!-- 多标签 -->
<Tags v-if="!ismobile && layoutTagsOpen"></Tags>
<a-layout-content class="main-content-wrapper">
<div id="adminui-main" class="adminui-main">
<router-view v-slot="{ Component }">
<keep-alive :include="keepLiveRoute">
<component :is="Component" :key="$route.name" v-if="routeShow" />
</keep-alive>
</router-view>
<iframe-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>
</template>
<!-- 双排菜单布局 -->
<template v-else-if="layout === 'doublerow'">
<a-layout>
<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">
<a-menu-item
v-for="item in menu"
: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)"
>
<a
v-if="item.meta && item.meta.type === 'link'"
:href="item.path"
target="_blank"
@click.stop="() => {}"
></a>
<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"></NavMenu>
</a-menu>
</a-layout-sider>
<!-- 手机端情况下的左侧菜单 -->
<Side-m v-if="ismobile"></Side-m>
<a-layout>
<div id="snowyHeader" class="snowy-header">
<div class="snowy-header-left" style="padding-left: 0px">
<moduleMenu @switchModule="switchModule" />
<Topbar v-if="!ismobile && breadcrumbOpen" />
</div>
<div class="snowy-header-right">
<userbar />
</div>
</div>
<!-- 多标签 -->
<Tags v-if="!ismobile && layoutTagsOpen"></Tags>
<a-layout-content class="main-content-wrapper">
<div id="adminui-main" class="adminui-main">
<router-view v-slot="{ Component }">
<keep-alive :include="keepLiveRoute">
<component :is="Component" v-if="routeShow" :key="$route.name" />
</keep-alive>
</router-view>
<iframe-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>
</template>
<!-- 退出最大化 -->
<div class="main-maximize-exit" @click="exitMaximize">
<fullscreen-exit-outlined style="color: #fff" />
</div>
</template>
<script>
import SideM from './components/sideM.vue'
import Topbar from './components/topbar.vue'
import Tags from './components/tags.vue'
import NavMenu from './components/NavMenu.vue'
import userbar from './components/userbar.vue'
import iframeView from './components/iframeView.vue'
import moduleMenu from './components/moduleMenu.vue'
import { ThemeModeEnum } from '@/utils/enum'
import { globalStore, keepAliveStore } from '@/store'
import { mapState, mapActions } from 'pinia'
export default defineComponent({
name: 'Index',
components: {
SideM,
Topbar,
Tags,
NavMenu,
userbar,
moduleMenu,
iframeView
},
data() {
return {
menu: [],
moduleMenu: [],
nextMenu: [],
pmenu: {},
doublerowSelectedKey: [],
layoutSiderDowbleMenu: true,
onSelectTag: false,
selectedKeys: [],
openKeys: [],
openKeysOther: []
}
},
computed: {
...mapState(globalStore, [
'theme',
'ismobile',
'layout',
'layoutTagsOpen',
'menuIsCollapse',
'breadcrumbOpen',
'topHanderThemeColorOpen',
'topHanderThemeColor',
'sideUniqueOpen',
'sysBaseConfig'
]),
...mapState(keepAliveStore, ['keepLiveRoute', 'routeShow']),
sideTheme() {
const theme = this.theme
return theme === ThemeModeEnum.REAL_DARK ? ThemeModeEnum.DARK : theme
},
secondMenuSideTheme() {
const theme = this.theme
return theme === ThemeModeEnum.REAL_DARK ? ThemeModeEnum.DARK : ThemeModeEnum.LIGHT
}
},
watch: {
$route() {
this.showThis()
},
layout: {
handler(val) {
document.body.setAttribute('data-layout', val)
if (val.includes('doublerow')) {
this.setDoublerowSelectedKey()
}
this.$nextTick(() => {
// 顶栏主题色
this.switchoverTopHanderThemeColor()
})
},
immediate: true
},
topHanderThemeColorOpen() {
this.switchoverTopHanderThemeColor()
},
topHanderThemeColorSpread() {
this.switchoverTopHanderThemeColor()
}
},
created() {
// 判断浏览器宽度,顺手加入缓存
this.onLayoutResize()
window.addEventListener('resize', this.onLayoutResize)
this.moduleMenu = this.$router.getMenu()
// 获取缓存中的菜单模块是哪个
const menuModuleId = this.$TOOL.data.get('SNOWY_MENU_MODULE_ID')
let menu = []
if (menuModuleId) {
// 防止切换一个无此应用的人
const module = this.$router.getMenu().filter((item) => item.id === menuModuleId)
if (module.length > 0) {
menu = module[0].children
} else {
menu = this.$router.getMenu()[0].children
}
} else {
menu = this.$router.getMenu()[0].children
}
// 此菜单为正常模块下的菜单
this.menu = this.filterUrl(menu)
this.showThis()
},
mounted() {
this.switchoverTopHanderThemeColor()
},
methods: {
...mapActions(globalStore, ['setTheme', 'setIsmobile', 'setLayout', 'setMenuIsCollapse']),
// 切换应用
switchModule(id) {
const menu = this.moduleMenu
if (menu.length > 0) {
const menus = menu.filter((item) => item.id === id)[0].children
if (menus.length > 0) {
// 将此模块的唯一值加入缓存
this.$TOOL.data.set('SNOWY_MENU_MODULE_ID', id)
// 正儿八百的菜单
this.menu = this.filterUrl(menus)
// 然后将其跳转至指定界面,默认始终取排序第一的
const path = this.traverseChild(this.menu)
this.$router.push({ path })
} else {
this.$message.warning('该模块下无任何菜单')
}
}
},
// 遍历子集获取一个path
traverseChild(menu) {
if (menu[0].children !== undefined) {
if (menu[0].children.length > 0) {
return this.traverseChild(menu[0].children)
} else {
return menu[0].path
}
} else {
return menu[0].path
}
},
// 当菜单被选中时
onSelect(obj) {
this.onSelectTag = true
const pathLength = obj.keyPath.length
const path = obj.keyPath[pathLength - 1]
this.$router.push({ path })
// 设置选中
this.selectedKeys = obj.selectedKeys
},
onLayoutResize() {
const clientWidth = document.body.clientWidth
this.setIsmobile(clientWidth < 992)
},
// 路由监听高亮
showThis() {
this.pmenu = this.$route.meta.breadcrumb ? this.$route.meta.breadcrumb[0] : {}
const nextTickMenu = this.filterUrl(this.pmenu.children)
this.$nextTick(() => {
let routeMenu = this.filterUrl(this.pmenu.children)
const active = this.$route.meta.active || this.$route.fullPath
const parentPathArray = this.getParentKeys(routeMenu, active)
if (parentPathArray) {
const parentPath = parentPathArray[parentPathArray.length - 1]
// 这一串操作下来只为取到最上面的路由的孩子们,最后成为双排菜单的第二排
const nextMenuTemp = nextTickMenu.filter((item) => item.path === parentPath)[0].children
if (nextMenuTemp) {
this.nextMenu = nextTickMenu.filter((item) => item.path === parentPath)[0].children
}
}
this.selectedKeys = new Array(active)
if (!this.onSelectTag) {
const pidKey = this.getParentKeys(this.menu, active)
this.openKeys = pidKey
} else if (this.sideUniqueOpen) {
const pidKey = this.getParentKeys(this.menu, active)
this.openKeys = pidKey
}
// 双排菜单下
if (this.layout === 'doublerow') {
this.setDoublerowSelectedKey()
}
})
},
// 双排菜单下点击显示右侧分栏
showMenu(route) {
this.pmenu = route
if (this.pmenu.children) {
this.nextMenu = this.filterUrl(this.pmenu.children)
}
if (!route.children || route.children.length === 0) {
this.layoutSiderDowbleMenu = false
this.$router.push({ path: route.path })
} else {
this.layoutSiderDowbleMenu = true
}
if (this.layout === 'doublerow') {
this.doublerowSelectedKey = [route.path]
}
},
// 设置双排菜单下的首列默认选中
setDoublerowSelectedKey() {
const pidKey = this.getParentKeys(this.menu, this.selectedKeys.toString())
this.$nextTick(() => {
const pidKeyArray = []
for (const key in pidKey) {
pidKeyArray.push(key)
}
if (pidKeyArray.length > 1) {
this.layoutSiderDowbleMenu = true
} else {
this.layoutSiderDowbleMenu = false
}
})
// 设置第一排选中的
this.menu.forEach((item) => {
if (pidKey !== undefined) {
if (pidKey[pidKey.length - 1].toString() === item.path) {
this.doublerowSelectedKey = [item.path]
}
}
})
},
// 菜单展开/关闭的回调
onOpenChange(keys) {
if (this.sideUniqueOpen) {
// 获取最新的
const openKey = keys[keys.length - 1]
if (keys.length > 1) {
// 获取上级
const pidKey = this.getParentKeys(this.menu, openKey)
this.openKeys = pidKey
} else {
this.openKeys = Array.of(openKey) // new Array(openKey);
}
} else {
this.openKeys = keys
}
},
// 获取上级keys
getParentKeys(data, val) {
// 递归父级key
for (const element of data) {
if (element.path === val) {
return [element.path]
}
if (element.children) {
const far = this.getParentKeys(element.children, val)
if (far) {
return far.concat(element.path)
}
}
}
},
// 转换外部链接的路由
filterUrl(map) {
const newMap = []
// eslint-disable-next-line no-unused-expressions
map &&
map.forEach((item) => {
item.meta = item.meta ? item.meta : {}
// 处理隐藏
if (item.meta.hidden) {
return false
}
// 处理http
if (item.meta.type === 'iframe') {
item.path = `/i/${item.name}`
}
// 递归循环
if (item.children && item.children.length > 0) {
item.children = this.filterUrl(item.children)
}
newMap.push(item)
})
return newMap
},
// 退出最大化
exitMaximize() {
document.getElementById('app').classList.remove('main-maximize')
},
// 切换顶栏颜色
switchoverTopHanderThemeColor() {
// 界面顶栏设置颜色
const header = document.getElementById('snowyHeader')
this.topHanderThemeColorOpen
? header.classList.add('snowy-header-primary-color')
: header.classList.remove('snowy-header-primary-color')
// 判断是否开启了通栏
const headerLogin = document.getElementById('snowyHeaderLogo')
try {
this.topHanderThemeColorSpread
? headerLogin.classList.add('snowy-header-logo-primary-color')
: headerLogin.classList.remove('snowy-header-logo-primary-color')
} catch (e) {}
// 如果是双排菜单,吧第二排的也给渲染了
if (this.layout === 'doublerow') {
const snowyDoublerowSideTop = document.getElementById('snowyDoublerowSideTop')
try {
this.topHanderThemeColorSpread
? snowyDoublerowSideTop.classList.add('snowy-doublerow-side-top-primary-color')
: snowyDoublerowSideTop.classList.remove('snowy-doublerow-side-top-primary-color')
} catch (e) {}
}
}
}
})
</script>