基于蚂蚁⾦服AntDesignVue-Menu导航菜单实现根据初始路由
⾃动选中对应菜单解决。。。
基于实现根据初始路由⾃动选定对应菜单,其实官⽅已经做了原⽣实现,但是VUE版本需要⾃⼰实现。
功能:
刷新或直接打开某路径时,⾃动根据路由选中当前路由对应的菜单
⽀持根⽬录(/)
匹配规则可以⾃定义,即使同样的path下对应多个菜单也可以,总之,只要Vue路由能正确导航就能正确匹配(判断是否选中时使⽤模拟导航)
注意:
由于vue2和vue3版本有差异,并且同⼀份代码兼容两个版本会使得代码冗余且难维护,因此争对两个版本的vue分别提供
由于逻辑复杂故采⽤TypeScript做类型声明,如果没有使⽤TypeScript的项⽬,可以把类容保存为".jsx"
后缀,并且去除相关的类型声明即可
具体部分⽤法参见源码最前⾯的⽂档注释部分,完整⽤法可以参考源码中的 MenuOption 类型。
具体导航到哪⾥ uterNavigation.location(RawLocation,原⽣VUE路由对象)决定,也可以使⽤⾃定义事件进⾏导航。
源码,vue2.x版本(可去除ts类型声明后保存为.jsx) - menu.tsx
import Vue, { VNode } from 'vue'
import { RawLocation } from 'vue-router/types/router'
import { Route } from 'vue-router'
/**
* 申明式嵌套菜单导航.
* 官⽅⽂档: www.antdv/components/menu-cn/#API
* <a-menu>
* <MenuItem>菜单项</MenuItem>
* <a-sub-menu title="⼦菜单">
* <MenuItem>⼦菜单项</MenuItem>
* </a-sub-menu>
* </a-menu>
* 根据提供的数据进⾏菜单渲染.
* <p>
* ⽰例:
* <pre><code>
* import Menu, { MenuOption } from '@/components/menu'
*
* const menus: Array<MenuOption> = [
* {
* // 菜单名称,⽤于显⽰
* name: '菜单1',
* // [可选]⽤于⾃动导航和判断当前路由与当前菜单是否匹配,以实现刷新⾃动选中或菜单组⾃动展开(如果不需要则建议使⽤原⽣组件)
* routerNavigation: {
* // 是否⾃动导航,如果开启则会⾃动根据{@code location}值进⾏导航.默认开启
* autoNavigation: true,
* // 该属性类型为Vue原⽣导航参数类型(RawLocation)
* location: { name: 'Login' }
* }
* },
* {
* name: '菜单组',
* children: [
* {
* name: '菜单2',
* routerNavigation: {
* location: '/reg'
* },
* // 还可以为菜单项添加原⽣事件
* on: {
* click: () => {}
* }
* },
* {
* name: '菜单3',
* routerNavigation: {
* location: { path: '/reg2' }
* }
* }
* ]
* }
* ];
*
* // template中使⽤(不要忘记在components中导⼊): <Menu mode="inline" :menus="menus"></Menu>
* </pre></code>
* 如⽰例所⽰,只要使⽤时申明需要显⽰的菜单名字以及该菜单导航所需的对象即可(该对象默认⽤于导航已经⾃动根据当前路由进⾏⾼亮) * 但是可以禁⽤⾃动导航功能.并可以⾃定义菜单的点击事件
* 如果不需要⾃动根据当前路由进⾏⾼亮和展开,请直接使⽤原⽣组件即可.
* </p>
* @author Laeni<m@laeni>
*/
export d({
name: 'Menu',
props: {
/**
* 需要进⾏渲染的菜单,
* 类型为{@link MenuOption}.
*/
menus: {
type: Array,
required: true
},
/
**
* 菜单类型
* ⽔平: vertical vertical-right
* 垂直: horizontal
* 内嵌: inline
*/
mode: {
type: String,
default: ''
},
/**
* 主题.
* 详情参考官⽹: www.antdv/components/menu-cn/#components-menu-demo-menu-themes
* light dark
*/
theme: {
type: String,
default: ''
},
/**
* 事件.
* 如
* {
* click: () => {},
* }
*/
on: {
type: Object,
default: () => {}
},
},
data(): {
// us的扩展,⽅便使⽤
menuOptionLocals: Array<MenuOptionLocal>,
// 菜单展开的分组
openKeys: Array<string>,
// 菜单选中的菜单项
selectedKeys: Array<string>
} {
return {
menuOptionLocals: [],
// 菜单展开的分组
openKeys: [],
// 菜单选中的菜单项
selectedKeys: []
}
},
methods: {
/**
* 初始化菜单选中状态.
* <p>
* 功能:
* <ul>
* <li>当路由改变时计算出路由对应的菜单,并选中</li>
* <li>如果选择的菜单在菜单组中,则将该组展开</li>
* </ul>
* </p>
*/
initMenu(): void {
// 从{@uOptionLocals}中将出与当前路由相符的选项
const menuOptionLocal: MenuOptionLocal | null = this.uOptionLocals, this.$route);
if (menuOptionLocal !== null) {
// 设置当前选择的key
this.selectedKeys = [menuOptionLocal.key];
// 依次将当前菜单所在的⽗菜单展开
this.openKeying(menuOptionLocal);
}
},
/**
* 渲染菜单项或菜单组.
* @param menu 单个MenuOption类型的对象
*/
renderMenu(menu: MenuOptionLocal): VNode {
if (menu.children && menu.children.length > 0) {
derSubMenu(menu);
} else {
derMenuItem(menu);
}
},
// 渲染菜单组
renderSubMenu(menu: MenuOptionLocal): VNode {
const props = {
key: menu.key,
title: menu.name,
}
return (
<a-sub-menu key={ menu.key } {...{ props }}>
{ menu.children && menu.children.map(v => derMenu(v)) }
</a-sub-menu>
);
},
// 渲染菜单项
renderMenuItem(menu: MenuOptionLocal): VNode {
const props = {
disabled: menu.disabled
}
const on = {
...,
// 注⼊点击事件
导航菜单
click: () => {
// 原始事件
if ( && lick === 'function') {
}
// 如果配置了⾃动导航,则根据配置规则进⾏点击时⾃动导航
if (uterNavigation && (uterNavigation.autoNavigation || uterNavigation.autoNavigation === undefined)) { // 导航到指定路由(如果当前路由处于⽬标路由则不进⾏导航)
if (this.$uterNavigation.location).route.fullPath !== this.$route.fullPath) {
this.$router.uterNavigation.location);
}
}
}
}
return (
<a-menu-item key={ menu.key } {...{ props, on }}>
{ menu.name }
</a-menu-item>
);
},
/*region private*/
/**
* 根据菜单的层次结构⾃动⽣成该菜单下唯⼀的 key.
* 添加该元素的⽗元素(如果有).
* @param menus 需要⽣产key则菜单.
* @param parent [递归调⽤时使⽤]⽗节点
*/
toMenuOptionLocal(menus: Array<MenuOption>, parent?: MenuOptionLocal): Array<MenuOptionLocal> { // 默认key前缀,如果⽗元素为空则使⽤,⽤于⽗元素
const defaultPrefix = 'k';
const menuLocals: Array<MenuOptionLocal> = [];
menus.forEach((v, i) => {
const menuLocal: MenuOptionLocal = {
...v,
children: undefined,
key: (parent ? parent.key : defaultPrefix) + '-' + i,
parent: parent,
};
// ⽣成⼦节点的key
if (v.children && v.children.length > 0) {
menuLocal.children = MenuOptionLocal(v.children, menuLocal);
}
// 如果可以导航则⽣成导航对象
if (v.routerNavigation && v.routerNavigation.location) {
}
menuLocals.push(menuLocal);
})
return menuLocals;
},
/**
* 从{@uOptionLocals}中将出与当前路由相符的选项.
* @param menuOptionLocals 菜单选项
* @param route 当前路由对象
* @return MenuOptionLocal 如果没有到则返回null
*/
findMenuOptionLocal(menuOptionLocals: Array<MenuOptionLocal>, route: Route): MenuOptionLocal | null { // 从节点树中出所有符合要求的节点
const menuOptionLocalAll: Array<MenuOptionLocal> = this.findMenuOptionLocalAll(menuOptionLocals, route);
// 从所有符合要求的节点中出优先级最⾼的⼀个节点(如"/xxx"优先级⾼于"/")
const high = this.findHighPriority(menuOptionLocalAll);
// 对⾸页进⾏特殊处理
if (high) {
// 如果 high ⼀定不满⾜要求则跳过
if (!uterNavigation || !uterNavigation.location) {
return null;
}
const highRoute: Route = ute || this.$uterNavigation.location).route;
const homeRoute: Route = this.$solve({ name: 'Home' }).route;
if (highRoute.fullPath !== route.fullPath && highRoute.fullPath === homeRoute.fullPath) {
return null;
}
}
return high;
},
/**
* 从{@uOptionLocals}中将出与当前路由相符的<b>所有</b>选项.
* @param menuOptionLocals 菜单选项
* @param route 当前路由对象
* @return Array<MenuOptionLocal> 返回0个或多个菜单项数组
*/
findMenuOptionLocalAll(menuOptionLocals: Array<MenuOptionLocal>, route: Route): Array<MenuOptionLocal> { // ⽤于放满⾜要求的选项节点
const menuOptionLocalAll: Array<MenuOptionLocal> = [];
for (const menu of menuOptionLocals) {
// 如果有⼦元素则先⼦元素
if (menu.children && menu.children.length > 0) {
const menuOptionLocal: MenuOptionLocal | null = this.findMenuOptionLocal(menu.children, route);
if (menuOptionLocal) {
menuOptionLocalAll.push(menuOptionLocal);
}
}
// 如果不是⼦元素则判断当前元素是否为⽬标元素
else if (uterNavigation && uterNavigation.location) {
// 利⽤Vue模拟解析⽣成⼀个路由对象
const resolveRoute: Route = ute || this.$uterNavigation.location).route;
// 通过⽐较{@link Route#path}和{@link Route#query}以判断该菜单是否为⽬标菜单
if (route.path.indexOf(resolveRoute.path) > -1) {
// 定义⼀个变量假设本元素符合要求,如果不符合则改为false
let ok: boolean = true;
for (const key of Object.keys(resolveRoute.query)) {
if (resolveRoute.query[key] && resolveRoute.query[key] !== route.query[key]) {
ok = false;
break;
} else if (typeof route.query[key] === 'undefined') {
ok = false;
break;
}
}
if (ok) {
menuOptionLocalAll.push(menu);
}
}
}
}
return menuOptionLocalAll;
},
// 从所有符合要求的节点中出优先级最⾼的⼀个节点(如"/xxx"优先级⾼于"/")
findHighPriority(menuOptionLocals: Array<MenuOptionLocal>): MenuOptionLocal | null {
if (menuOptionLocals.length === 0) {
return null;
}
// 假定第⼀个元素优先级最⾼
let high: MenuOptionLocal = menuOptionLocals[0];
// 通过path进⾏⽐较
for (let i = 1; i < menuOptionLocals.length; i++) {
const menu = menuOptionLocals[i];
// 如果 high 或 menu ⼀定不满⾜要求则跳过
if (!uterNavigation || !uterNavigation.location || !uterNavigation || !uterNavigation.location) { high = menu;
continue;
}
// 利⽤Vue模拟解析⽣成⼀个路由对象
const highRoute: Route = ute || this.$uterNavigation.location).route;
const menuRoute: Route = ute || this.$uterNavigation.location).route;
// 优先级⽐较
if (menuRoute.path.indexOf(highRoute.path) > -1) {
high = menu;
}
}
return high;
},
/**
* 依次将当前菜单所在的⽗菜单展开.
* @param menuOptionLocal 当前url导航对应的菜单.
*/
openKeying(menuOptionLocal: MenuOptionLocal): void {
if (menuOptionLocal.parent) {
this.openKeys.push(menuOptionLocal.parent.key);
this.openKeying(menuOptionLocal.parent);
}
}
/*endregion*/
},
created(): void {
// @ts-ignore
},
mounted(): void {
this.initMenu();
},
render(): VNode {
const props: any = {
openKeys: this.openKeys,
selectedKeys: this.selectedKeys,
}
if (de) {
}
if (this.theme) {
props.theme = this.theme;
}
const on: any = {
// 同步展开或关闭时的标签
openChange: (openKeys: never[]) => this.openKeys = openKeys,
// select: (item: any) => this.selectedKeys = item.selectedKeys,
}
return (
<a-menu {...{props, on}}>
{uOptionLocals.map((menu) => derMenu(menu))}
</a-menu>
);
},
watch: {
$route() {
this.initMenu();
},
menus() {
// @ts-ignore
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论