手摸手创建一个 Vue + Ts 项目(四) —— 完善左侧菜单栏
系列目录
前言
上一篇中,我们实现了一个最简单的左侧菜单栏,效果如下:
但还有一些问题存在,例如没有支持点击切换路由、隐藏菜单、图标支持等等,这一篇我们来完善下。
完善菜单
在上面生成的菜单中,有一个问题,迫切的需要解决下。那就是不能够隐藏一些菜单,例如 404 等。先来解决下这个:
隐藏菜单
我们先定义在菜单的元数据中,增加一个属性:hidden
,当配置该属性为 true 的时候,则该路由不在菜单中展示。
例如配置 404 页面的路由如下:
import type { RouteRecordRaw } from "vue-router"
const errorRoutes: RouteRecordRaw[] = [
{
path: '/404',
name: 'NotFound',
meta: {
title: 'Page Not Found'
hidden: true
},
component: () => import('@/views/error/404.vue')
},
// 所有未定义路由,全部重定向到 404
{
path: '/:pathMatch(.*)*',
redirect: '/404',
hidden: true
}
]
export default errorRoutes
同理,对 /
根目录路由,同样配置在菜单中隐藏。
修改上面定义的「组装成菜单信息」逻辑如下:
routes.forEach((route: RouteRecordRaw) => {
if (!route.meta?.hidden) {
const menuOption: MenuOption = {
label: route.name,
key: route.name as string,
};
menuOptions.value.push(menuOption);
}
});
重新刷新页面,效果如下:
设置点击菜单时切换路由
目前还没有实现点击相应菜单时,切换不同的页面。要想实现该效果,NaiveUI 的 Menu 组件,提供了非常好的支持,可以通过将 label
渲染为 <router-link />
来改变路由,具体可以查看相应文档:菜单 Menu - Naive UI
这里,我们改一下生成菜单数据的地方 —— useMenu.ts
:
import { h } from "vue";
import { MenuOption } from "naive-ui";
import { RouteRecordRaw, RouterLink } from "vue-router";
const getMenuOptions = (routes: RouteRecordRaw[]): MenuOption[] => {
let menuOptions: MenuOption[] = [];
routes.forEach((route: RouteRecordRaw) => {
// @ts-ignore
if (!route.meta?.hidden) {
const menuOption: MenuOption = {
label: () => {
if (route.children && Array.isArray(route.children)) {
return route.name;
} else {
return h(
RouterLink,
{ to: { name: route.name } },
{ default: () => route.name }
);
}
},
key: route.name as string,
};
if (route.children && route.children.length > 0) {
menuOption.children = getMenuOptions(route.children);
}
menuOptions.push(menuOption);
}
});
return menuOptions;
};
刷新页面之后呢,点击 Table 子菜单,发现并没有跳转过来,这是因为
dashboard
和 table
路由配置中,因为只有一个页面,父子路由名称一样,只匹配到了父路由。这里暂时把父节点的 name
属性删掉,重新测试,可以正常跳转啦:
但父级菜单都变成空白啦,这个有两种方式,可以通过修改名称,或者配置当只有一个子菜单时,不显示父菜单来解决。下面我们就实现一下第二种方式。
当只有一个子菜单时不显示父菜单
这里同样还是修改生成菜单数据的地方 —— useMenu.ts
文件,判断,当子菜单只有一个时,直接取子菜单即可。修改为如下:
/**
* 判断路由是否只有一个子路由
* @param route 路由
* @returns 如果该路由只有一个子路由,则返回 true;否则返回 false
*/
const isSingleChildren = (route: RouteRecordRaw): boolean => {
return route?.children?.length === 1;
};
/**
* 过滤路由配置中需要在菜单中隐藏的路由
* @param routes 路由列表
* @returns 路由列表
*/
const filterHiddenRouter = (routes: RouteRecordRaw[]): RouteRecordRaw[] => {
return routes.filter((item: RouteRecordRaw) => {
return !item.meta?.hidden;
});
};
/**
* 将路由信息转换为菜单信息
* @param route 路由信息
* @returns 菜单信息
*/
const getMenuOption = (route: RouteRecordRaw): MenuOption | undefined => {
// @ts-ignore
const routeInfo = isSingleChildren(route) ? route.children[0] : route;
const menuOption: MenuOption = {
label: () => {
if (routeInfo.children && Array.isArray(routeInfo.children)) {
return routeInfo.name;
} else {
return h(
RouterLink,
{ to: { name: routeInfo.name } },
{ default: () => routeInfo.name }
);
}
},
key: routeInfo.name as string,
};
if (routeInfo.children && routeInfo.children.length > 0) {
menuOption.children = getMenuOptions(routeInfo.children);
}
return menuOption;
};
const getMenuOptions = (routes: RouteRecordRaw[]): MenuOption[] => {
let menuOptions: MenuOption[] = [];
filterHiddenRouter(routes).forEach((route: RouteRecordRaw) => {
// @ts-ignore
const menuOption = getMenuOption(route);
if (menuOption) {
menuOptions.push(menuOption);
}
});
return menuOptions;
};
刷新页面,已经实现效果啦:
添加菜单Icon
NaiveUI 的 Menu 组件,提供了比较方便的图标实现。并且在文档「菜单 Menu - Naive UI」中也提供了比较详细的示例。
这里参考文档,简单实现一下:
首先,还是在生成菜单数据的地方,先来获取路由配置中的图标属性。
-
composables
/useMenu.ts
:import { h, Component } from "vue"; import { NIcon } from "naive-ui"; const renderIcon = (icon: Component) => { return () => h(NIcon, null, { default: () => h(icon) }) } const getMenuOption = (route: RouteRecordRaw): MenuOption | undefined => { // @ts-ignore const routeInfo = isSingleChildren(route) ? route.children[0] : route; const menuOption: MenuOption = { label: () => { if (routeInfo.children && Array.isArray(routeInfo.children)) { return routeInfo.name; } else { return h( RouterLink, { to: { name: routeInfo.name } }, { default: () => routeInfo.name } ); } }, key: routeInfo.name as string, icon: routeInfo.meta?.icon ? renderIcon(routeInfo.meta?.icon as Component) : undefined }; if (routeInfo.children && routeInfo.children.length > 0) { menuOption.children = getMenuOptions(routeInfo.children); } return menuOption; };
之后修改路由配置信息,在路由的元数据中增加 icon 属性:
以 dashboard
的路由配置为例:
import type { RouteRecordRaw } from "vue-router";
import BasicLayout from "@/layouts/BasicLayout.vue";
import { DashboardCustomizeRound } from '@vicons/material'
const dashboardRoutes: RouteRecordRaw[] = [
{
path: "/dashboard",
component: BasicLayout,
children: [
{
path: "",
name: "Dashboard",
component: () => import("@/views/dashboard/index.vue"),
meta: {
icon: DashboardCustomizeRound
}
},
],
},
];
export default dashboardRoutes;
刷新页面:
这里需要注意哈,路由配置中是 ts 文件,不能够自动导入,所以需要手动导入需要依赖的图标组件。
解决了上面一系列问题后,基本看起来就像一个正常的菜单了,但样式还有一些问题,打开浏览器的「开发者工具」,可以看到高度并没有撑满浏览器,后面我们会解决下这个问题。
使用 TS 来重新定义路由的 Meta
在前面的编码过程中,我们在路由配置的元数据中,添加了两个属性:hidden
和 icon
,当用到这两个属性时,没有任何提示,其实与 ts 的理念是相违背的,所以这里我们来重新定义下路由的元数据类型。
首先,在 router
文件夹下,添加 type.ts
文件,来定义路由元数据类型和新的路由类型:
-
router
/type.ts
:import { RouteRecordRaw } from "vue-router" import { Component } from 'vue' interface RouteRecordMeta { hidden?: boolean, icon?: Component } // @ts-expect-error export interface RouteRecord extends Omit<RouteRecordRaw, 'meta'> { name?: string, meta?: RouteRecordMeta, children?: RouteRecord[] }
在这里定义了两个接口类型,分别是路由元数据(
RouteRecordMeta
)和路由类型(RouteRecord
)。
之后使用到 vue-router
的 RouteRecordRaw
类型的地方,修改为我们刚定义的 RouteRecord
。例如 dashboard
路由配置文件中:
import { RouteRecord } from "@/router/type"
import BasicLayout from "@/layouts/BasicLayout.vue";
import { DashboardCustomizeRound } from '@vicons/material'
const dashboardRoutes: RouteRecord[] = [
{
path: "/dashboard",
component: BasicLayout,
children: [
{
path: "",
name: "Dashboard",
component: () => import("@/views/dashboard/index.vue"),
meta: {
icon: DashboardCustomizeRound,
}
},
],
},
];
export default dashboardRoutes;
结语
本篇完善了下之前简陋的左侧菜单,增加了一些特性:隐藏菜单、设置点击菜单时切换路由、当只有一个子菜单时不显示父菜单、菜单icon、使用Ts重定义路由元数据类型。至此,一个基本完善的菜单就完成了,下一篇中,让我们对于布局的样式进行一定的修改,让其看起来更加美观一些。
我是「代码笔耕」,致力于打造高效简洁、稳定可靠代码的后端开发。 由于不是专业的前端开发,也是通过写这一系列的文章,来提升巩固下自己的水平。 本文可能存在纰漏或错误,如有问题欢迎指正,感谢您阅读这篇文章,如果觉得还行的话,不要忘记点赞、评论、收藏喔! 最后欢迎大家关注我的公众号「代码笔耕」和开源项目:easii (easii) - Gitee.com
转载自:https://juejin.cn/post/7239340461235945531