npm install vue-router
// router
import { useRouter } from '@/router'
useRouter(app)
// 要放在app.mount('#app')之前
app.mount('#app')
<script setup lang="ts">
</script>
<template>
<router-view></router-view>
</template>
<style scoped>
</style>
后面都请无脑复制,因为我已经把原博主的过程都走了一遍。虽然他有提供项目git地址,但是很多地方不一样。(原文有提供处理没设置路径404页面)
在 src 目录下,新建 views 目录,用于存放页面文件(我们创建三个页面)
<script setup lang="ts">
</script>
<template>
<div v-for="i of 10">
<h3>Dashboard {{ i }}</h3>
<router-link to="/table">Go to Table</router-link>
</div>
</template>
<style scoped>
</style>
<script setup lang="ts">
</script>
<template>
<div>测试页面</div>
</template>
<style scoped>
</style>
<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 文件夹,区分各模块不同的路由配置文件(接下来我们要创建6个ts文件)页面会因为找不到一些依赖和文件而报红,咱先不管,后面再处理
import type { RouteRecord } from '@/router/type'
import BasicLayout from "@/layouts/BasicLayout.vue"
import { CalendarSettings } from '@vicons/carbon'
const dashboardRoutes: RouteRecord[] = [
{
path: "/combination",
name:'combination',
component: BasicLayout,
meta:{
icon: CalendarSettings
},
children: [
{
path: "/combination/dashboard",
name: "dashboard",
component: () => import("@/views/dashboard/index.vue"),
},
{
path: "/combination/test",
name: "test",
component: () => import("@/views/dashboard/test.vue"),
},
],
},
]
export default dashboardRoutes
import type { RouteRecord } from '@/router/type'
const rootRoutes: RouteRecord[] = [
{
path: '/',
name: 'home',
redirect: '/combination/dashboard',
meta: {
hidden: true
},
}
]
export default rootRoutes
import type { RouteRecord } from '@/router/type'
import BasicLayout from "@/layouts/BasicLayout.vue";
import { CalendarTools } from '@vicons/carbon'
const tableRoutes: RouteRecord[] = [
{
path: "/tableCombination",
name:'tableCombination',
component: BasicLayout,
meta:{
icon: CalendarTools
},
children: [
{
path: "/tableCombination/table",
name: "table",
component: () => import("@/views/table/index.vue"),
},
],
},
]
export default tableRoutes
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)
}
/**
* 动态加载路由配置
*/
import type { RouteRecordRaw } from "vue-router";
const modules = import.meta.glob("./modules/**/*.ts", { eager: true });
const routes = Object.keys(modules).reduce(
(routes: RouteRecordRaw[], key: string) => {
// @ts-ignore
const module = modules[key].default
console.log('module===', module);
if (Array.isArray(module)) {
return [...routes, ...module]
} else {
return [...routes, ...module.routes]
}
}, [] as RouteRecordRaw[]
);
console.log('routes===>',routes);
export default routes
import type { RouteRecordRaw } from "vue-router"
import type { 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[]
}
这里需要创建一个菜单栏vue页面以及一个ts文件用来数据处理。我这个项目Naive UI组件是手动导入。所以会跟原文有些许不一样。原文没有可伸缩部分。
<script lang="ts">
import { useMenu } from "@/composables/useMenu";
import { ref ,defineComponent} from "vue";
import {NLayout, NLayoutSider, NScrollbar,NMenu, NCol, NSwitch} from 'naive-ui'
export default defineComponent({
components: {
NLayout,
NLayoutSider,
NScrollbar,
NMenu,
NCol,
NSwitch,
},
setup(){
let collapsed = ref(false)
const { menuOptions, expandKeys, updateExpandKeys, currentMenu, updateValue } = useMenu();
return{
menuOptions,
expandKeys,
updateExpandKeys,
currentMenu,
updateValue,
collapsed
}
}
})
</script>
<template>
<!-- <n-switch v-model:value="collapsed" /> -->
<n-layout has-sider>
<n-layout-sider
bordered
collapse-mode="width"
:width="240"
:collapsed-width="64"
:collapsed="collapsed"
show-trigger
@collapse="collapsed = true"
@expand="collapsed = false"
: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 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>
import type { Ref,Component } from "vue";
import { ref, watch, h } from "vue";
import type{ MenuOption } from "naive-ui";
import { NIcon } from "naive-ui";
import routes from "@/router/routes";
// import type { RouteRecordRaw } from "vue-router";
import type { RouteRecord } from '@/router/type'
import { useRoute, RouterLink } from "vue-router";
const renderIcon = (icon: Component) => {
return () => h(NIcon, null, { default: () => h(icon) })
}
export interface UserMenu {
/**
* 菜单选项
*/
menuOptions: Ref<MenuOption[]>;
/**
* 展开的子菜单标识符数组
*/
expandKeys: Ref<string[]>;
/**
* 更改子菜单标识符数组回调方法
*/
updateExpandKeys: (keys: string[]) => void;
/**
* 当前选中的菜单
*/
currentMenu: Ref<string>;
/**
* 修改选中菜单时的回调方法
*/
updateValue: (key: string) => void;
}
/**
* 判断路由是否只有一个子路由
* @param route 路由
* @returns 如果该路由只有一个子路由,则返回 true;否则返回 false
*/
const isSingleChildren = (route: RouteRecord): boolean => {
// return route?.children?.length === 1;
//看需求需要一个children时是否展示上级name。false:展示父级(后期可以根据meta中字段判断某一菜单是否展示父级)
return false;
};
/**
* 过滤路由配置中需要在菜单中隐藏的路由
* @param routes 路由列表
* @returns 路由列表
*/
const filterHiddenRouter = (routes: RouteRecord[]): RouteRecord[] => {
return routes.filter((item: RouteRecord) => {
return !item.meta?.hidden;
});
};
/**
* 将路由信息转换为菜单信息
* @param route 路由信息
* @returns 菜单信息
*/
const getMenuOption = (route: RouteRecord[]): MenuOption | undefined => {
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;
};
const getMenuOptions = (routes: RouteRecord[]): MenuOption[] => {
let menuOptions: MenuOption[] = [];
filterHiddenRouter(routes).forEach((route: RouteRecord) => {
// @ts-ignore
const menuOption = getMenuOption(route);
if (menuOption) {
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
}
个人理解vicons是个图标库,你想使用谁的图标引入谁的
npm i -D @vicons/fluent
npm i -D @vicons/ionicons4
npm i -D @vicons/ionicons5
npm i -D @vicons/antd
npm i -D @vicons/material
npm i -D @vicons/fa
npm i -D @vicons/tabler
npm i -D @vicons/carbon
我这边是简单处理:创建了一个css文件。import '@/styles/index.css'
引入main.ts
我看很多推荐使用Tailwind CSS,我还需要再研究研究。
src\styles\index.css
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
}
#app {
width: 100%;
height: 100%;
}
.n-layout {
height: 100%;
width: 100%;
}
原地址:手摸手创建一个 Vue + Ts 项目(二) —— 实现一个左侧菜单栏