【更新】重写layout布局,为更多布局扩展更方便,顺手解决标签点击不切换应用的问题

pull/141/MERGE
小诺 2023-09-25 00:49:28 +08:00 committed by 俞宝山
parent e6f4ee7f4d
commit d00dd22a29
15 changed files with 789 additions and 553 deletions

View File

@ -28,7 +28,7 @@
<strong>We're sorry but Snowy2.0 doesn't work properly without JavaScript
enabled. Please enable it to continue.</strong>
</noscript>
<div id="app">
<div id="app" class="admin-ui">
<div class="app-loading">
<div class="app-loading__logo">
<img src="/img/logo.png"/>

View File

@ -46,10 +46,10 @@ const DEFAULT_CONFIG = {
SNOWY_BREADCRUMD_OPEN: false,
// 顶栏是否应用主题色
SNOWY_TOP_HANDER_THEME_COLOR_OPEN: false,
SNOWY_TOP_HEADER_THEME_COLOR_OPEN: false,
// 顶栏主题色通栏
SNOWY_TOP_HANDER_THEME_COLOR_SPREAD: false,
SNOWY_TOP_HEADER_THEME_COLOR_SPREAD: false,
// 侧边菜单是否排他展开
SNOWY_SIDE_UNIQUE_OPEN: true,

View File

@ -20,7 +20,7 @@
},
computed: {
...mapState(iframeStore, ['iframeList']),
...mapState(globalStore, ['ismobile', 'layoutTags'])
...mapState(globalStore, ['isMobile', 'layoutTags'])
},
watch: {
$route(e) {
@ -35,12 +35,12 @@
...mapActions(iframeStore, ['setIframeList', 'pushIframeList', 'clearIframeList']),
push(route) {
if (route.meta.type === 'iframe') {
if (this.ismobile || !this.layoutTags) {
if (this.isMobile || !this.layoutTags) {
this.setIframeList(route)
} else {
this.pushIframeList(route)
}
} else if (this.ismobile || !this.layoutTags) {
} else if (this.isMobile || !this.layoutTags) {
this.clearIframeList()
}
}

View File

@ -20,13 +20,7 @@
<a-button type="primary" @click="leaveFor('/usercenter')"></a-button>
</a-space>
</a-drawer>
<xn-form-container
title="详情"
:width="700"
:visible="visible"
:destroy-on-close="true"
@close="onClose"
>
<xn-form-container title="详情" :width="700" :visible="visible" :destroy-on-close="true" @close="onClose">
<a-form ref="formRef" :model="formData" layout="vertical">
<a-form-item label="主题:" name="subject">
<span>{{ formData.subject }}</span>
@ -84,7 +78,10 @@
let clientId = tool.data.get('CLIENTID') ? tool.data.get('CLIENTID') : ''
let url = sysConfig.API_URL + '/dev/message/createSseConnect?clientId=' + clientId
// heartbeatTimeout: 30s
let source = new EventSourcePolyfill(url, { headers: { token: tool.data.get('TOKEN') }, heartbeatTimeout: 30000 })
let source = new EventSourcePolyfill(url, {
headers: { token: tool.data.get('TOKEN') },
heartbeatTimeout: 300000
})
//
source.addEventListener('open', (e) => {})
//

View File

@ -5,7 +5,7 @@
mode="horizontal"
v-if="menu && menu.length > 1"
class="module-menu"
id="moduleMunu"
id="moduleMenu"
>
<a-menu-item
v-for="item in menu"
@ -21,7 +21,7 @@
</a-menu-item>
</a-menu>
</div>
<div v-else class="panel-item hidden-sm-and-down">
<div v-else>
<a-popover v-if="menu.length > 1" placement="bottomLeft">
<template #content>
<a-row :gutter="[0, 5]" class="module-row">
@ -35,7 +35,9 @@
</div>
</a-row>
</template>
<appstore-outlined />
<div class="panel-item hidden-sm-and-down module-card-scope">
<appstore-outlined />
</div>
</a-popover>
</div>
</template>
@ -49,24 +51,30 @@
const store = globalStore()
const { moduleUnfoldOpen, topHanderThemeColorOpen } = storeToRefs(store)
const moduleBackColor = ref(topHanderThemeColorOpen)
const { moduleUnfoldOpen, topHeaderThemeColorOpen } = storeToRefs(store)
const moduleBackColor = ref(topHeaderThemeColorOpen)
const module = computed(() => {
return store.module
})
//
watch(moduleUnfoldOpen, (newValue) => {
nextTick(() => {
setModuleBackColor()
})
})
watch(module, (newValue) => {
selectedKeys.value = [newValue]
setSelectedKeys()
})
//
watch(topHanderThemeColorOpen, (newValue) => {
watch(topHeaderThemeColorOpen, (newValue) => {
moduleBackColor.value = newValue
setModuleBackColor()
})
const emit = defineEmits({ switchModule: null })
const menu = router.getMenu()
const selectedKeys = ref([tool.data.get('SNOWY_MENU_MODULE_ID')])
const selectedKeys = ref([module.value])
const moduleClick = (id) => {
emit('switchModule', id)
tool.data.set('SNOWY_MENU_MODULE_ID', id)
@ -82,24 +90,24 @@
const setModuleBackColor = () => {
if (moduleUnfoldOpen.value) {
try {
const moduleMunu = document.getElementById('moduleMunu')
const moduleMenu = document.getElementById('moduleMenu')
moduleBackColor.value
? moduleMunu.classList.add('module-menu-color')
: moduleMunu.classList.remove('module-menu-color')
? moduleMenu.classList.add('module-menu-color')
: moduleMenu.classList.remove('module-menu-color')
} catch (err) {}
setSelectedKeys()
}
}
//
const setSelectedKeys = () => {
//
//
moduleBackColor.value
? (selectedKeys.value = new Array([]))
: (selectedKeys.value = [tool.data.get('SNOWY_MENU_MODULE_ID')])
}
</script>
<style type="less">
<style lang="less">
.module-row {
max-width: 357px;
}
@ -135,4 +143,7 @@
color: white;
background-color: var(--primary-color);
}
.module-card-scope {
height: 49px;
}
</style>

View File

@ -41,14 +41,14 @@
</div>
<div class="mb-4 layout-slide">
<h4 class="">顶栏应用主题色</h4>
<a-switch :checked="topHanderThemeColorOpen" @change="changeTopHanderThemeColorOpen" />
<a-switch :checked="topHeaderThemeColorOpen" @change="changeTopHanderThemeColorOpen" />
</div>
<div class="mb-4 layout-slide">
<h4>顶栏主题色通栏</h4>
<a-switch
style="float: right"
:checked="topHanderThemeColorSpread"
:disabled="!topHanderThemeColorOpen"
:checked="topHeaderThemeColorSpread"
:disabled="!topHeaderThemeColorOpen"
@change="changeTopHanderThemeColorSpread"
/>
</div>
@ -99,8 +99,8 @@
sideUniqueOpen: 'SIDE_UNIQUE_OPEN',
layoutTagsOpen: 'LAYOUT_TAGS_OPEN',
breadcrumbOpen: 'BREADCRUMD_OPEN',
topHanderThemeColorOpen: 'TOP_HANDER_THEME_COLOR_OPEN',
topHanderThemeColorSpread: 'TOP_HANDER_THEME_COLOR_SPREAD',
topHeaderThemeColorOpen: 'TOP_HEADER_THEME_COLOR_OPEN',
topHeaderThemeColorSpread: 'TOP_HEADER_THEME_COLOR_SPREAD',
moduleUnfoldOpen: 'MODULE_UNFOLD_OPEN'
}
export default defineComponent({
@ -159,22 +159,22 @@
'layoutTagsOpen',
'breadcrumbOpen',
'moduleUnfoldOpen',
'topHanderThemeColorOpen',
'topHanderThemeColorSpread',
'topHeaderThemeColorOpen',
'topHeaderThemeColorSpread',
'formStyle'
])
},
mounted() {},
methods: {
changeTopHanderThemeColorOpen() {
this.toggleState('topHanderThemeColorOpen')
if (!this.topHanderThemeColorOpen) {
this.globalStore.topHanderThemeColorSpread = false
tool.data.set('SNOWY_TOP_HANDER_THEME_COLOR_SPREAD', false)
this.toggleState('topHeaderThemeColorOpen')
if (!this.topHeaderThemeColorOpen) {
this.globalStore.topHeaderThemeColorSpread = false
tool.data.set('SNOWY_TOP_HEADER_THEME_COLOR_SPREAD', false)
}
},
changeTopHanderThemeColorSpread() {
this.toggleState('topHanderThemeColorSpread')
this.toggleState('topHeaderThemeColorSpread')
},
toggleState(stateName) {
this.globalStore.toggleConfig(stateName)

View File

@ -1,5 +1,5 @@
<template>
<div class="adminui-topbar">
<div class="admin-ui-topbar">
<div class="left-panel">
<a-breadcrumb>
<template v-for="item in breadList" :key="item.title">

View File

@ -5,7 +5,7 @@
<div v-if="!isMobile" class="screen panel-item hidden-sm-and-down" @click="fullscreen">
<fullscreen-outlined />
</div>
<dev-user-message />
<!-- <dev-user-message />-->
<a-dropdown class="user panel-item">
<div class="user-avatar">
<a-avatar :src="userInfo.avatar" />
@ -74,7 +74,7 @@
const setDrawer = ref(import.meta.env.VITE_SET_DRAWER)
const store = globalStore()
const isMobile = computed(() => {
return store.ismobile
return store.isMobile
})
const userInfo = computed(() => {
return store.userInfo

View File

@ -1,501 +1,388 @@
<template>
<div class="aminui">
<!-- 经典布局 -->
<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" />
</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 @switchModule="switchModule" />
<Topbar v-if="!ismobile && breadcrumbOpen" />
</div>
<div class="snowy-header-right">
<userbar />
</div>
</div>
<!-- 多标签 -->
<Tags v-if="!ismobile && layoutTagsOpen" />
<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 />
<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" />
</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">
<div v-if="!ismobile" class="panel-item hidden-sm-and-down" @click="menuIsCollapseClick">
<MenuUnfoldOutlined v-if="menuIsCollapse" />
<MenuFoldOutlined v-else />
</div>
<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 />
<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>
<!-- 经典布局 -->
<classical
v-if="layout === 'classical'"
:is-mobile="isMobile"
:menu-is-collapse="menuIsCollapse"
:side-theme="sideTheme"
:sys-base-config="sysBaseConfig"
:open-keys="openKeys"
:selected-keys="selectedKeys"
:menu="menu"
:breadcrumb-open="breadcrumbOpen"
:layout-tags-open="layoutTagsOpen"
:keep-live-route="keepLiveRoute"
:route-show="routeShow"
:route="route"
@onSelect="onSelect"
@onOpenChange="onOpenChange"
@menuIsCollapseClick="menuIsCollapseClick"
@switchModule="switchModule"
/>
<!-- 双排菜单布局 -->
<double-row
v-else-if="layout === 'doublerow'"
:is-mobile="isMobile"
:menu-is-collapse="menuIsCollapse"
:side-theme="sideTheme"
:sys-base-config="sysBaseConfig"
:open-keys="openKeys"
:selected-keys="selectedKeys"
:menu="menu"
:breadcrumb-open="breadcrumbOpen"
:layout-tags-open="layoutTagsOpen"
:keep-live-route="keepLiveRoute"
:route-show="routeShow"
:route="route"
:layoutSiderDowbleMenu="layoutSiderDowbleMenu"
:secondMenuSideTheme="secondMenuSideTheme"
:nextMenu="nextMenu"
:doublerowSelectedKey="doublerowSelectedKey"
:pMenu="pMenu"
@onSelect="onSelect"
@showMenu="showMenu"
@onOpenChange="onOpenChange"
@menuIsCollapseClick="menuIsCollapseClick"
@switchModule="switchModule"
/>
<!-- 退出最大化 -->
<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'
<script setup>
import Classical from '@/layout/pattern/classical.vue'
import DoubleRow from '@/layout/pattern/doublerow.vue'
import { globalStore, keepAliveStore } from '@/store'
import { mapState, mapStores, mapActions } from 'pinia'
import { ThemeModeEnum } from '@/utils/enum'
import { useRouter, useRoute } from 'vue-router'
import tool from '@/utils/tool'
import { message } from 'ant-design-vue'
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: {
...mapStores(globalStore),
...mapState(globalStore, [
'theme',
'ismobile',
'layout',
'layoutTagsOpen',
'menuIsCollapse',
'breadcrumbOpen',
'topHanderThemeColorOpen',
'topHanderThemeColorSpread',
'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()
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 moduleMenu = ref([])
const doublerowSelectedKey = ref([])
const layoutSiderDowbleMenu = ref(true)
const currentRoute = ref()
// 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(() => {
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 keepLiveRoute = computed(() => {
return kStore.keepLiveRoute
})
const routeShow = computed(() => {
return kStore.routeShow
})
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 filterUrl = (map) => {
const newMap = []
const traverse = (maps) => {
maps &&
maps.forEach((item) => {
item.meta = item.meta ? item.meta : {}
//
if (item.meta.hidden) {
return false
}
// iframe
if (item.meta.type === 'iframe') {
item.path = `/i/${item.name}`
}
//
if (item.children && item.children.length > 0) {
item.children = filterUrl(item.children)
}
newMap.push(item)
})
}
traverse(map)
return newMap
}
//
const showThis = () => {
pMenu.value = route.meta.breadcrumb ? route.meta.breadcrumb[0] : {}
//
nextTick(() => {
//
const active = route.meta.active || route.fullPath
selectedKeys.value = new Array(active)
const pidKey = getParentKeys(pMenu.value.children, active)
const nextTickMenu = pMenu.value.children
if (pidKey) {
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
}
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 = 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
}
}
if (!onSelectTag.value || sideUniqueOpen.value) {
openKeys.value = pidKey
}
//
if (layout.value === 'doublerow') {
setDoubleRowSelectedKey()
}
})
}
// -start
moduleMenu.value = router.getMenu()
//
const menuModuleId = tool.data.get('SNOWY_MENU_MODULE_ID')
let initMenu = []
if (menuModuleId) {
//
const module = router.getMenu().filter((item) => item.id === menuModuleId)
if (module.length > 0) {
initMenu = module[0].children
} else {
initMenu = router.getMenu()[0].children
}
} else {
initMenu = router.getMenu()[0].children
}
menu.value = filterUrl(initMenu)
showThis()
onMounted(() => {
switchoverTopHeaderThemeColor()
})
watch(route, (newValue) => {
currentRoute.value = route.path
//
selectedKeys.value = []
showThis()
if (layoutTagsOpen.value) {
const pidKey = getParentKeys(moduleMenu.value, route.path)
moduleMenu.value.forEach((item) => {
if (pidKey.includes(item.path)) {
tagSwitchModule(item.id, route.path)
}
})
}
})
//
watch(layout, (newValue) => {
document.body.setAttribute('data-layout', newValue)
if (newValue.includes('doublerow')) {
showThis()
setDoubleRowSelectedKey()
}
nextTick(() => {
//
switchoverTopHeaderThemeColor()
})
})
watch(topHeaderThemeColorOpen, (newValue) => {
console.log(topHeaderThemeColorOpen)
switchoverTopHeaderThemeColor()
})
watch(topHeaderThemeColorSpread, (newValue) => {
switchoverTopHeaderThemeColor()
})
const menuIsCollapseClick = () => {
store.toggleConfig('menuIsCollapse')
}
//
const switchoverTopHeaderThemeColor = () => {
console.log('刷新完之后' + topHeaderThemeColorOpen.value)
//
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')
} 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')
} 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 {
menu = this.$router.getMenu()[0].children
openKeys.value = Array.of(openKey) // new Array(openKey);
}
//
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) {
//
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('该模块下无任何菜单')
}
} 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]
}
},
// 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
if (element.children) {
const far = traverse(element.children, val)
if (far) {
return far.concat(element.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
},
menuIsCollapseClick() {
this.globalStore.toggleConfig('menuIsCollapse')
},
// 退
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) {}
}
}
}
})
return traverse(data, val)
}
//
const showMenu = (route) => {
pMenu.value = route
if (pMenu.value.children) {
nextMenu.value = filterUrl(pMenu.value.children)
}
if (!route.children || route.children.length === 0) {
layoutSiderDowbleMenu.value = false
router.push({ path: route.path })
} else {
layoutSiderDowbleMenu.value = true
}
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 (moduleMenu.value.length > 0) {
showThis()
const menus = moduleMenu.value.filter((item) => item.id === id)[0].children
if (menus.length > 0) {
//
tool.data.set('SNOWY_MENU_MODULE_ID', id)
//
menu.value = filterUrl(menus)
//
const path = traverseChild(menu.value)
router.push({ path })
} else {
message.warning('该模块下无任何菜单')
}
}
}
//
const tagSwitchModule = (id, path) => {
//
tool.data.set('SNOWY_MENU_MODULE_ID', id)
store.setModule(id)
const menus = moduleMenu.value.filter((item) => item.id === id)[0].children
//
menu.value = filterUrl(menus)
router.push({ path })
}
// path
const traverseChild = (menu) => {
if (menu[0].children !== undefined) {
if (menu[0].children.length > 0) {
return traverseChild(menu[0].children)
} else {
return menu[0].path
}
} else {
return menu[0].path
}
}
// 退
const exitMaximize = () => {
document.getElementById('app').classList.remove('main-maximize')
}
</script>

