本文主要是介绍Vue3+Ts项目(Naive UI组件)——创建有图标可伸缩的左边菜单栏,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 安装、配置vue-router
- 1、安装
- 2、main.ts配置
- 3、在App.vue中,渲染路由配置到的组件
- 创建测试路径页面
- 1、src\views\dashboard\index.vue
- 2、src\views\dashboard\test.vue
- 3、src\views\table\index.vue
- 配置页面路由
- 1、src\router\modules\dashboard.ts
- 2、src\router\modules\index.ts (主要用于测试不想展示的菜单路径隐藏)
- 3、src\router\modules\table.ts
- 4、src\router\index.ts 启用 vue-router
- 5、src\router\routes.ts 动态获取所有路由配置
- 6、src\router\type.ts
- 绘制有图标可伸缩的菜单栏(重要部分)
- 1、src\layouts\BasicLayout.vue
- 2、src\composables\useMenu.ts 主要是这个文件
- 处理找不到@vicons/carbon报红以及菜单栏不是整个画面样式
- 1、naive-ui 推荐使用 [xicons](https://xicons.org/#/) 作为图标库。
- 2、菜单栏不是整个画面样式
前言:在我不懈的摸索中终于实现了。我主要是总结另一位博主的创建过程,就是他标题取的,感觉有被白嫖到(手摸手创建…),哈哈哈哈哈哈,后面会附上原博主地址。以及我个人的总结理解。
安装、配置vue-router
1、安装
npm install vue-router
2、main.ts配置
// routerimport { useRouter } from '@/router'useRouter(app)
// 要放在app.mount('#app')之前app.mount('#app')
3、在App.vue中,渲染路由配置到的组件
<script setup lang="ts">
</script><template><router-view></router-view>
</template><style scoped>
</style>
创建测试路径页面
后面都请无脑复制,因为我已经把原博主的过程都走了一遍。虽然他有提供项目git地址,但是很多地方不一样。(原文有提供处理没设置路径404页面)
在 src 目录下,新建 views 目录,用于存放页面文件(我们创建三个页面)
1、src\views\dashboard\index.vue
<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>
2、src\views\dashboard\test.vue
<script setup lang="ts">
</script><template><div>测试页面</div>
</template><style scoped></style>
3、src\views\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 文件夹,区分各模块不同的路由配置文件(接下来我们要创建6个ts文件)页面会因为找不到一些依赖和文件而报红,咱先不管,后面再处理
1、src\router\modules\dashboard.ts
import type { RouteRecord } from '@/router/type'
import BasicLayout from "@/layouts/BasicLayout.vue"import { CalendarSettings } from '@vicons/carbon'const dashboardRoutes: RouteRecord[] = [{path: "/",name:'combination',component: BasicLayout,meta:{icon: CalendarSettings},children: [{path: "/dashboard",name: "dashboard",component: () => import("@/views/dashboard/index.vue"),},{path: "/test",name: "test",component: () => import("@/views/dashboard/test.vue"),},],},
]export default dashboardRoutes
2、src\router\modules\index.ts (主要用于测试不想展示的菜单路径隐藏)
import type { RouteRecord } from '@/router/type'const rootRoutes: RouteRecord[] = [{path: '/',name: 'home',redirect: '/dashboard',meta: {hidden: true},}
]export default rootRoutes
3、src\router\modules\table.ts
import type { RouteRecord } from '@/router/type'
import BasicLayout from "@/layouts/BasicLayout.vue";
import { CalendarTools } from '@vicons/carbon' const tableRoutes: RouteRecord[] = [{path: "/",name:'tableCombination',component: BasicLayout,meta:{icon: CalendarTools},children: [{path: "/table",name: "table",component: () => import("@/views/table/index.vue"),},],},
]export default tableRoutes
4、src\router\index.ts 启用 vue-router
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)
}
5、src\router\routes.ts 动态获取所有路由配置
/*** 动态加载路由配置*/
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-ignoreconst module = modules[key].defaultconsole.log('module===', module);if (Array.isArray(module)) {return [...routes, ...module]} else {return [...routes, ...module.routes]}}, [] as RouteRecordRaw[]
);
console.log('routes===>',routes);export default routes
6、src\router\type.ts
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组件是手动导入。所以会跟原文有些许不一样。原文没有可伸缩部分。
1、src\layouts\BasicLayout.vue
<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-siderborderedcollapse-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>
2、src\composables\useMenu.ts 主要是这个文件
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-ignoreconst 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/carbon报红以及菜单栏不是整个画面样式
1、naive-ui 推荐使用 xicons 作为图标库。
个人理解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
2、菜单栏不是整个画面样式
我这边是简单处理:创建了一个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 项目(二) —— 实现一个左侧菜单栏
这篇关于Vue3+Ts项目(Naive UI组件)——创建有图标可伸缩的左边菜单栏的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!