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

news2024/11/16 2:44:18

文章目录

      • 安装、配置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配置
// 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%;
}

原地址:手摸手创建一个 Vue + Ts 项目(二) —— 实现一个左侧菜单栏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1310351.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年【北京市安全员-B证】考试及北京市安全员-B证复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 北京市安全员-B证考试参考答案及北京市安全员-B证考试试题解析是安全生产模拟考试一点通题库老师及北京市安全员-B证操作证已考过的学员汇总&#xff0c;相对有效帮助北京市安全员-B证复审考试学员顺利通过考试。 1、…

PyCharm连接远程服务器上Docker容器,使用远程服务器的python intercepter解释器和GPU资源 [本地调试深度学习代码]

概述 在编写常规深度学习代码时&#xff0c;总是需要使用服务器上的GPU资源&#xff0c;所以一般要写完代码&#xff0c;放到服务器&#xff0c;然后使用GPU运行。但是由于之前的习惯&#xff0c;总想本地调试一下或者本地直接跑测试结果&#xff0c;再放到服务器去跑。 网上…

格式工厂功能详解!!

格式工厂&#xff08;Format Factory&#xff09;是由上海格诗网络科技有限公司创立于2008年2月&#xff0c;是面向全球用户的互联网软件。 下载地址https://www.onlinedown.net/soft/64717.htm&#xff1a; 该软件的主打产品“格式工厂”发展以来&#xff0c;已经成为全球领…

华为云之云监控服务CES基础入门实践

华为云之云监控服务CES基础入门实践 一、云监控服务CES介绍二、本次实践介绍1.本次实践介绍2.本次实践目录 三、购买华为云ECS云服务器1.进入ECS云服务器控制台2.购买ECS云服务器3.检查ECS弹性云主机的状态 四、监控ECS弹性云主机性能1.远程连接ECS2.进入云监控服务CES页面3.使…

开源协作知识库软件AFFINE如何本地部署并结合内网穿透实现远程访问——“cpolar内网穿透”

前言 本篇文章讲解Notion开源平替全能知识库工具AFFINE如何本地部署&#xff0c;并实现公网远程访问。AFFiNE 是一个全新的开源项目&#xff0c;旨在克服 Notion 和 Miro 在安全和隐私方面的一些局限性。它的设计目标是帮助用户将会议记录、待办事项、文档中的目标、视频会议白…

Leetcode刷题笔记题解(C++):328. 奇偶链表

思路&#xff1a;遍历链表生成奇链表和偶链表&#xff0c;然后拼接两个链表生成新的链表。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), ne…

策略模式-大道至简

文章目录 摆个类图本质 摆个类图 本质 定义一个标准策略接口Strategy&#xff0c;这个接口中声明一个场景下应该使用的策略&#xff08;执行的逻辑&#xff09;。随后具体的执行器&#xff08;具体的场景&#xff09;应当实现这个接口&#xff0c;并实现自己的策略执行逻辑。为…

高通平台开发系列讲解(外设篇)高通平台EMMC适配说明

文章目录 一、EMMC的内部框图说明二、EMMC 设备树配置三、EMMC 内核配置四、EMMC 源码沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要图解高通平台 EMMC适配说明。 eMMC(嵌入式多媒体卡)是一种集成了闪存存储器和控制器的存储芯片,通常用于嵌入式设备中,…

实时输电线路故障监测系统:精准定位、快速修复

随着电力系统的不断发展&#xff0c;输电线路的故障检测和维修变得越来越重要。为了提高故障检测的效率和准确性&#xff0c;恒峰智慧科技设计的实时输电线路故障监测系统应运而生。本文将介绍一种采用分布式行波测量技术的输电线路故障定位及隐患监测装置&#xff0c;以帮助您…

6.3 C++11 原子操作与原子类型

一、原子类型 1.多线程下的问题 在C中&#xff0c;一个全局数据在多个线程中被同时使用时&#xff0c;如果不加任何处理&#xff0c;则会出现数据同步的问题。 #include <iostream> #include <thread> #include <chrono> long val 0;void test() {for (i…

微信小程序单图上传和多图上传

图片上传主要用到 1、wx.chooseImage(Object object) 从本地相册选择图片或使用相机拍照。 参数 Object object 属性类型默认值必填说明countnumber9否最多可以选择的图片张数sizeTypeArray.<string>[original, compressed]否所选的图片的尺寸sourceTypeArray.<s…

docker-ubuntu中基于keepalived+niginx模拟主从热备完整过程

一、环境准备 &#x1f517;在Ubuntu中安装docker 二、主机 1、环境搭建 1.1 镜像拉取 docker pull ubuntu:16.041.2 创建网桥 docker network create -dbridge --subnet192.168.126.0/24 br11.3 启动容器 docker run -it --name ubuntu-1 --privileged -v /home/vac/l…

运行和部署若依分离版前端

一、运行 一、用vscode打开 二、安装依赖 # 建议不要直接使用 cnpm 安装依赖&#xff0c;会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 npm install --registryhttps://registry.npmmirror.com# 启动服务 npm run dev浏览器访问 http://localhost:80二、部…

美颜技术讲解:视频美颜SDK的开发与集成

如今&#xff0c;美颜技术的应用愈发成为吸引用户的一项重要功能。本文将深入探讨视频美颜SDK的开发与集成&#xff0c;揭示其背后的技术原理和实现步骤。 一、美颜技术的背后 美颜技术并非仅仅是简单的滤镜效果&#xff0c;而是一项涉及复杂图像处理和算法的技术。在视频美颜…

(第31天)RHEL 7 安装 Oracle 11GR2 RAC 数据库

前言 Oracle RAC是什么? Oracle Real Application Clusters (RAC) 允许客户跨多台服务器运行单个 Oracle 数据库,以最大限度地提高可用性并实现水平可扩展性,同时访问共享存储。连接到 Oracle RAC 实例的用户会话可以在中断期间进行故障转移并安全地重放更改,而无需对最终…

12.7 GNSS学习笔记记录

1.接收机坐标初值一般采用单点定位获得 2.定位精度主要取决于以下两个因素&#xff1a;一是所测卫星在空间的几何分布&#xff0c;通常称为卫星的几何分布图形&#xff1b;二是观测量的精度。其中&#xff0c;观测量的精度与卫星轨道和钟差、大气传播延迟、接收机噪声等因素有…

2024 年,新程序员如何与AI共赢!!

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

JAVA:注册表窗口的实现

目录 题目要求&#xff1a; 思路大意&#xff1a; 窗体的实现&#xff1a; 窗口A&#xff1a; 窗口B&#xff1a; 窗体之间的构思&#xff1a; 关键代码的实现&#xff1a; 窗口A&#xff1a; 封装列表&#xff1a; 窗口B&#xff1a; 题目要求&#xff1a; 使用…

Web漏洞分析-文件解析及上传(下)

随着互联网的迅速发展&#xff0c;网络安全问题变得日益复杂&#xff0c;而文件解析及上传漏洞成为攻击者们频繁攻击的热点之一。本文将深入研究文件解析及上传漏洞&#xff0c;通过对文件上传、Web容器IIS、命令执行、Nginx文件解析漏洞以及公猫任意文件上传等方面的细致分析&…