增加了侧边栏搜索功能

NavMenu使用有状态组件写法

修复了一些样式问题

代码优化
master
toesbieya 6 years ago
parent 67b5a534fc
commit ee86939942
  1. 16
      vue/src/asset/style/variables.scss
  2. 142
      vue/src/component/menu/NavMenu/index.vue
  3. 47
      vue/src/component/menu/NavMenu/item.vue
  4. 9
      vue/src/component/menu/NavMenu/style.scss
  5. 56
      vue/src/layout/component/Aside/component/MenuSearch.vue
  6. 42
      vue/src/layout/component/Aside/component/OnePartRoot.vue
  7. 15
      vue/src/layout/component/Aside/component/TwoPartSub.vue
  8. 4
      vue/src/layout/component/Aside/style.scss
  9. 3
      vue/src/layout/component/Aside/theme-dark.scss
  10. 2
      vue/src/layout/component/Logo.vue
  11. 3
      vue/src/layout/component/Navbar/index.vue
  12. 6
      vue/src/layout/component/Navbar/style.scss
  13. 4
      vue/src/layout/component/Navbar/theme-dark.scss
  14. 4
      vue/src/layout/component/Navbar/theme-light.scss
  15. 50
      vue/src/layout/component/SettingDrawer.vue
  16. 3
      vue/src/layout/mixin/hamburger.js
  17. 22
      vue/src/layout/mixin/menu.js
  18. 24
      vue/src/layout/mixin/menuSearch.js
  19. 30
      vue/src/layout/store/app.js
  20. 5
      vue/src/layout/store/aside.js
  21. 39
      vue/src/store/module/resource.js
  22. 12
      vue/src/util/auth.js
  23. 5
      vue/src/util/index.js
  24. 29
      vue/src/util/tree.js

@ -78,10 +78,20 @@ $aside-width: 208px;
//侧边栏折叠时的宽度
$aside-collapse-width: $menu-icon-size + $menu-padding * 2;
//头部导航栏高度
//导航栏高度
$nav-height: 48px;
//头部多页签栏高度
//导航栏的padding-left
$navbar-padding-left: 12px;
//亮色导航栏的功能项颜色
$navbar-item-light-color: $menu-text-light-color;
//暗色导航栏的功能项颜色
$navbar-item-dark-color: rgba(255, 255, 255, .85);
//亮色导航栏的功能项hover颜色
$navbar-item-hover-light-color: rgba(255, 255, 255, .85);
//暗色导航栏的功能项hover颜色
$navbar-item-hover-dark-color: #0f2438;
//多页签栏高度
$tags-view-height: 32px;
//页头行高

