手摸手创建一个 Vue3 + Ts 项目(二) —— 实现左侧菜单栏
发表于:2023-06-01 | 分类: 前端
字数统计: 4.9k | 阅读时长: 22分钟 | 阅读量:

前言

在上一篇中,我们初始化了整个项目,并且添加了最基本的依赖,已经实现了效果如下:

image-20230607143306006

而在常见的后台项目中,常见于左侧有一菜单栏,右侧是标题栏和内容的布局。这一篇中,我们主要来实现该基本布局。

安装 vue-router

vue-router 官网:https://router.vuejs.org/zh/

安装

pnpm install vue-router@4

创建两个测试页面

在 src 目录下,新建 views 目录,用于存放页面文件。

分别创建两个测试页面,dashboardtable

  • dashboard/index.vue

    <script setup lang="ts">
    
    </script>
    
    <template>
    	<div>
    		<h3>Dashboard</h3>
    		<router-link to="/table">Go to Table</router-link>
    	</div>
    </template>
    
    <style scoped>
    
    </style>
  • table/index.vue

    <script setup lang="ts">
    
    </script>
    
    <template>
    	<div>
    		<h3>Table</h3>
    		<router-link to="/dashboard">Go to Dashboard</router-link>
    	</div>
    </template>
    
    <style scoped>
    
    </style>

配置页面路由

有了页面文件之后,第一件事情就是需要配置当前页面的路由。

在 src 目录下,新建 router 文件夹,用于存放路由配置文件。首先在其下创建一个 modules 文件夹,区分各模块不同的路由配置文件。

创建两个路由配置文件:dashboard.tstable.ts

  • router/modules/dashboard.ts

    import type { RouteRecordRaw } from 'vue-router'
    
    const dashboardRoutes: RouteRecordRaw[] = [
      {
        path: '/dashboard',
        name: 'Dashboard',
        component: () => import('@/views/dashboard/index.vue')
      }
    ]
    
    export default dashboardRoutes
  • router/modules/table.ts

    import type { RouteRecordRaw } from 'vue-router'
    
    const tableRoutes: RouteRecordRaw[] = [
      {
        path: '/table',
        name: 'Table',
        component: () => import('@/views/table/index.vue')
      }
    ]
    
    export default tableRoutes

动态获取所有路由配置

配置好前面的路由信息后,需要将这些路由配置加载到 vue-router 中,这里采用动态读取文件的加载方式,即在 modules 目录下创建好路由配置后,可以自动加载到 vue-router 中,不需要再次导入。

在 router 目录下创建一个 routes.ts 文件,用于读取 modules 目录下的路由配置信息:

  • routes.ts

    import type { RouteRecordRaw } from 'vue-router'
    
    const modules = import.meta.glob('./modules/**/*.ts', { eager: true })
    
    const routes = Object.keys(modules).reduce((routes, key) => {
        // @ts-ignore
        const module = modules[key].default
        if (Array.isArray(module)) {
            return [...routes, ...module]
        } else {
            return [...routes, ...module.routes]
        }
    }, [] as RouteRecordRaw[])
    
    export default routes

启用 vue-router

经过前面的准备工作后,就可以将所有路由应用到 vue 中了。在 router 目录下创建 index.ts

  • index.ts

    import {createWebHistory, createRouter} from "vue-router";
    import type {App} from 'vue'
    // 获取所有路由
    import routes from './routes'
    
    const router = createRouter({
      routes,
      // 这里使用历史记录模式
      history: createWebHistory()
    })
    
    export const useRouter = (app: App<Element>): void => {
        app.use(router)
    }

随后在 main.ts 中启用:

  • main.ts

    // router
    import { useRouter } from '@/router'
    
    useRouter(app)

最后,在 App.vue 中,渲染路由匹配到的组件:

<script setup lang="ts">
</script>

<template>
	<router-view></router-view>
</template>

<style scoped>
</style>

测试

启动项目之后,打开发现页面空白,这是因为我们没有配置根路径,之后会加上的,一步步来。

可以先在浏览器的地址栏后面增加 /dashboard,就可以访问到 dashboard 下的页面啦:

image-20230607143441197

点击 「Go To Table」:,可以正常访问到 table 页面:

image-20230607143453827

OK,到目前为止,vue-router 已经成功整合完成,接下来完善下。

配置根路径

在前面讲到,打开页面时,需要手动输入要访问的链接,这对于用户来说是非常不友好的,那如何配置打开页面时,自动跳转到 /dashbaord 呢?

接下来,我们就来解决这个问题。

在 router/modules 目录下,创建 index.ts 文件:

  • router/modules/index.ts

    import type { RouteRecordRaw } from "vue-router"
    
    const rootRoutes: RouteRecordRaw[] = [
      {
        path: '/',
        name: 'Home',
        redirect: '/dashboard'
      }
    ]
    
    export default rootRoutes