View File

@ -0,0 +1,146 @@
<template>
<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 ? '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 @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="keepLiveRoute">
<component :is="Component" :key="route.name" v-if="routeShow" />
</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>
</template>
<script setup name="classicalLayout">
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 IframeView from '@/layout/components/iframeView.vue'
import TopBar from '@/layout/components/topbar.vue'
const props = defineProps({
isMobile: {
type: Boolean,
default: () => false
},
menuIsCollapse: {
type: Boolean,
default: () => false
},
sideTheme: {
type: String,
default: () => ''
},
sysBaseConfig: {
type: Object,
default: () => undefined
},
openKeys: {
type: Array,
default: () => []
},
selectedKeys: {
type: Array,
default: () => []
},
menu: {
type: Array,
default: () => []
},
breadcrumbOpen: {
type: Boolean,
default: () => false
},
layoutTagsOpen: {
type: Boolean,
default: () => false
},
keepLiveRoute: {
type: Array,
default: () => []
},
routeShow: {
type: Boolean,
default: () => false
},
route: {
type: Object,
default: () => undefined
}
})
const emit = defineEmits(['onSelect', 'onOpenChange', 'menuIsCollapseClick', 'switchModule'])
const onSelect = (obj) => {
emit('onSelect', obj)
}
//
const onOpenChange = (keys) => {
emit('onOpenChange', keys)
}
//
const switchModule = (id) => {
emit('switchModule', id)
}
const menuIsCollapseClick = () => {
emit('menuIsCollapseClick')
}
</script>

