Vue3+Ts项目(Naive UI组件)——创建有图标可伸缩的左边菜单栏

news/2024/7/10 3:17:10 标签: ui, vue, typescript

文章目录

      • 安装、配置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、菜单栏不是整个画面样式

前言:在我不懈的摸索中终于实现了。我主要是总结另一位博主的创建过程,就是他标题取的,感觉有被白嫖到(手摸手创建…),哈哈哈哈哈哈,后面会附上原博主地址。以及我个人的总结理解。

在这里插入图片描述

vuerouter_6">安装、配置vue-router

1、安装
npm install vue-router
2、main.ts配置
typescript">// router
  import { useRouter } from '@/router'
  useRouter(app)
// 要放在app.mount('#app')之前
  app.mount('#app')
vue_20">3、在App.vue中,渲染路由配置到的组件
typescript"><script setup lang="ts">
</script>

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

<style scoped>
</style>

创建测试路径页面

后面都请无脑复制,因为我已经把原博主的过程都走了一遍。虽然他有提供项目git地址,但是很多地方不一样。(原文有提供处理没设置路径404页面)

在 src 目录下,新建 views 目录,用于存放页面文件(我们创建三个页面

vue_38">1、src\views\dashboard\index.vue
typescript"><script setup lang="ts">
</script>

<template>
	<div v-for="i of 10">
		<h3>Dashboard {{ i }}</h3>
		<router-link to="/tableCombination/table">Go to Table</router-link>
	</div>
</template>

<style scoped>

</style>
vue_56">2、src\views\dashboard\test.vue
typescript"><script setup lang="ts">
</script>

<template>
	<div>测试页面</div>
</template>

<style scoped>

</style>
vue_71">3、src\views\table\index.vue
typescript">  <script setup lang="ts">
  
  </script>
  
  <template>
   <div>
     <h3>Table</h3>
    <router-link to="/combination/dashboard">Go to Dashboard</router-link>
   </div>
  </template>
  
  <style scoped>
  
  </style>

配置页面路由

在 src 目录下,新建 router 文件夹,用于存放路由配置文件。首先在其下创建一个 modules 文件夹,区分各模块不同的路由配置文件(接下来我们要创建6个ts文件)页面会因为找不到一些依赖和文件而报红,咱先不管,后面再处理

1、src\router\modules\dashboard.ts
typescript">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
2、src\router\modules\index.ts (主要用于测试不想展示的菜单路径隐藏)
typescript">import type { RouteRecord } from '@/router/type'
  
const rootRoutes: RouteRecord[] = [
  {
    path: '/',
    name: 'home',
    redirect: '/combination/dashboard',
    meta: {
        hidden: true
      },
  }
]

export default rootRoutes
3、src\router\modules\table.ts
typescript">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
vuerouter_168">4、src\router\index.ts 启用 vue-router
typescript">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 动态获取所有路由配置
typescript">/**
 * 动态加载路由配置
 */
import type { RouteRecordRaw } from "vue-router";

const modules = import.meta.glob("./modules/**/*.ts", { eager: true });

const routes = Object.keys(modules).reduce(
  (routes: RouteRecordRaw[], key: uiltin">string) => {
    // @ts-ignore
    const module = modules[key].default
    uiltin">console.log('module===', module);
    
    if (uiltin">Array.isArray(module)) {
      return [...routes, ...module]
    } else {
      return [...routes, ...module.routes]
    }
  }, [] as RouteRecordRaw[]
);
uiltin">console.log('routes===>',routes);

export default routes

6、src\router\type.ts
typescript">import type { RouteRecordRaw } from "vue-router"
import type { Component } from 'vue'

interface RouteRecordMeta {
  hidden?: uiltin">boolean,
  icon?: Component
}

// @ts-expect-error
export interface RouteRecord extends Omit<RouteRecordRaw, 'meta'> {
  name?: uiltin">string,
  meta?: RouteRecordMeta,
  children?: RouteRecord[]
}

绘制有图标可伸缩的菜单栏(重要部分)

这里需要创建一个菜单栏vue页面以及一个ts文件用来数据处理。我这个项目Naive UI组件是手动导入。所以会跟原文有些许不一样。原文没有可伸缩部分。

vue_232">1、src\layouts\BasicLayout.vue
typescript"><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 主要是这个文件
typescript">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<uiltin">string[]>;
  /**
   * 更改子菜单标识符数组回调方法
   */
  updateExpandKeys: (keys: uiltin">string[]) => void;
  /**
   * 当前选中的菜单
   */
  currentMenu: Ref<uiltin">string>;
  /**
   * 修改选中菜单时的回调方法
   */
  updateValue: (key: uiltin">string) => void;
}

/**
 * 判断路由是否只有一个子路由
 * @param route  路由
 * @returns  如果该路由只有一个子路由,则返回 true;否则返回 false
 */
const isSingleChildren = (route: RouteRecord): uiltin">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 && uiltin">Array.isArray(routeInfo.children)) {
        return routeInfo.name;
      } else {
        return h(
          RouterLink,
          { to: { name: routeInfo.name } },
          { default: () => routeInfo.name }
        );
      }
    },
    key: routeInfo.name as uiltin">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<uiltin">string[]> = ref<uiltin">string[]>([]);

  /**
   * 当前菜单
   */
  const currentMenu: Ref<uiltin">string> = ref<uiltin">string>("");

  const route = useRoute();
  /**
   * 监听路由变化
   */
  watch(
    () => route.path,
    () => {
      routeChanged();
    },
    { immediate: true }
  );

  /**
   * 判断路由是否包含在菜单列表中
   *
   * @param routeName 路由名称
   * @param menuList  菜单列表
   * @returns 如果包含则返回 true;否则返回 false
   */
  function menuContains(routeName: uiltin">string, menuList: MenuOption[]): uiltin">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 uiltin">string, menus))
      .map((it) => it.name as uiltin">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: uiltin">string[]): void {
    expandKeys.value = keys
  }

  /**
   * 选中的菜单发生改变
   */
  function updateValue(key: uiltin">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 项目(二) —— 实现一个左侧菜单栏


http://www.niftyadmin.cn/n/5264565.html

相关文章

【Qt5】QMouseEvent的globalPos

2023年12月14日&#xff0c;周四下午 QMouseEvent的globalPos()函数是用于获取鼠标事件发生时的全局坐标。它返回一个QPoint对象&#xff0c;表示鼠标事件的全局位置。 全局坐标是相对于整个屏幕的坐标系&#xff0c;而不是相对于应用程序窗口或控件的坐标系。它可以用来确定鼠…

Linux----1、初始Linux

# 初识Linux # 一、计算机资源介绍 计算机资源分为2 部分&#xff1a;硬件资源、软件资源 硬件&#xff1a; 一般硬件是指计算机的物理组成&#xff0c;由真实&#xff08;看得见&#xff0c;摸得着&#xff09;的设备组成的 软件&#xff1a; 软件一般是指应用程序&#x…

<JavaEE> 锁进阶 -- synchronized 的锁优化

目录 一、如何形容 synchronized 锁 二、锁升级 2.1 偏向锁 2.2 轻量级锁 2.3 重量级锁 三、锁消除 四、锁粗化 一、如何形容 synchronized 锁 synchronized 锁是一个内部优化非常好的锁&#xff0c;大部分情况下这个锁都是适用的。在初始阶段 synchronized 是一个乐观…

中小企业业财融合策略(2):财务赋能,如何支持业务经营?

上一文《中小企业业财融合(1):把财务从琐碎的凭证处理、核算中解放出来!》&#xff0c;我们提出了财务升级的背景以及必要性&#xff0c;本篇我们就财务赋能&#xff0c;如何支持业务经营&#xff1f;提出一些简要的看法&#xff0c;希望对大家有所帮助。 一、财务赋能&#x…

云原生之深入解析Kubernetes中服务的性能

一、Pyroscope 简介 ① 什么是 Pyroscope&#xff1f; 开发人员通常需要查看生产应用程序中的性能瓶颈以确定问题的原因&#xff0c;为此通常需要可以通过日志和代码工具收集的信息。不幸的是&#xff0c;这种方法通常很耗时&#xff0c;并且不能提供有关潜在问题的足够详细信…

Netty—NIO万字详解

文章目录 NIO基本介绍同步、异步、阻塞、非阻塞IO的分类NIO 和 BIO 的比较NIO 三大核心原理示意图NIO的多路复用说明 核心一&#xff1a;缓存区 (Buffer)Buffer类及其子类Buffer缓冲区的分类MappedByteBuffer类说明&#xff1a; 核心二&#xff1a;通道 (Channel)Channel类及其…

一个简单的cmake模板(C++)

链接&#xff1a;小黑屋1024 / Python GitCode #对cmake版本的要求&#xff0c;此处不低于3.16 cmake_minimum_required(VERSION 3.16)#项目名称&#xff1a;此处为test project(test)#设置编译生成产物输出路径 ##可执行文件exe SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURC…

AD20-Excel创建IC类元件库

目录 准备模板AD操作 准备模板 AD操作 结果生成如下&#xff1a; over&#xff01;&#xff01;&#xff01;