@ -1,9 +1,12 @@
<script type="text/jsx">
import cssVariables from '@/asset/style/variables.scss'
import Item from './item'
import {isEmpty, deepClone, trim} from "@/util"
const inlineIndent = parseFloat(cssVariables.menuPadding)
export default {
functional: true,
name: 'NavMenu',
props: {
//
@ -21,6 +24,9 @@ export default {
//
showParentOnCollapse: Boolean,
//
searchWord: String,
// <0
showIconMaxDepth: {type: Number, default: 1},
@ -31,41 +37,133 @@ export default {
switchTransitionName: String
},
render(h, context) {
const {
data, children,
props: {
mode, menus, theme, collapse, showParentOnCollapse,
showIconMaxDepth, switchTransition, switchTransitionName
data() {
return {
//使computed
realSearchWord: ''
}
},
computed: {
//
realMenus() {
return isEmpty(this.realSearchWord)
? this.menus
: this.filterAfterSearch(deepClone(this.menus))
},
//使
useSwitchTransition() {
return this.switchTransition && !isEmpty(this.switchTransitionName)
},
themeClass() {
return `el-menu--${this.theme}`
},
menuClass() {
return `el-menu--${this.mode} ${this.themeClass}`
}
},
watch: {
//
searchWord(v) {
this.realSearchWord = trim(v)
},
//
realSearchWord() {
this.$nextTick(this.expandAfterSearch)
}
},
methods: {
//
filterAfterSearch(menus) {
if (!menus) return undefined
return menus.filter(menu => {
//
if (menu.meta.title.includes(this.realSearchWord)) {
return true
}
} = context
const themeClassName = `el-menu--${theme}`
const inlineIndent = parseFloat(cssVariables.menuPadding)
const children = this.filterAfterSearch(menu.children)
if (children) menu.children = children
return children && children.length > 0
})
},
//
expandAfterSearch() {
const menu = this.$refs['el-menu']
//
if (isEmpty(this.realSearchWord)) {
menu.openedMenus = []
return this.$nextTick(menu.initOpenedMenu)
}
const {openedMenus, submenus} = menu
const expandMenus = [...new Set(this.getHighlightMenuParent(this.realMenus))]
//el-menuopenuniqueOpened
for (const {fullPath} of expandMenus) {
const sub = submenus[fullPath]
if (!sub || openedMenus.includes(sub.index)) continue
sub.indexPath.forEach(i => {
!openedMenus.includes(i) && openedMenus.push(i)
})
}
},
//
getHighlightMenuParent(children, parent) {
const result = []
children.forEach(child => {
if (child.meta.title.includes(this.realSearchWord)) {
parent && result.push(parent)
}
if (child.children) {
result.push(...this.getHighlightMenuParent(child.children, child))
}
})
return result
}
},
let items = children || menus.map(m => (
render() {
let items = this.realMenus.map(m => (
<Item
menu={m}
popper-class={themeClassName}
show-parent={showParentOnCollapse}
collapse={collapse}
popper-class={this.themeClass}
highlight={this.realSearchWord}
show-parent={this.showParentOnCollapse}
collapse={this.collapse}
inline-indent={inlineIndent}
show-icon-max-depth={showIconMaxDepth}
show-icon-max-depth={this.showIconMaxDepth}
/>
))
if (switchTransition && switchTransitionName) {
items = <transition-group name={switchTransitionName}>{items}</transition-group>
if (this.useSwitchTransition) {
items = <transition-group name={this.switchTransitionName}>{items}</transition-group>
}
return (
<el-menu
ref="menu"
class={`el-menu--${mode} ${themeClassName}`}
mode={mode}
collapse={collapse}
ref="el-menu"
class={this.menuClass}
mode={this.mode}
collapse={this.collapse}
collapse-transition={false}
{...data}
{...{attrs: this.$attrs, on: this.$listeners}}
>
{items}
</el-menu>

@ -1,16 +1,40 @@
<script type="text/jsx">
import {isEmpty} from "@/util"
const MenuItemContent = {
functional: true,
props: {icon: String, title: String},
props: {
icon: String,
title: String,
highlight: String
},
render(h, context) {
const {icon, title} = context.props
const {icon, title, highlight} = context.props
const content = isEmpty(highlight)
? title
: getHighlightContent(h, title, highlight)
return [<v-icon icon={icon}/>, <span slot="title" class="menu-item-content">{title}</span>]
return [<v-icon icon={icon}/>, <span slot="title" class="menu-item-content">{content}</span>]
}
}
//vnode
function getHighlightContent(h, title, highlight) {
const start = title.indexOf(highlight)
if (start === -1) return title
const end = start + highlight.length
return [
title.substring(0, start),
<span class="menu-highlight-result">{title.substring(start, end)}</span>,
title.substring(end)
]
}
//showIconMaxDepthdepth
function getIcon({icon, showIconMaxDepth, depth}) {
if (showIconMaxDepth == null || showIconMaxDepth < 0) {
@ -30,18 +54,18 @@ function getOnlyChild(menu) {
return null
}
function renderSingleMenu(h, {index, inlineIndent, icon, title}) {
function renderSingleMenu(h, {index, inlineIndent, icon, title, highlight}) {
return (
<el-menu-item key={index} index={index} inline-indent={inlineIndent}>
<MenuItemContent icon={icon} title={title}/>
<MenuItemContent icon={icon} title={title} highlight={highlight}/>
</el-menu-item>
)
}
function renderSubMenu(h, {index, inlineIndent, icon, title, popperClass, children}) {
function renderSubMenu(h, {index, inlineIndent, icon, title, popperClass, highlight, children}) {
return (
<el-submenu key={index} index={index} inline-indent={inlineIndent} popper-class={popperClass}>
<MenuItemContent slot="title" icon={icon} title={title}/>
<MenuItemContent slot="title" icon={icon} title={title} highlight={highlight}/>
{children}
</el-submenu>
)
@ -57,7 +81,7 @@ function renderChildrenWithParentMenu(h, {icon, title, children}) {
}
function renderMenu(h, props) {
const {menu, inlineIndent, popperClass, showParent, collapse, showIconMaxDepth, depth = 1} = props
const {menu, inlineIndent, popperClass, highlight, showParent, collapse, showIconMaxDepth, depth = 1} = props
const onlyOneChild = getOnlyChild(menu)
@ -69,7 +93,8 @@ function renderMenu(h, props) {
index: fullPath,
inlineIndent,
icon: getIcon({icon, showIconMaxDepth, depth}),
title
title,
highlight
})
}
@ -91,6 +116,7 @@ function renderMenu(h, props) {
icon: getIcon({icon, showIconMaxDepth, depth}),
title,
popperClass,
highlight,
children
})
}
@ -105,6 +131,9 @@ export default {
//
popperClass: String,
//
highlight: String,
//
showParent: Boolean,

@ -1,14 +1,21 @@
@import "~@/asset/style/variables.scss";
//设置菜单图标的大小和与文字的间隔
.el-menu {
//图标的大小
.icon {
font-size: $menu-icon-size;
}
//图标与文字的间隔
.icon + .menu-item-content {
margin-left: $menu-icon-text-gap;
}
//菜单的高亮关键字
.menu-highlight-result {
vertical-align: unset;
color: $--color-success;
}
}
//原始样式使用 '>' 可能导致侧边栏折叠时还会显示右侧箭头图标

@ -0,0 +1,56 @@
<template>
<div class="aside-search">
<el-input
v-model="value"
size="mini"
clearable
placeholder="搜索菜单"
prefix-icon="el-icon-search"
@input="search"
/>
</div>
</template>
<script>
import {debounce} from "@/util"
export default {
name: "MenuSearch",
data: () => ({value: ''}),
methods: {
search(v) {
this.$emit('search', v)
}
},
created() {
this.search = debounce(this.search)
},
beforeDestroy() {
this.$emit('search', '')
}
}
</script>
<style lang="scss">
@import "~@/asset/style/variables.scss";
//
.collapse .aside-search {
display: none;
}
.aside-search {
margin: 12px 8px;
}
//
.dark .aside-search .el-input__inner {
border: none;
color: #ffffff;
background-color: lighten($menu-dark-background, 5%);
}
</style>

@ -1,10 +1,10 @@
<script type="text/jsx">
import hamburgerMixin from '@/layout/mixin/hamburger'
import menuMixin from "@/layout/mixin/menu"
import menuSearchMixin from '@/layout/mixin/menuSearch'
import {getters as appGetters} from "@/layout/store/app"
import {getters as asideGetters, mutations as asideMutations} from "@/layout/store/aside"
import {getters as pageGetters} from "@/layout/store/page"
import Hamburger from '@/layout/component/Hamburger'
import Logo from '@/layout/component/Logo'
import NavMenu from '@/component/menu/NavMenu'
import {getSidebarMenus, getActiveMenuByRoute} from "@/layout/util"
@ -13,9 +13,9 @@ import {moveToActiveMenuVertically} from "@/util/element-ui/elMenu"
export default {
name: "Sidebar",
mixins: [hamburgerMixin, menuMixin],
mixins: [hamburgerMixin, menuMixin, menuSearchMixin],
components: {Hamburger, Logo, NavMenu},
components: {Logo, NavMenu},
data() {
return {
@ -30,7 +30,7 @@ export default {
//
menus: () => getSidebarMenus(),
//
//
renderInDrawer() {
return this.isMobile || asideGetters.autoHide
},
@ -89,6 +89,11 @@ export default {
return !this.isMobile && appGetters.navMode !== 'aside'
},
//
renderMenuSearch() {
return !this.isMobile && asideGetters.search
},
className() {
return {'sidebar': true, 'collapse': this.collapse}
}
@ -103,7 +108,7 @@ export default {
this.activeMenu = getActiveMenuByRoute(this.$route)
const menu = this.$refs.menu
const menu = this.$_getElMenuInstance()
if (!menu) return
const item = menu.items[this.activeMenu]
@ -154,7 +159,7 @@ export default {
onSelect(index, indexPath, item, jump = true) {
//
if (asideGetters.uniqueOpen && indexPath.length === 1) {
const menu = this.$refs.menu
const menu = this.$_getElMenuInstance()
const opened = menu.openedMenus
opened.forEach(i => i !== index && menu.closeMenu(i))
}
@ -168,9 +173,25 @@ export default {
}
},
//el-menu
watchOpenedMenus() {
//
if (this.watchOpenedMenusCallback) {
this.watchOpenedMenusCallback()
this.watchOpenedMenusCallback = null
}
const menu = this.$_getElMenuInstance()
if (!menu) return
this.watchOpenedMenusCallback = menu.$watch('openedMenus', v => {
this.openedMenuNum = v.length
})
},
//
moveToCurrentMenu() {
moveToActiveMenuVertically(this.$refs.menu)
moveToActiveMenuVertically(this.$_getElMenuInstance())
}
},
@ -188,7 +209,11 @@ export default {
on-mouseenter={() => this.mouseOutside = false}
>
{this.showLogo && <logo show-title={!this.collapse}/>}
{this.renderMenuSearch && <menu-search on-search={this.handlerSearch}/>}
<nav-menu
ref="nav-menu"
menus={this.menus}
theme={asideGetters.theme}
collapse={this.collapse}
@ -199,7 +224,8 @@ export default {
switch-transition-name="sidebar"
on={{'select': this.onSelect, 'hook:mounted': this.watchOpenedMenus}}
/>
{this.renderHamburger && <hamburger class="el-menu-item"/>}
{!this.renderInDrawer && this.renderHamburger && <hamburger class="el-menu-item"/>}
</div>
)

@ -1,5 +1,7 @@
<script type="text/jsx">
import hamburgerMixin from '@/layout/mixin/hamburger'
import menuMixin from "@/layout/mixin/menu"
import menuSearchMixin from '@/layout/mixin/menuSearch'
import {getters as appGetters} from "@/layout/store/app"
import {getters as asideGetters} from "@/layout/store/aside"
import NavMenu from "@/component/menu/NavMenu"
@ -9,7 +11,7 @@ import {moveToActiveMenuVertically} from "@/util/element-ui/elMenu"
export default {
name: "SubSidebar",
mixins: [menuMixin],
mixins: [hamburgerMixin, menuMixin, menuSearchMixin],
components: {NavMenu},
@ -42,7 +44,7 @@ export default {
this.activeMenu = getActiveMenuByRoute(this.$route)
const menu = this.$refs.menu
const menu = this.$_getElMenuInstance()
if (!menu) return
const item = menu.items[this.activeMenu]
@ -63,7 +65,7 @@ export default {
onSelect(index, indexPath, item, jump = true) {
//
if (asideGetters.uniqueOpen && indexPath.length === 1) {
const menu = this.$refs.menu
const menu = this.$_getElMenuInstance()
const opened = menu.openedMenus
opened.forEach(i => i !== index && menu.closeMenu(i))
}
@ -73,7 +75,7 @@ export default {
//
moveToCurrentMenu() {
moveToActiveMenuVertically(this.$refs.menu)
moveToActiveMenuVertically(this.$_getElMenuInstance())
}
},
@ -85,6 +87,9 @@ export default {
<div class="sub-sidebar-title">
{this.rootMenu && this.rootMenu.meta.title}
</div>
{asideGetters.search && <menu-search on-search={this.handlerSearch}/>}
<nav-menu
menus={this.menus}
theme={asideGetters.theme}
@ -96,6 +101,8 @@ export default {
switch-transition-name="sidebar"
on-select={this.onSelect}
/>
{this.renderHamburger && <hamburger class="el-menu-item"/>}
</div>
)
}

@ -4,9 +4,9 @@
display: flex;
z-index: 11;
//移动端下侧边栏抽屉不能设置z-index否则会被modal遮住
//移动端下侧边栏抽屉不能设置z-index否则会被el-drawer的遮罩遮住
@include mobile {
z-index: auto;
z-index: auto !important;
}
//单层和双层侧边栏

@ -1,5 +1,8 @@
.aside.dark {
&,
> .el-drawer__wrapper .el-drawer__body {
background-color: $menu-dark-background;
}
.root-sidebar,
.root-sidebar > .el-menu--vertical.el-menu {

@ -34,7 +34,7 @@ $logoSize: 30px;
width: 100%;
height: $nav-height;
line-height: $nav-height;
padding-left: $menu-padding;
padding: 0 $menu-padding;
.logo-link {
display: block;

@ -3,7 +3,6 @@ import {mapState} from 'vuex'
import guideMixin from '@/mixin/guide'
import hamburgerMixin from '@/layout/mixin/hamburger'
import Bell from "./component/Bell"
import Hamburger from '@/layout/component/Hamburger'
import HeadMenu from "./component/HeadMenu"
import Logo from "@/layout/component/Logo"
import {getters as appGetters, mutations as appMutations} from "@/layout/store/app"
@ -17,7 +16,7 @@ export default {
mixins: [guideMixin.navbar, hamburgerMixin],
components: {Bell, Hamburger, HeadMenu, Logo},
components: {Bell, HeadMenu, Logo},
computed: {
...mapState('user', ['avatar', 'name', 'prepareLogout']),

@ -6,7 +6,7 @@
align-items: center;
flex-wrap: nowrap;
height: $nav-height;
padding-left: 12px;
padding-left: $navbar-padding-left;
z-index: 10;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
transition: all .2s;
@ -30,8 +30,8 @@
.logo-container {
width: auto;
min-width: 192px;
padding-left: 8px;
min-width: $aside-width - $navbar-padding-left;
padding-left: $menu-padding - $navbar-padding-left;
}
.avatar-wrapper {

@ -2,10 +2,10 @@
background-color: $root-menu-dark-background;
.navbar-item {
color: rgba(255, 255, 255, .85);
color: $navbar-item-dark-color;
&:hover {
background: $--color-primary;
background: $navbar-item-hover-dark-color;
}
}
}

@ -2,10 +2,10 @@
background-color: #ffffff;
.navbar-item {
color: $menu-text-light-color;
color: $navbar-item-light-color;
&:hover {
background: rgba(0, 0, 0, .025);
background: $navbar-item-hover-light-color;
}
}
}

@ -28,10 +28,6 @@
</div>
<el-divider>页面设置</el-divider>
<div class="drawer-item">
<span>显示logo</span>
<el-switch :value="pageGetters.showLogo" @input="changePageShowLogo"/>
</div>
<div class="drawer-item">
<span>分层结构</span>
<el-select
@ -44,6 +40,10 @@
<el-option value="left-right" label="左右"/>
</el-select>
</div>
<div class="drawer-item">
<span>显示logo</span>
<el-switch :value="pageGetters.showLogo" @input="changePageShowLogo"/>
</div>
<div class="drawer-item">
<span>显示页头</span>
<el-switch :value="pageGetters.showPageHeader" @input="changePageShowHeader"/>
@ -66,6 +66,18 @@
<el-option value="dark" label="暗色"/>
</el-select>
</div>
<div class="drawer-item">
<span>汉堡包位置</span>
<el-select
:value="asideGetters.hamburgerPosition"
size="mini"
style="width: 90px"
@input="changeAsideHamburgerPosition"
>
<el-option value="aside" label="侧边栏"/>
<el-option value="head" label="导航栏"/>
</el-select>
</div>
<div class="drawer-item">
<span>手风琴效果</span>
<el-switch :value="asideGetters.uniqueOpen" @input="changeAsideUniqueOpen"/>
@ -83,16 +95,8 @@
<el-switch :value="asideGetters.autoHide" @input="changeAsideAutoHide"/>
</div>
<div class="drawer-item">
<span>汉堡包位置</span>
<el-select
:value="asideGetters.hamburgerPosition"
size="mini"
style="width: 90px"
@input="changeAsideHamburgerPosition"
>
<el-option value="aside" label="侧边栏"/>
<el-option value="head" label="导航栏"/>
</el-select>
<span>显示搜索框</span>
<el-switch :value="asideGetters.search" @input="changeAsideSearch"/>
</div>
<el-divider>导航栏设置</el-divider>
@ -181,18 +185,19 @@ export default {
navMode: 'mix'
},
page: {
showLogo: true,
position: 'left-right',
showLogo: true,
showPageHeader: true,
showBackToTop: true
},
aside: {
theme: 'light',
hamburgerPosition: 'aside',
uniqueOpen: true,
collapse: false,
showParentOnCollapse: false,
autoHide: false,
hamburgerPosition: 'aside'
search: true
},
navbar: {
theme: 'light'
@ -241,12 +246,12 @@ export default {
appMutations.navMode(v)
},
changePageShowLogo(v) {
pageMutations.showLogo(v)
},
changePagePosition(v) {
pageMutations.position(v)
},
changePageShowLogo(v) {
pageMutations.showLogo(v)
},
changePageShowHeader(v) {
pageMutations.showPageHeader(v)
},
@ -257,6 +262,9 @@ export default {
changeAsideTheme(v) {
asideMutations.theme(v)
},
changeAsideHamburgerPosition(v) {
asideMutations.hamburgerPosition(v)
},
changeAsideUniqueOpen(v) {
asideMutations.uniqueOpen(v)
},
@ -269,8 +277,8 @@ export default {
changeAsideAutoHide(v) {
asideMutations.autoHide(v)
},
changeAsideHamburgerPosition(v) {
asideMutations.hamburgerPosition(v)
changeAsideSearch(v) {
asideMutations.search(v)
},
changeNavbarTheme(v) {

@ -1,3 +1,4 @@
import Hamburger from '@/layout/component/Hamburger'
import {getters as appGetters} from "@/layout/store/app"
import {getters as asideGetters} from "@/layout/store/aside"
import {getSidebarMenus} from "@/layout/util"
@ -7,6 +8,8 @@ import {getSidebarMenus} from "@/layout/util"
*/
export default {
components: {Hamburger},
computed: {
//渲染汉堡包的条件
//①侧边栏有菜单

@ -4,6 +4,7 @@
import {mutations as tagsViewMutations} from "@/layout/store/tagsView"
import {refreshPage} from "@/util/route"
import {isExternal} from "@/util/validate"
import {findComponentByTag} from "@/util/vue"
export default {
data() {
@ -23,7 +24,6 @@ export default {
//外部链接时打开新窗口
if (isExternal(fullPath)) {
window.open(fullPath)
//重置高亮菜单
return this.resetActiveMenu()
}
@ -39,24 +39,18 @@ export default {
//由于侧边栏菜单数组更新后,el-menu不一定会更新(当数组中不存在单级菜单时)
//所以手动更新el-menu的当前高亮菜单
resetActiveMenu() {
const menu = this.$refs.menu
const menu = this.$_getElMenuInstance()
menu && menu.updateActiveIndex(this.activeMenu)
},
//渲染el-menu时监听其展开菜单
watchOpenedMenus() {
//尝试取消之前设置的监听
if (this.watchOpenedMenusCallback) {
this.watchOpenedMenusCallback()
this.watchOpenedMenusCallback = null
//获取el-menu实例
//目前被侧边栏和双层侧边栏的主菜单使用
$_getElMenuInstance() {
if (!this.$_elMenuInstance) {
this.$_elMenuInstance = findComponentByTag(this, 'el-menu')
}
const menu = this.$refs.menu
if (!menu) return
this.watchOpenedMenusCallback = menu.$watch('openedMenus', v => {
this.openedMenuNum = v.length
})
return this.$_elMenuInstance
}
}
}

@ -0,0 +1,24 @@
/**
* 侧边栏搜索框混入
*/
import MenuSearch from '@/layout/component/Aside/component/MenuSearch'
import {trim} from "@/util"
import {findComponentByTag} from "@/util/vue"
export default {
components: {MenuSearch},
methods: {
$_getNavMenuInstance() {
if (!this.$_navMenuInstance) {
this.$_navMenuInstance = findComponentByTag(this, 'nav-menu')
}
return this.$_navMenuInstance
},
handlerSearch(v) {
this.$_getNavMenuInstance().realSearchWord = trim(v)
}
}
}

@ -42,33 +42,41 @@ function sort(routes) {
return
}
//菜单排序值的空值处理
const getSortValue = item => {
const sort = deepTap(item)
const sort = deepGetSortValue(item)
return isEmpty(sort) ? 10000 : sort
}
const deepTap = item => {
const {name, children = [], meta: {title, hidden, sort} = {}} = item
//获取菜单的排序值
const deepGetSortValue = item => {
const {children = [], meta: {hidden, sort} = {}} = item
if (hidden) return null
if (!isEmpty(sort)) return sort
//如果是类似首页那样的路由层级
if (isEmpty(name, title) && children.length === 1) {
return deepTap(children[0])
//如果只有一个子节点,那么取子节点的排序值
if (children.length === 1) {
return deepGetSortValue(children[0])
}
return null
}
//对根节点排序
routes.sort((pre, next) => {
pre = getSortValue(pre)
next = getSortValue(next)
if (pre < next) return -1
else if (pre === next) return 0
else return 1
if (pre === next) return 0
return 1
})
//对每一个根节点的子级排序
routes.forEach(route => {
const {children} = route
if (children && children.length) {
sort(children)
}
children && children.length && sort(children)
})
}

@ -26,7 +26,10 @@ const state = {
autoHide: false,
//汉堡包的位置,aside 或 head
hamburgerPosition: 'aside'
hamburgerPosition: 'aside',
//是否显示搜索框
search: true
}
const store = Vue.observable(state)

@ -7,7 +7,7 @@ import {str2routeConfig, metaExtend} from "@/router/util"
import {getAll} from "@/api/system/resource"
import {isEmpty, deepClone} from "@/util"
import {needAuth, auth} from "@/util/auth" // 此处存在循环引用?
import {createTree} from "@/util/tree"
import {createTree, shakeTree} from "@/util/tree"
import {isExternal} from "@/util/validate"
const state = {
@ -42,7 +42,7 @@ const mutations = {
}
const actions = {
init({state, commit}, {resources, admin}) {
init({state, commit}, {admin}) {
return getAll
.request()
.then(({data}) => {
@ -55,7 +55,7 @@ const actions = {
!state.init && addDynamicRoutes(routes)
//生成经过权限过滤后的菜单
appMutations.menus(getAuthorizedMenus({resources, admin}, routes))
appMutations.menus(getAuthorizedMenus(routes))
//设置初始化完成的标志
commit('init', true)
@ -70,7 +70,7 @@ function transformOriginRouteData(data) {
//未开启后端动态路由功能时,返回前端预设的静态路由
if (!routeConfig.useBackendDataAsRoute) return getDynamicRoutes()
data = deepClone(data).filter(i => {
const treeArray = deepClone(data).filter(i => {
//过滤掉数据接口或者未启用的项
if (i.type === 3 || !i.enable) {
return false
@ -84,16 +84,22 @@ function transformOriginRouteData(data) {
return true
})
return createTree(data)
return createTree(treeArray)
}
//获取经过权限控制后的菜单
function getAuthorizedMenus({resources, admin}, menus) {
function getAuthorizedMenus(menus) {
menus = deepClone(menus)
clean(menus)
addFullPath(menus)
filter(menus, i => !needAuth(i) || auth(i.fullPath))
return menus
return shakeTree(menus, i => {
//非叶子节点时,当定义了children属性却没有子节点时移除
if (i.children && i.children.length === 0) {
return false
}
return !needAuth(i) || auth(i.fullPath)
})
}
//删除不显示的菜单(没有children且没有meta.title)
@ -135,23 +141,8 @@ function addFullPath(routes, basePath = '/') {
})
}
//若没有children且传入的validate校验方法未通过,则删除,若有,当children长度为0时删除
function filter(arr, validate) {
for (let i = arr.length - 1; i >= 0; i--) {
const {children} = arr[i]
if (!children || children.length <= 0) {
!validate(arr[i]) && arr.splice(i, 1)
continue
}
filter(children, validate)
children.length <= 0 && arr.splice(i, 1)
}
}
//根据权限列表生成哈希表:<权限路径,权限id>
//该方法只处理叶子节点或者是接口类型的节点
function generateResourceMap(resources) {
if (!Array.isArray(resources) || resources.length <= 0) {
return {}

@ -4,15 +4,19 @@ import {isEmpty} from "@/util"
/**
* 判断是否需要鉴权需要则返回true
* 以下情况不需要鉴权①无meta②设置了noAuth=true③没有名称
* 以下情况不需要鉴权①无meta是redirect③没有名称设置了noAuth=true
*
* @param route vue-router的routeConfig或项目中说明的路由配置项
* @return {boolean}
*/
export function needAuth(route) {
if (route.path.startsWith('/redirect')) return false
const {meta} = route
return !meta || !meta.noAuth || isEmpty(meta.title) && isEmpty(meta.dynamicTitle)
const {path, meta} = route
if (!meta || path.startsWith('/redirect')) return false
if (isEmpty(meta.title) && isEmpty(meta.dynamicTitle)) return false
return meta.noAuth !== true
}
/**

@ -262,3 +262,8 @@ export function deepClone(source) {
}, {})
}
}
//有空值判断的trim
export function trim(str) {
return isEmpty(str) ? str : str.trim()
}

@ -1,4 +1,4 @@
import {deepClone} from "@/util/index"
import {deepClone} from "@/util"
import {createWorker} from '@/util/worker'
const DEFAULT_PROPS = {id: 'id', pid: 'pid', children: 'children', leafHasChildren: true}
@ -114,26 +114,34 @@ export function createLimitTreeByMap(fullMap, limit) {
/**
* 根据判断函数裁剪树当节点不满足predicate且无下级节点时将被裁剪
* 此方法会改变原数组的children属性
*
* @param tree {array} 原始树
* @param predicate {function} 判断节点是否满足条件的函数传入一个节点data
* @param predicate {function} 返回false时移除该节点传入当前节点
* @param childrenKey {string} 节点中代表子节点数组含义的字段名称比如[{ch:[...]}]就是'ch'
* @return {array} 经过裁剪后的树
*/
function shakeTree(tree = [], predicate = () => true, childrenKey = 'children') {
export function shakeTree(tree, predicate = () => true, childrenKey = 'children') {
if (!tree) return undefined
return tree.filter(data => {
data[childrenKey] = shakeTree(data[childrenKey], predicate)
return predicate(data) || data[childrenKey].length
const children = shakeTree(data[childrenKey], predicate, childrenKey)
if (children) {
data[childrenKey] = children
}
return predicate(data) || children && children.length > 0
})
}
/**
* 自底向上计算每个节点下有多少子节点
* 自底向上计算每个节点的value
*
* @param node
* @param valueKey {string}
* @param childrenKey {string}
* @return {*|number}
* @return {number}
*/
function calc(node, valueKey = 'value', childrenKey = 'children') {
if (!node) return 0
@ -181,9 +189,7 @@ export function flatTree(tree, childrenKey = 'children') {
export function getNodesByDfs(node) {
const nodes = []
const stack = []
stack.push(node)
const stack = [node]
while (stack.length > 0) {
const item = stack.pop()
@ -199,8 +205,7 @@ export function getNodesByDfs(node) {
export function getNodesByBfs(node) {
const nodes = []
const queue = []
queue.unshift(node)
const queue = [node]
while (queue.length > 0) {
const item = queue.shift()

Loading…
Cancel
Save