在这个文件中,配置了 / 根路径,通过 redirect 指定根路径直接跳转到 /dashboard,则重新打开浏览器后,会发现地址栏,又 / 自动变为 /dashboard

配置 404

当输入一个错误的 URL 地址时,这里的错误是指,没有配置相关的路由信息,会出现页面空白的现象,例如最开始访问 / 时,其实就是没有匹配到相关的路由。这里可以配置一个 404 页面,当出现这种情况时,跳转到该页面。

创建一个 404 页面

在 views 目录下,创建一个 error 文件夹,用于存放 404 页面:

  • views/error/404.vue

    <script setup lang="ts">
      import { ref } from 'vue'
      import { useIntervalFn } from '@vueuse/core'
      import { useRouter } from 'vue-router'
    
      const router = useRouter()
    
      const timeRef = ref<number>(10)
    
      const { pause } = useIntervalFn(() => {
        if (timeRef.value > 1) {
          timeRef.value--
        } else {
          pause()
          router.push('/')
        }
      }, 1000)
    </script>
    
    <template>
      <div class="flex h-full flex-col items-center mt-20">
        <n-result status="404" title="404 资源不存在" size="huge">
          <template #footer>
            <n-button @click="$router.push('/')">返回首页</n-button>
          </template>
          <div class="text-center">{{ timeRef }} 秒后返回首页</div>
        </n-result>
      </div>
    </template>

这里需要注意,这里用到了一个之前没有提到的模块:@vueuse/core。这是一个 Vue 组合式 Api 必不可少的功能函数库。里面封装了许多常用功能,开箱即用。

官网地址:http://www.vueusejs.com/

安装:pnpm i @vueuse/core

配置404路由

OK,回到正题。

新建好 404 页面后,相应需要创建一个路由,在 router/modules 目录下创建一个 error.ts 文件:

import type { RouteRecordRaw } from "vue-router"

const errorRoutes: RouteRecordRaw[] = [
  {
    path: '/404',
    name: 'NotFound',
    meta: {
      title: 'Page Not Found'
    },
    component: () => import('@/views/error/404.vue')
  },
  // 所有未定义路由,全部重定向到 404
  {
    path: '/:pathMatch(.*)*',
    redirect: '/404'
  }
]

export default errorRoutes

测试

在浏览器中输入一个未配置 URL,效果如下:

image-20230607143505404

10 秒后,会自动跳转到主页。

实现菜单栏

实现一个最简单的左侧菜单栏,主要有以下四步:

  1. 获取所有路由信息,组装成一个树状菜单结构;
  2. 将树状菜单结构应用于菜单组件;
  3. 配置菜单点击切换路由;
  4. 菜单固定在左侧;

当然了,还有很多额外的特性,例如:嵌套菜单、菜单icon、隐藏菜单等等,下面我们来一起实现它。

路由信息组装成树状菜单结构

首先,我们组装成的树状菜单结构,其属性需要与实际要应用的菜单组件相对应,这里我们用到的是 NaiveUI菜单 Menu 组件,通过翻阅文档,可以看到菜单选项中必传的配置属性就两个:labelkey

  • **label**:「string | (() => VNodeChild)」菜单项的内容
  • **key**:「string」菜单项的标识符

了解了这个,直接开干:

  1. 获取路由配置

    这一步非常简单,因为在上面的路由章节中,routes.ts 已经能够直接返回所有的路由配置。所以,可以直接导入:

    import routes from '@/router/routes'
  2. 组装成菜单信息

    let menuOptions: MenuOption[] = [];
    routes.forEach((route: RouteRecordRaw) => {
        const menuOption: MenuOption = {
          label: route.name,
          key: route.name as string,
        };
        if (route.children && route.children.length > 0) {
          menuOption.children = getMenuOptions(route.children)
        }
        menuOptions.push(menuOption);
    });

    其中 MenuOption 是 NaiveUI 中定义的一种类型

