feat: add mixed navigation mode; #102

新增:混合导航菜单模式;
master
iczer 5 years ago
parent 002cf50440
commit 094935b758
  1. 12
      src/components/menu/menu.js
  2. 1
      src/components/setting/Setting.vue
  3. 2
      src/components/setting/i18n.js
  4. 41
      src/layouts/AdminLayout.vue
  5. 17
      src/layouts/header/AdminHeader.vue
  6. 2
      src/layouts/header/HeaderSearch.vue
  7. 6
      src/layouts/header/index.less
  8. 25
      src/router/guards.js
  9. 26
      src/store/modules/setting.js
  10. 3
      src/utils/i18n.js

@ -77,7 +77,7 @@ export default {
}, },
created () { created () {
this.updateMenu() this.updateMenu()
if (!this.options[0].fullPath) { if (this.options.length > 0 && !this.options[0].fullPath) {
this.formatOptions(this.options, '') this.formatOptions(this.options, '')
} }
// 自定义国际化配置 // 自定义国际化配置
@ -90,7 +90,7 @@ export default {
}, },
watch: { watch: {
options(val) { options(val) {
if (!val[0].fullPath) { if (val.length > 0 && !val[0].fullPath) {
this.formatOptions(this.options, '') this.formatOptions(this.options, '')
} }
}, },
@ -195,18 +195,14 @@ export default {
}, },
updateMenu () { updateMenu () {
const menuRoutes = this.$route.matched.filter(item => item.path !== '') const menuRoutes = this.$route.matched.filter(item => item.path !== '')
const route = menuRoutes.pop() this.selectedKeys = this.getSelectedKey(this.$route)
this.selectedKeys = [this.getSelectedKey(route)]
let openKeys = menuRoutes.map(item => item.path) let openKeys = menuRoutes.map(item => item.path)
if (!fastEqual(openKeys, this.sOpenKeys)) { if (!fastEqual(openKeys, this.sOpenKeys)) {
this.collapsed || this.mode === 'horizontal' ? this.cachedOpenKeys = openKeys : this.sOpenKeys = openKeys this.collapsed || this.mode === 'horizontal' ? this.cachedOpenKeys = openKeys : this.sOpenKeys = openKeys
} }
}, },
getSelectedKey (route) { getSelectedKey (route) {
if (route.meta.invisible && route.parent) { return route.matched.map(item => item.path)
return this.getSelectedKey(route.parent)
}
return route.path
} }
}, },
render (h) { render (h) {

@ -26,6 +26,7 @@
> >
<img-checkbox :title="$t('navigate.side')" img="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" value="side"/> <img-checkbox :title="$t('navigate.side')" img="https://gw.alipayobjects.com/zos/rmsportal/JopDzEhOqwOjeNTXkoje.svg" value="side"/>
<img-checkbox :title="$t('navigate.head')" img="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" value="head"/> <img-checkbox :title="$t('navigate.head')" img="https://gw.alipayobjects.com/zos/rmsportal/KDNDBbriJhLwuqMoxcAr.svg" value="head"/>
<img-checkbox :title="$t('navigate.mix')" img="https://gw.alipayobjects.com/zos/antfincdn/x8Ob%26B8cy8/LCkqqYNmvBEbokSDscrm.svg" value="mix"/>
</img-checkbox-group> </img-checkbox-group>
</setting-item> </setting-item>
<setting-item> <setting-item>

@ -12,6 +12,7 @@ module.exports = {
title: '导航设置', title: '导航设置',
side: '侧边导航', side: '侧边导航',
head: '顶部导航', head: '顶部导航',
mix: '混合导航',
content: { content: {
title: '内容区域宽度', title: '内容区域宽度',
fluid: '流式', fluid: '流式',
@ -82,6 +83,7 @@ module.exports = {
title: 'Navigation Mode', title: 'Navigation Mode',
side: 'Side Menu Layout', side: 'Side Menu Layout',
head: 'Top Menu Layout', head: 'Top Menu Layout',
mix: 'Mix Menu Layout',
content: { content: {
title: 'Content Width', title: 'Content Width',
fluid: 'Fluid', fluid: 'Fluid',

@ -3,7 +3,7 @@
<drawer v-if="isMobile" v-model="collapsed"> <drawer v-if="isMobile" v-model="collapsed">
<side-menu :theme="theme.mode" :menuData="menuData" :collapsed="false" :collapsible="false" @menuSelect="onMenuSelect"/> <side-menu :theme="theme.mode" :menuData="menuData" :collapsed="false" :collapsible="false" @menuSelect="onMenuSelect"/>
</drawer> </drawer>
<side-menu :class="[fixedSideBar ? 'fixed-side' : '']" :theme="theme.mode" v-else-if="layout === 'side'" :menuData="menuData" :collapsed="collapsed" :collapsible="true" /> <side-menu :class="[fixedSideBar ? 'fixed-side' : '']" :theme="theme.mode" v-else-if="layout === 'side' || layout === 'mix'" :menuData="sideMenuData" :collapsed="collapsed" :collapsible="true" />
<div v-if="fixedSideBar && !isMobile" :style="`width: ${sideMenuWidth}; min-width: ${sideMenuWidth};max-width: ${sideMenuWidth};`" class="virtual-side"></div> <div v-if="fixedSideBar && !isMobile" :style="`width: ${sideMenuWidth}; min-width: ${sideMenuWidth};max-width: ${sideMenuWidth};`" class="virtual-side"></div>
<drawer v-if="!hideSetting" v-model="showSetting" placement="right"> <drawer v-if="!hideSetting" v-model="showSetting" placement="right">
<div class="setting" slot="handler"> <div class="setting" slot="handler">
@ -12,7 +12,7 @@
<setting /> <setting />
</drawer> </drawer>
<a-layout class="admin-layout-main beauty-scroll"> <a-layout class="admin-layout-main beauty-scroll">
<admin-header :style="headerStyle" :menuData="menuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/> <admin-header :style="headerStyle" :menuData="headMenuData" :collapsed="collapsed" @toggleCollapse="toggleCollapse"/>
<a-layout-header v-if="fixedHeader"></a-layout-header> <a-layout-header v-if="fixedHeader"></a-layout-header>
<a-layout-content class="admin-layout-content"> <a-layout-content class="admin-layout-content">
<div :style="`min-height: ${minHeight}px; position: relative`"> <div :style="`min-height: ${minHeight}px; position: relative`">
@ -32,7 +32,7 @@ import PageFooter from './footer/PageFooter'
import Drawer from '../components/tool/Drawer' import Drawer from '../components/tool/Drawer'
import SideMenu from '../components/menu/SideMenu' import SideMenu from '../components/menu/SideMenu'
import Setting from '../components/setting/Setting' import Setting from '../components/setting/Setting'
import {mapState, mapMutations} from 'vuex' import {mapState, mapMutations, mapGetters} from 'vuex'
const minHeight = window.innerHeight - 64 - 24 - 122 const minHeight = window.innerHeight - 64 - 24 - 122
@ -46,30 +46,61 @@ export default {
showSetting: false showSetting: false
} }
}, },
watch: {
$route(val) {
this.setActivated(val)
},
layout() {
this.setActivated(this.$route)
}
},
computed: { computed: {
...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar', ...mapState('setting', ['isMobile', 'theme', 'layout', 'footerLinks', 'copyright', 'fixedHeader', 'fixedSideBar',
'hideSetting', 'menuData']), 'hideSetting', 'menuData']),
...mapGetters('setting', ['firstMenu', 'subMenu']),
sideMenuWidth() { sideMenuWidth() {
return this.collapsed ? '80px' : '256px' return this.collapsed ? '80px' : '256px'
}, },
headerStyle() { headerStyle() {
let width = (this.fixedHeader && this.layout == 'side' && !this.isMobile) ? `calc(100% - ${this.sideMenuWidth})` : '100%' let width = (this.fixedHeader && this.layout !== 'head' && !this.isMobile) ? `calc(100% - ${this.sideMenuWidth})` : '100%'
let position = this.fixedHeader ? 'fixed' : 'static' let position = this.fixedHeader ? 'fixed' : 'static'
let transition = this.fixedHeader ? 'transition: width 0.2s' : '' let transition = this.fixedHeader ? 'transition: width 0.2s' : ''
return `width: ${width}; position: ${position}; ${transition}` return `width: ${width}; position: ${position}; ${transition}`
},
headMenuData() {
const {layout, menuData, firstMenu} = this
return layout === 'mix' ? firstMenu : menuData
},
sideMenuData() {
const {layout, menuData, subMenu} = this
return layout === 'mix' ? subMenu : menuData
} }
}, },
methods: { methods: {
...mapMutations('setting', ['correctPageMinHeight']), ...mapMutations('setting', ['correctPageMinHeight', 'setActivatedFirst']),
toggleCollapse () { toggleCollapse () {
this.collapsed = !this.collapsed this.collapsed = !this.collapsed
}, },
onMenuSelect () { onMenuSelect () {
this.toggleCollapse() this.toggleCollapse()
}, },
setActivated(route) {
if (this.layout === 'mix') {
let matched = route.matched
matched = matched.slice(0, matched.length - 1)
const {firstMenu} = this
for (let menu of firstMenu) {
if (matched.findIndex(item => item.path === menu.fullPath) !== -1) {
this.setActivatedFirst(menu.fullPath)
break
}
}
}
}
}, },
created() { created() {
this.correctPageMinHeight(minHeight - 1) this.correctPageMinHeight(minHeight - 1)
this.setActivated(this.$route)
}, },
beforeDestroy() { beforeDestroy() {
this.correctPageMinHeight(-minHeight + 1) this.correctPageMinHeight(-minHeight + 1)

@ -6,12 +6,12 @@
<h1 v-if="!isMobile">{{systemName}}</h1> <h1 v-if="!isMobile">{{systemName}}</h1>
</router-link> </router-link>
<a-divider v-if="isMobile" type="vertical" /> <a-divider v-if="isMobile" type="vertical" />
<a-icon v-if="layout === 'side'" class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse"/> <a-icon v-if="layout !== 'head'" class="trigger" :type="collapsed ? 'menu-unfold' : 'menu-fold'" @click="toggleCollapse"/>
<div v-if="layout == 'head' && !isMobile" class="admin-header-menu"> <div v-if="layout !== 'side' && !isMobile" class="admin-header-menu" :style="`width: ${menuWidth};`">
<i-menu class="head-menu" style="height: 64px; line-height: 64px;box-shadow: none" :theme="headerTheme" mode="horizontal" :options="menuData" @select="onSelect"/> <i-menu class="head-menu" :theme="headerTheme" mode="horizontal" :options="menuData" @select="onSelect"/>
</div> </div>
<div :class="['admin-header-right', headerTheme]"> <div :class="['admin-header-right', headerTheme]">
<header-search class="header-item" /> <header-search class="header-item" @active="val => searchActive = val" />
<a-tooltip class="header-item" title="帮助文档" placement="bottom" > <a-tooltip class="header-item" title="帮助文档" placement="bottom" >
<a href="https://iczer.github.io/vue-antd-admin/" target="_blank"> <a href="https://iczer.github.io/vue-antd-admin/" target="_blank">
<a-icon type="question-circle-o" /> <a-icon type="question-circle-o" />
@ -49,7 +49,8 @@ export default {
{key: 'CN', name: '简体中文', alias: '简体'}, {key: 'CN', name: '简体中文', alias: '简体'},
{key: 'HK', name: '繁體中文', alias: '繁體'}, {key: 'HK', name: '繁體中文', alias: '繁體'},
{key: 'US', name: 'English', alias: 'English'} {key: 'US', name: 'English', alias: 'English'}
] ],
searchActive: false
} }
}, },
computed: { computed: {
@ -63,6 +64,12 @@ export default {
langAlias() { langAlias() {
let lang = this.langList.find(item => item.key == this.lang) let lang = this.langList.find(item => item.key == this.lang)
return lang.alias return lang.alias
},
menuWidth() {
const {layout, searchActive} = this
const headWidth = layout === 'head' ? '1236px' : '100%'
const extraWidth = searchActive ? '564px' : '364px'
return `calc(${headWidth} - ${extraWidth})`
} }
}, },
methods: { methods: {

@ -24,10 +24,12 @@ export default {
methods: { methods: {
enterSearchMode () { enterSearchMode () {
this.searchMode = true this.searchMode = true
this.$emit('active', true)
setTimeout(() => this.$refs.input.focus(), 300) setTimeout(() => this.$refs.input.focus(), 300)
}, },
leaveSearchMode () { leaveSearchMode () {
this.searchMode = false this.searchMode = false
setTimeout(() => this.$emit('active', false), 300)
} }
} }
} }

@ -4,6 +4,12 @@
box-shadow: @shadow-down; box-shadow: @shadow-down;
position: relative; position: relative;
background: @base-bg-color; background: @base-bg-color;
.head-menu{
height: 64px;
line-height: 64px;
vertical-align: middle;
box-shadow: none;
}
&.dark{ &.dark{
background: @header-bg-color-dark; background: @header-bg-color-dark;
color: white; color: white;

@ -38,7 +38,30 @@ const authorityGuard = (to, from, next, options) => {
} }
} }
/**
* 混合导航模式下一级菜单跳转重定向
* @param to
* @param from
* @param next
* @param options
* @returns {*}
*/
const redirectGuard = (to, from, next, options) => {
const {store} = options
if (store.state.setting.layout === 'mix') {
const firstMenu = store.getters['setting/firstMenu']
if (firstMenu.find(item => item.fullPath === to.fullPath)) {
store.commit('setting/setActivatedFirst', to.fullPath)
const subMenu = store.getters['setting/subMenu']
if (subMenu.length > 0) {
return next({path: subMenu[0].fullPath})
}
}
}
next()
}
export default { export default {
beforeEach: [loginGuard, authorityGuard], beforeEach: [loginGuard, authorityGuard, redirectGuard],
afterEach: [] afterEach: []
} }

@ -1,5 +1,6 @@
import config from '@/config' import config from '@/config'
import {ADMIN} from '@/config/default' import {ADMIN} from '@/config/default'
import {formatFullPath} from '@/utils/i18n'
export default { export default {
namespaced: true, namespaced: true,
state: { state: {
@ -8,8 +9,30 @@ export default {
palettes: ADMIN.palettes, palettes: ADMIN.palettes,
pageMinHeight: 0, pageMinHeight: 0,
menuData: [], menuData: [],
activatedFirst: undefined,
...config, ...config,
}, },
getters: {
firstMenu(state) {
const {menuData} = state
if (!menuData[0].fullPath) {
formatFullPath(menuData)
}
return menuData.map(item => {
const menuItem = {...item}
delete menuItem.children
return menuItem
})
},
subMenu(state) {
const {menuData, activatedFirst} = state
if (!menuData[0].fullPath) {
formatFullPath(menuData)
}
const current = menuData.find(menu => menu.fullPath === activatedFirst)
return current ? current.children : []
}
},
mutations: { mutations: {
setDevice (state, isMobile) { setDevice (state, isMobile) {
state.isMobile = isMobile state.isMobile = isMobile
@ -49,6 +72,9 @@ export default {
}, },
setAsyncRoutes(state, asyncRoutes) { setAsyncRoutes(state, asyncRoutes) {
state.asyncRoutes = asyncRoutes state.asyncRoutes = asyncRoutes
},
setActivatedFirst(state, activatedFirst) {
state.activatedFirst = activatedFirst
} }
} }
} }

@ -73,5 +73,6 @@ function mergeI18nFromRoutes(i18n, routes) {
export { export {
initI18n, initI18n,
mergeI18nFromRoutes mergeI18nFromRoutes,
formatFullPath
} }

Loading…
Cancel
Save