View File

@ -0,0 +1,189 @@
<template>
<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" />
</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 @switchModule="switchModule" />
<top-bar v-if="!isMobile && breadcrumbOpen" />
</div>
<div class="snowy-header-right">
<user-bar />
</div>
</div>
<!-- 多标签 -->
<Tags v-if="!isMobile && layoutTagsOpen"></Tags>
<a-layout-content class="main-content-wrapper">
<div id="admin-ui-main" class="admin-ui-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 />
<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>
<script setup name="doublerowLayout">
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 IframeView from '@/layout/components/iframeView.vue'
import TopBar from '@/layout/components/topbar.vue'
const props = defineProps({
isMobile: {
type: Boolean,
default: () => false
},
menuIsCollapse: {
type: Boolean,
default: () => false
},
sideTheme: {
type: String,
default: () => ''
},
sysBaseConfig: {
type: Object,
default: () => undefined
},
openKeys: {
type: Array,
default: () => []
},
selectedKeys: {
type: Array,
default: () => []
},
menu: {
type: Array,
default: () => []
},
breadcrumbOpen: {
type: Boolean,
default: () => false
},
layoutTagsOpen: {
type: Boolean,
default: () => false
},
keepLiveRoute: {
type: Array,
default: () => []
},
routeShow: {
type: Boolean,
default: () => false
},
route: {
type: Object,
default: () => undefined
},
layoutSiderDowbleMenu: {
type: Boolean,
default: () => true
},
secondMenuSideTheme: {
type: String,
default: () => undefined
},
nextMenu: {
type: Array,
default: () => []
},
doublerowSelectedKey: {
type: Array,
default: () => []
},
pMenu: {
type: Object,
default: () => undefined
}
})
const emit = defineEmits(['onSelect', 'showMenu', 'onOpenChange', 'menuIsCollapseClick', 'switchModule'])
const onSelect = (obj) => {
emit('onSelect', obj)
}
const showMenu = (route) => {
emit('showMenu', route)
}
const menuIsCollapseClick = () => {
emit('menuIsCollapseClick')
}
//
const switchModule = (id) => {
emit('switchModule', id)
}
</script>