这里将菜单相关的一些属性和操作,封装成为 “组合式函数”(Composables) ,在 src 目录中新建 composables 文件夹,用于存放“组合式函数”文件。创建 useMenu.ts 文件:

  • composables/useMenu.ts

    import type { Ref } from "vue";
    import { ref, watch } from "vue";
    import { MenuOption } from "naive-ui";
    import routes from "@/router/routes";
    import { RouteRecordRaw, useRoute } from "vue-router";
    
    export interface UserMenu {
      /**
       * 菜单选项
       */
      menuOptions: Ref<MenuOption[]>;
      /**
       * 展开的子菜单标识符数组
       */
      expandKeys: Ref<string[]>;
      /**
       * 更改子菜单标识符数组回调方法
       */
      updateExpandKeys: (keys: string[]) => void;
      /**
       * 当前选中的菜单
       */
      currentMenu: Ref<string>;
      /**
       * 修改选中菜单时的回调方法
       */
      updateValue: (key: string) => void;
    }
    
    const getMenuOptions = (routes: RouteRecordRaw[]): MenuOption[] => {
      let menuOptions: MenuOption[] = [];
      routes.forEach((route: RouteRecordRaw) => {
        const menuOption: MenuOption = {
          label: route.name,
          key: route.name as string,
        };
        if (route.children && route.children.length > 0) {
          menuOption.children = getMenuOptions(route.children)
        }
        menuOptions.push(menuOption);
      });
      return menuOptions;
    };
    
    export function useMenu(): UserMenu {
      const menus: MenuOption[] = getMenuOptions(routes);
    
      /**
       * 菜单选项
       */
      const menuOptions = ref(menus);
    
      /**
       * 展开的子菜单标识符数组
       */
      const expandKeys: Ref<string[]> = ref<string[]>([]);
    
      /**
       * 当前菜单
       */
      const currentMenu: Ref<string> = ref<string>("");
    
      const route = useRoute();
      /**
       * 监听路由变化
       */
      watch(
        () => route.path,
        () => {
          routeChanged();
        },
        { immediate: true }
      );
    
      /**
       * 判断路由是否包含在菜单列表中
       *
       * @param routeName 路由名称
       * @param menuList  菜单列表
       * @returns 如果包含则返回 true;否则返回 false
       */
      function menuContains(routeName: string, menuList: MenuOption[]): boolean {
        for (let menu of menuList) {
          if (menu.key === routeName) {
            return true;
          }
          if (menu.children && menu.children.length > 0) {
            const childMenuContains = menuContains(routeName, menu.children);
            if (childMenuContains) {
              return true;
            }
          }
        }
        return false;
      }
    
      /**
       * 路由发生变化时的回调
       */
      function routeChanged(): void {
        // 获取匹配到的路由列表
        const matched = route.matched;
        // 获取匹配到路由名称
        const matchedNames = matched
          .filter((it) => menuContains(it.name as string, menus))
          .map((it) => it.name as string);
        const matchLen = matchedNames.length;
        const matchExpandKeys = matchedNames.slice(0, matchLen - 1);
        const openKey = matchedNames[matchLen - 1];
        expandKeys.value = matchExpandKeys;
        currentMenu.value = openKey;
      }
    
      /**
       * 更改子菜单标识符数组回调方法
       */
      function updateExpandKeys(keys: string[]): void {
        expandKeys.value = keys
      }
    
      /**
       * 选中的菜单发生改变
       */
      function updateValue(key: string): void {
        currentMenu.value = key
      }
    
      return {
        menuOptions,
        expandKeys,
        updateExpandKeys,
        currentMenu,
        updateValue
      } as UserMenu
    }

将树状菜单结构应用于菜单组件

生成菜单数据后,应用于 NaiveUI 的 Menu 组件非常简单:

<script setup lang="ts">
import { useMenu } from "@/composables/useMenu";

const { menuOptions, expandKeys, updateExpandKeys, currentMenu, updateValue } = useMenu();
</script>

<template>
    <n-menu
      :options="menuOptions"
      :expanded-keys="expandKeys"
      :on-update:expanded-keys="updateExpandKeys"
      :value="currentMenu"
      :on-update:value="updateValue"
    ></n-menu>
</tempalte>

菜单固定在左侧

NaiveUI 提供了一个布局(Layout)组件,可以非常方便地进行常用的页面布局。例如最常见的如下布局:

image-20230607143518371

左侧为菜单栏,右侧上部分为标题栏,中间是内容,切换路由时,会在该部分渲染,下面是网站的 Footer。

这种组件呢比较通用,所以通常会封装成为一个单独的组件文件。

封装布局组件

在 src 目录下,新建一个 layouts 文件夹,用于存放布局组件文件。

