mirror of
https://github.com/iczer/vue-antd-admin
synced 2025-04-06 04:00:06 +08:00
269 lines
7.5 KiB
JavaScript
269 lines
7.5 KiB
JavaScript
/**
|
||
* 该插件可根据菜单配置自动生成 ANTD menu组件
|
||
* menuOptions示例:
|
||
* [
|
||
* {
|
||
* name: '菜单名称',
|
||
* path: '菜单路由',
|
||
* meta: {
|
||
* icon: '菜单图标',
|
||
* invisible: 'boolean, 是否不可见, 默认 false',
|
||
* },
|
||
* children: [子菜单配置]
|
||
* },
|
||
* {
|
||
* name: '菜单名称',
|
||
* path: '菜单路由',
|
||
* meta: {
|
||
* icon: '菜单图标',
|
||
* invisible: 'boolean, 是否不可见, 默认 false',
|
||
* },
|
||
* children: [子菜单配置]
|
||
* }
|
||
* ]
|
||
*
|
||
* i18n: 国际化配置。系统默认会根据 options route配置的 path 和 name 生成英文以及中文的国际化配置,如需自定义或增加其他语言,配置
|
||
* 此项即可。如:
|
||
* i18n: {
|
||
* messages: {
|
||
* CN: {dashboard: {name: '监控中心'}}
|
||
* HK: {dashboard: {name: '監控中心'}}
|
||
* }
|
||
* }
|
||
**/
|
||
import Menu from 'ant-design-vue/es/menu'
|
||
import Icon from 'ant-design-vue/es/icon'
|
||
import fastEqual from 'fast-deep-equal'
|
||
import {getI18nKey} from '@/utils/routerUtil'
|
||
|
||
const {Item, SubMenu} = Menu
|
||
|
||
const resolvePath = (path, params = {}) => {
|
||
let _path = path
|
||
Object.entries(params).forEach(([key, value]) => {
|
||
_path = _path.replace(new RegExp(`:${key}`, 'g'), value)
|
||
})
|
||
return _path
|
||
}
|
||
|
||
const toRoutesMap = (routes) => {
|
||
const map = {}
|
||
routes.forEach(route => {
|
||
map[route.fullPath] = route
|
||
if (route.children && route.children.length > 0) {
|
||
const childrenMap = toRoutesMap(route.children)
|
||
Object.assign(map, childrenMap)
|
||
}
|
||
})
|
||
return map
|
||
}
|
||
|
||
export default {
|
||
name: 'IMenu',
|
||
props: {
|
||
options: {
|
||
type: Array,
|
||
required: true
|
||
},
|
||
theme: {
|
||
type: String,
|
||
required: false,
|
||
default: 'dark'
|
||
},
|
||
mode: {
|
||
type: String,
|
||
required: false,
|
||
default: 'inline'
|
||
},
|
||
collapsed: {
|
||
type: Boolean,
|
||
required: false,
|
||
default: false
|
||
},
|
||
i18n: Object,
|
||
openKeys: Array
|
||
},
|
||
data () {
|
||
return {
|
||
selectedKeys: [],
|
||
sOpenKeys: [],
|
||
cachedOpenKeys: []
|
||
}
|
||
},
|
||
computed: {
|
||
menuTheme() {
|
||
return this.theme == 'light' ? this.theme : 'dark'
|
||
},
|
||
routesMap() {
|
||
return toRoutesMap(this.options)
|
||
}
|
||
},
|
||
created () {
|
||
this.updateMenu()
|
||
if (this.options.length > 0 && !this.options[0].fullPath) {
|
||
this.formatOptions(this.options, '')
|
||
}
|
||
// 自定义国际化配置
|
||
if(this.i18n && this.i18n.messages) {
|
||
const messages = this.i18n.messages
|
||
Object.keys(messages).forEach(key => {
|
||
this.$i18n.mergeLocaleMessage(key, messages[key])
|
||
})
|
||
}
|
||
},
|
||
watch: {
|
||
options(val) {
|
||
if (val.length > 0 && !val[0].fullPath) {
|
||
this.formatOptions(this.options, '')
|
||
}
|
||
},
|
||
i18n(val) {
|
||
if(val && val.messages) {
|
||
const messages = this.i18n.messages
|
||
Object.keys(messages).forEach(key => {
|
||
this.$i18n.mergeLocaleMessage(key, messages[key])
|
||
})
|
||
}
|
||
},
|
||
collapsed (val) {
|
||
if (val) {
|
||
this.cachedOpenKeys = this.sOpenKeys
|
||
this.sOpenKeys = []
|
||
} else {
|
||
this.sOpenKeys = this.cachedOpenKeys
|
||
}
|
||
},
|
||
'$route': function () {
|
||
this.updateMenu()
|
||
},
|
||
sOpenKeys(val) {
|
||
this.$emit('openChange', val)
|
||
this.$emit('update:openKeys', val)
|
||
}
|
||
},
|
||
methods: {
|
||
renderIcon: function (h, icon, key) {
|
||
if (this.$scopedSlots.icon && icon && icon !== 'none') {
|
||
const vnodes = this.$scopedSlots.icon({icon, key})
|
||
vnodes.forEach(vnode => {
|
||
vnode.data.class = vnode.data.class ? vnode.data.class : []
|
||
vnode.data.class.push('anticon')
|
||
})
|
||
return vnodes
|
||
}
|
||
return !icon || icon == 'none' ? null : h(Icon, {props: {type: icon}})
|
||
},
|
||
renderMenuItem: function (h, menu) {
|
||
let tag = 'router-link'
|
||
const path = resolvePath(menu.fullPath, menu.meta.params)
|
||
let config = {props: {to: {path, query: menu.meta.query}, }, attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;'}}
|
||
if (menu.meta && menu.meta.link) {
|
||
tag = 'a'
|
||
config = {attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;', href: menu.meta.link, target: '_blank'}}
|
||
}
|
||
return h(
|
||
Item, {key: menu.fullPath},
|
||
[
|
||
h(tag, config,
|
||
[
|
||
this.renderIcon(h, menu.meta ? menu.meta.icon : 'none', menu.fullPath),
|
||
this.$t(getI18nKey(menu.fullPath))
|
||
]
|
||
)
|
||
]
|
||
)
|
||
},
|
||
renderSubMenu: function (h, menu) {
|
||
let this_ = this
|
||
let subItem = [h('span', {slot: 'title', attrs: {style: 'overflow:hidden;white-space:normal;text-overflow:clip;'}},
|
||
[
|
||
this.renderIcon(h, menu.meta ? menu.meta.icon : 'none', menu.fullPath),
|
||
this.$t(getI18nKey(menu.fullPath))
|
||
]
|
||
)]
|
||
let itemArr = []
|
||
menu.children.forEach(function (item) {
|
||
itemArr.push(this_.renderItem(h, item))
|
||
})
|
||
return h(SubMenu, {key: menu.fullPath},
|
||
subItem.concat(itemArr)
|
||
)
|
||
},
|
||
renderItem: function (h, menu) {
|
||
const meta = menu.meta
|
||
if (!meta || !meta.invisible) {
|
||
let renderChildren = false
|
||
const children = menu.children
|
||
if (children != undefined) {
|
||
for (let i = 0; i < children.length; i++) {
|
||
const childMeta = children[i].meta
|
||
if (!childMeta || !childMeta.invisible) {
|
||
renderChildren = true
|
||
break
|
||
}
|
||
}
|
||
}
|
||
return (menu.children && renderChildren) ? this.renderSubMenu(h, menu) : this.renderMenuItem(h, menu)
|
||
}
|
||
},
|
||
renderMenu: function (h, menuTree) {
|
||
let this_ = this
|
||
let menuArr = []
|
||
menuTree.forEach(function (menu, i) {
|
||
menuArr.push(this_.renderItem(h, menu, '0', i))
|
||
})
|
||
return menuArr
|
||
},
|
||
formatOptions(options, parentPath) {
|
||
options.forEach(route => {
|
||
let isFullPath = route.path.substring(0, 1) == '/'
|
||
route.fullPath = isFullPath ? route.path : parentPath + '/' + route.path
|
||
if (route.children) {
|
||
this.formatOptions(route.children, route.fullPath)
|
||
}
|
||
})
|
||
},
|
||
updateMenu () {
|
||
this.selectedKeys = this.getSelectedKeys()
|
||
let openKeys = this.selectedKeys.filter(item => item !== '')
|
||
openKeys = openKeys.slice(0, openKeys.length -1)
|
||
if (!fastEqual(openKeys, this.sOpenKeys)) {
|
||
this.collapsed || this.mode === 'horizontal' ? this.cachedOpenKeys = openKeys : this.sOpenKeys = openKeys
|
||
}
|
||
},
|
||
getSelectedKeys() {
|
||
let matches = this.$route.matched
|
||
const route = matches[matches.length - 1]
|
||
let chose = this.routesMap[route.path]
|
||
if (chose.meta && chose.meta.highlight) {
|
||
chose = this.routesMap[chose.meta.highlight]
|
||
const resolve = this.$router.resolve({path: chose.fullPath})
|
||
matches = (resolve.resolved && resolve.resolved.matched) || matches
|
||
}
|
||
return matches.map(item => item.path)
|
||
}
|
||
},
|
||
render (h) {
|
||
return h(
|
||
Menu,
|
||
{
|
||
props: {
|
||
theme: this.menuTheme,
|
||
mode: this.$props.mode,
|
||
selectedKeys: this.selectedKeys,
|
||
openKeys: this.openKeys ? this.openKeys : this.sOpenKeys
|
||
},
|
||
on: {
|
||
'update:openKeys': (val) => {
|
||
this.sOpenKeys = val
|
||
},
|
||
click: (obj) => {
|
||
obj.selectedKeys = [obj.key]
|
||
this.$emit('select', obj)
|
||
}
|
||
}
|
||
}, this.renderMenu(h, this.options)
|
||
)
|
||
}
|
||
}
|