View File

@ -12,7 +12,7 @@ import { nextTick } from 'vue'
import { viewTagsStore } from '@/store'
export function beforeEach(to, from) {
const adminMain = document.querySelector('#adminui-main')
const adminMain = document.querySelector('#admin-ui-main')
if (!adminMain) {
return false
}
@ -24,7 +24,7 @@ export function beforeEach(to, from) {
}
export function afterEach(to) {
const adminMain = document.querySelector('#adminui-main')
const adminMain = document.querySelector('#admin-ui-main')
if (!adminMain) {
return false
}

View File

@ -34,7 +34,7 @@ export const globalStore = defineStore({
id: 'global',
state: () => ({
// 移动端布局
ismobile: false,
isMobile: false,
// 布局
layout: getCacheConfig('SNOWY_LAYOUT'),
// 菜单是否折叠 toggle
@ -46,9 +46,9 @@ export const globalStore = defineStore({
// 是否展示面包屑
breadcrumbOpen: getCacheConfig('SNOWY_BREADCRUMD_OPEN'),
// 顶栏是否应用主题色
topHanderThemeColorOpen: getCacheConfig('SNOWY_TOP_HANDER_THEME_COLOR_OPEN'),
topHeaderThemeColorOpen: getCacheConfig('SNOWY_TOP_HEADER_THEME_COLOR_OPEN'),
// 顶栏主题色通栏
topHanderThemeColorSpread: getCacheConfig('SNOWY_TOP_HANDER_THEME_COLOR_SPREAD'),
topHeaderThemeColorSpread: getCacheConfig('SNOWY_TOP_HEADER_THEME_COLOR_SPREAD'),
// 模块坞
moduleUnfoldOpen: getCacheConfig('SNOWY_MODULE_UNFOLD_OPEN'),
// 主题
@ -60,12 +60,14 @@ export const globalStore = defineStore({
// 用户信息
userInfo: toolDataGet('USER_INFO') || {},
// 系统配置
sysBaseConfig: toolDataGet('SNOWY_SYS_BASE_CONFIG') || config.SYS_BASE_CONFIG
sysBaseConfig: toolDataGet('SNOWY_SYS_BASE_CONFIG') || config.SYS_BASE_CONFIG,
// 默认应用
module: getCacheConfig('SNOWY_MENU_MODULE_ID')
}),
getters: {},
actions: {
setIsmobile(key) {
this.ismobile = key
setIsMobile(key) {
this.isMobile = key
},
setLayout(key) {
this.layout = key
@ -95,6 +97,9 @@ export const globalStore = defineStore({
},
setSysBaseConfig(key) {
this.sysBaseConfig = key
},
setModule(key) {
this.module = key
}
}
})

View File

@ -33,20 +33,20 @@ a, button, input, textarea {
}
/* 大布局样式 */
.aminui {
.admin-ui {
overflow: hidden;
height: 100vh;
height: 100%;
display: flex;
flex-flow: column;
}
.aminui-wrapper {
.admin-ui-wrapper {
display: flex;
flex: 1;
overflow: auto;
}
.adminui-main {
.admin-ui-main {
display: flex;
flex-direction: column;
height: 100%;
@ -222,16 +222,16 @@ a, button, input, textarea {
}
/* 面包屑 */
.adminui-topbar {
.admin-ui-topbar {
padding-left: 15px
}
.adminui-topbar .left-panel {
.admin-ui-topbar .left-panel {
display: flex;
align-items: center;
}
.adminui-topbar .right-panel {
.admin-ui-topbar .right-panel {
display: flex;
align-items: center;
}
@ -363,7 +363,7 @@ a, button, input, textarea {
}
/*页面最大化*/
.aminui.main-maximize {
.admin-ui.main-maximize {
.main-maximize-exit {
display: block;
}
@ -436,9 +436,10 @@ a, button, input, textarea {
// 滚动条需要哪里加哪个class
body,
.ant-scrolling-effect,
.ant-drawer-wrapper-body,
.ant-drawer-body,
.aminui,
.admin-ui,
.ant-modal-wrap,
.ant-transfer-list-content,
.ant-card,
@ -460,7 +461,7 @@ body,
.gen-preview-content,
.ant-menu,
.adminui-main{
.admin-ui-main{
&::-webkit-scrollbar {
/*滚动条整体样式*/
width : 0px; /*高宽分别对应横竖滚动条的尺寸*/

View File

@ -26,7 +26,7 @@
margin-left: 0px !important;
}
.adminui-main {
.admin-ui-main {
> .el-container {
display: block;
height: auto;
@ -84,43 +84,43 @@
}
}
.adminui-main > .el-container > *:first-child:not(.el-aside):not(.el-header) {
.admin-ui-main > .el-container > *:first-child:not(.el-aside):not(.el-header) {
border: 0;
margin-top: 0;
}
.adminui-main > .el-container > *:first-child:not(.el-aside):not(.el-header) + .el-aside {
.admin-ui-main > .el-container > *:first-child:not(.el-aside):not(.el-header) + .el-aside {
margin-top: 0;
}
.adminui-main > .el-container > .el-aside {
.admin-ui-main > .el-container > .el-aside {
border-bottom: 1px solid #ebeef5 !important;
}
.adminui-main > .el-container > .el-container {
.admin-ui-main > .el-container > .el-container {
border-top: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
margin-top: 15px;
}
.adminui-main > .el-container > .el-header {
.admin-ui-main > .el-container > .el-header {
@extend . headerPublic;
border-bottom: 1px solid #ebeef5;
}
.adminui-main > .el-container > .el-main {
.admin-ui-main > .el-container > .el-main {
border-top: 1px solid #ebeef5;
border-bottom: 1px solid #ebeef5;
margin-top: 15px;
}
.adminui-main > .el-container > .el-main + .el-aside {
.admin-ui-main > .el-container > .el-main + .el-aside {
border-left: 0 !important;
border-top: 1px solid #ebeef5;
margin-top: 15px;
}
.adminui-main > .el-container > .el-container > .el-header {
.admin-ui-main > .el-container > .el-container > .el-header {
@extend . headerPublic
}
}