Vue3+Ts项目(Naive UI组件)——创建有图标可伸缩的左边菜单栏
2023-12-14 14:38:09
前言:在我不懈的摸索中终于实现了。我主要是总结另一位博主的创建过程,就是他标题取的,感觉有被白嫖到(手摸手创建…),哈哈哈哈哈哈,后面会附上原博主地址。以及我个人的总结理解。
安装、配置vue-router
1、安装
npm install vue-router
2、main.ts配置
// router
import { 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-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
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-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>
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-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/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%;
}
文章来源:https://blog.csdn.net/weixin_45594156/article/details/134990652
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!