在 layouts 文件夹下新建一个 BasicLayout.vue 文件,这里先实现一个简单的布局,左边是菜单栏,右边是实际路由内容:

  • layouts/BasicLayout.vue

    <script setup lang="ts">
    import { useMenu } from "@/composables/useMenu";
    
    const { menuOptions, expandKeys, updateExpandKeys, currentMenu, updateValue } = useMenu();
    </script>
    
    <template>
      <n-layout has-sider>
        <n-layout-sider
          bordered
          collapse-mode="width"
          :width="220"
          :native-scrollbar="false"
        >
          <n-scrollbar>
            <n-menu
              :options="menuOptions"
              :expanded-keys="expandKeys"
              :on-update:expanded-keys="updateExpandKeys"
              :value="currentMenu"
              :on-update:value="updateValue"
            ></n-menu>
          </n-scrollbar>
        </n-layout-sider>
    
        <article flex-1 flex-col overflow-hidden>
          <section flex-1 overflow-hidden bg="#f5f6fb">
            <router-view v-slot="{ Component, route }">
              <template v-if="Component">
                <component :is="Component" :key="route.path" />
              </template>
            </router-view>
          </section>
        </article>
      </n-layout>
    </template>
    
    <style scoped></style>

修改路由信息

定义好通用布局组件后,需要将前面定义的 dashboardtable 页面的路由:

  • router/modules/dashboard.ts

    import type { RouteRecordRaw } from "vue-router";
    import BasicLayout from "@/layouts/BasicLayout.vue";
    
    const dashboardRoutes: RouteRecordRaw[] = [
      {
        path: "/",
        name: "Dashboard",
        component: BasicLayout,
        children: [
          {
            path: "/dashboard",
            name: "Dashboard",
            component: () => import("@/views/dashboard/index.vue"),
          },
        ],
      },
    ];
    
    export default dashboardRoutes;
  • router/modules/table.ts

    import type { RouteRecordRaw } from "vue-router";
    import BasicLayout from "@/layouts/BasicLayout.vue";
    
    const tableRoutes: RouteRecordRaw[] = [
      {
        path: "/",
        name: "Table",
        component: BasicLayout,
        children: [
          {
            path: "/table",
            name: "Table",
            component: () => import("@/views/table/index.vue"),
          },
        ],
      },
    ];
    
    export default tableRoutes;

这样子配置布局组件就能生效,是因为 vue-router 的嵌套路由功能所支持,在渲染时,是根据匹配到的组件,一级一级来渲染的。

具体可以查看文档 嵌套路由 | Vue Router (vuejs.org)

测试效果

完成后呢,先来简单测试下,效果如下:

image-20230607143529522

非常简陋,但已初具雏形。同时发现样式很奇怪,似乎全都挤在了中间。通过浏览器样式面板中查看,原来 vite 默认生成的 vue 项目,会在 style.css 中添加如下一个 css 配置:

  • style.css
#app {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

所以造成了全部居中的现象,OK,了解了原因,直接把这一整个文件删掉,后面根据我们的需求,再进行样式调整。

删掉后,同时在 main.ts 中,把 import './style.css' 这一行也删掉,重新查看页面,看起来正常多了。

image-20230607143536545

完善菜单

在上面生成的菜单中,有一个问题,迫切的需要解决下。那就是不能够隐藏一些菜单,例如 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);
  }
});

重新刷新页面,效果如下:

image-20230607143544323

设置点击菜单时切换路由

目前还没有实现点击相应菜单时,切换不同的页面。要想实现该效果,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 子菜单,发现并没有跳转过来,这是因为

dashboardtable 路由配置中,因为只有一个页面,父子路由名称一样,只匹配到了父路由。这里暂时把父节点的 name 属性删掉,重新测试,可以正常跳转啦:

image-20230607143553434

但父级菜单都变成空白啦,这个有两种方式,可以通过修改名称,或者配置当只有一个子菜单时,不显示父菜单来解决。下面我们就实现一下第二种方式。

当只有一个子菜单时不显示父菜单

这里同样还是修改生成菜单数据的地方 —— 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;
};

刷新页面,已经实现效果啦:

image-20230607143602225

添加菜单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;

刷新页面:

image-20230607143612313

这里需要注意哈,路由配置中是 ts 文件,不能够自动导入,所以需要手动导入需要依赖的图标组件。

解决了上面一系列问题后,基本看起来就像一个正常的菜单了,但样式还有一些问题,打开浏览器的「开发者工具」,可以看到高度并没有撑满浏览器,后面我们会解决下这个问题。

image-20230607143619541

使用 TS 来重新定义路由的 Meta

在前面的编码过程中,我们在路由配置的元数据中,添加了两个属性:hiddenicon,当用到这两个属性时,没有任何提示,其实与 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-routerRouteRecordRaw 类型的地方,修改为我们刚定义的 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;

结语

这一篇,从如何使用 vue-router,及其各种场景的配置,到实现了一个左侧菜单栏和其基本的功能,下一篇让我们来针对菜单栏和内容栏进行一定的美化,让项目的样式变得更加美观一些。

上一篇:
手摸手创建一个 Vue3 + Ts 项目(三) —— 使用 UnoCSS 来调整布局样式
下一篇:
手摸手创建一个 Vue3 + Ts 项目(一) —— 初始化项目