【Vue3+Vite+TS】8.0 组件六:导航菜单
必备UI组件
将用到的组件:
Menu 菜单
组件设计
新建src\components\baseline\menu\src\index.vue
<template>
<div>Menu</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { MenuItem } from './types'
const props = defineProps({
//说明:
data: {
required: true,
type: Array as PropType<MenuItem[]>,
},
})
console.log('data:', props.data)
</script>
<style lang="scss" scoped></style>
新建src\components\baseline\menu\src\types.ts
export interface MenuItem {
//导航菜单的图标
icon?: string
//导航菜单的名字
name: string
//导航菜单的标识
code: string
//子菜单
children?: MenuItem[]
}
新建src\components\baseline\menu\index.ts
import { App } from 'vue'
import Menu from './src/index.vue'
export { Menu }
//组件可通过use的形式使用
export default {
Menu,
install(app: App) {
app.component('bs-menu', Menu)
},
}
修改src\components\baseline\index.ts
import { App } from 'vue'
import ChooseArea from './chooseArea'
import ChooseIcon from './chooseIcon'
import Container from './container'
import Trend from './trend'
import Notification from './notification'
import List from './list'
import Menu from './menu'
const components = [
ChooseArea,
ChooseIcon,
Container,
Trend,
Notification,
List,
Menu,
]
export { ChooseArea, ChooseIcon, Container, Trend, Notification, List, Menu }
//组件可通过use的形式使用
export default {
install(app: App) {
components.map(item => {
app.use(item)
})
},
ChooseArea,
ChooseIcon,
Container,
Trend,
Notification,
List,
Menu,
}
修改src\router\index.ts,新增路由参数
......
{
path: '/menu',
component: () =>
import('../views/baseline/menu/index.vue'),
},
......
新建src\views\baseline\menu\index.vue
<template>
<div><bs-menu :data="data"></bs-menu></div>
</template>
<script lang="ts" setup>
let data = [
{ name: '首页', code: '1', icon: 'el-icon-document' },
{ name: '图标选择器', code: '2', icon: 'el-icon-document' },
{
name: '省市区选择组件',
code: '3',
icon: 'el-icon-document',
children: [
{ name: '省市选择组件', code: '3-1', icon: 'el-icon-document' },
{ name: '省市区村选择组件', code: '3-2', icon: 'el-icon-document' },
],
},
]
</script>
<style lang="scss" scoped></style>
如下,可见数据已经传达到基础组件:
完善组件
首先实现一级菜单。
修改src\components\baseline\menu\src\index.vue
<template>
<div>
<el-menu>
<template v-for="(item, index) in data" :key="index">
<div>
<el-menu-item
v-if="!item.children || !item.children.length"
:index="item.code"
>
<component v-if="item.icon" :is="item.icon"></component>
<span>{{ item.name }}</span>
</el-menu-item>
</div>
</template>
</el-menu>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { MenuItem } from './types'
const props = defineProps({
//说明:
data: {
required: true,
type: Array as PropType<MenuItem[]>,
},
})
console.log('data:', props.data)
</script>
<style lang="scss" scoped></style>
修改src\views\baseline\menu\index.vue
<template>
<div style="width: 2rem"><bs-menu :data="data"></bs-menu></div>
</template>
<script lang="ts" setup>
let data = [
{ name: '首页', code: '1', icon: 'el-icon-document' },
{ name: '图标选择器', code: '2', icon: 'el-icon-document' },
{
name: '省市区选择组件',
code: '3',
icon: 'el-icon-document',
children: [
{ name: '省市选择组件', code: '3-1', icon: 'el-icon-document' },
{ name: '省市区村选择组件', code: '3-2', icon: 'el-icon-document' },
],
},
]
</script>
<style lang="scss" scoped></style>
效果如下:
接下来实现多级菜单
修改src\components\baseline\menu\src\types.ts
export interface MenuItem {
//导航菜单的图标
icon?: string
//导航菜单的名字
name: string
//导航菜单的标识
index: string
//子菜单
children?: MenuItem[]
}
修改src\components\baseline\menu\src\index.vue
<template>
<div>
<el-menu
:default-active="defaultActive"
:router="router"
v-bind="$attrs"
>
<template v-for="(item, index) in data" :key="index">
<div>
<!-- 一级无二级菜单的菜单栏 -->
<el-menu-item
v-if="!item.children || !item.children.length"
:index="item.index"
>
<component v-if="item.icon" :is="item.icon"></component>
<span>{{ item.name }}</span>
</el-menu-item>
<el-sub-menu
v-if="item.children && item.children.length"
:index="item.index"
>
<template #title>
<component
v-if="item.icon"
:is="item.icon"
></component>
<span>{{ item.name }}</span>
</template>
<!-- 二级菜单栏 -->
<el-menu-item
v-for="(item2, index2) in item.children"
:index="item2.index"
>
<component
v-if="item2.icon"
:is="item2.icon"
></component>
<span>{{ item2.name }}</span>
</el-menu-item>
</el-sub-menu>
</div>
</template>
</el-menu>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue'
import { MenuItem } from './types'
const props = defineProps({
//说明:
data: {
required: true,
type: Array as PropType<MenuItem[]>,
},
// 默认选中的菜单
defaultActive: {
type: String,
default: '',
},
//是否是路由模式 router,
//是否启用 vue-router 模式
//启用该模式会在激活导航时以 index 作为 path 进行路由跳转
router: {
type: Boolean,
default: false,
},
})
// console.log('data:', props.data)
</script>
<style lang="scss" scoped>
svg {
margin-right: 0.04rem;
}
</style>
修改src\views\baseline\menu\index.vue
<template>
<div style="width: 2rem">
<bs-menu :data="data" defaultActive="3-2"></bs-menu>
</div>
</template>
<script lang="ts" setup>
let data = [
{ name: '首页', index: '1', icon: 'el-icon-document' },
{ name: '图标选择器', index: '2', icon: 'el-icon-document' },
{
name: '行政区域选择组件',
index: '3',
icon: 'el-icon-document',
children: [
{ name: '省市选择组件', index: '3-1', icon: 'el-icon-document' },
{ name: '省市区选择组件', index: '3-2', icon: 'el-icon-document' },
{
name: '省市区村选择组件',
index: '3-3',
icon: 'el-icon-document',
},
],
},
0,
]
</script>
<style lang="scss" scoped></style>
实现效果如下:
v-bind="$attrs"
:接受父组件传入的数据和方法,并排除在组件的props中响应的参数。
具体可以参考: vue中使用v-bind="$attrs"和v-on="$listeners"进行多层组件监听
TSX实现无限层级的导航菜单
首先需要安装插件。
npm i -D @vitejs/plugin-vue-jsx
修改vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
//******************* Element自动导入 *******************
//目前语言包存在报错,无法自动导出打包,暂时注释
// import AutoImport from 'unplugin-auto-import/vite'
// import Components from 'unplugin-vue-components/vite'
// import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
//******************* Element自动导入 *******************
//******************* rollup 打包体积分析插件可视化工具 *******************
import { visualizer } from 'rollup-plugin-visualizer'
//******************* rollup 打包体积分析插件可视化工具 *******************
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
vueJsx(),
//******************* Element自动导入 *******************
// AutoImport({
// resolvers: [ElementPlusResolver()],
// }),
// Components({
// resolvers: [ElementPlusResolver()],
// }),
//******************* Element自动导入 *******************
//******************* 打包插件可视化工具 *******************
visualizer(),
//******************* 打包插件可视化工具 *******************
],
resolve: {
alias: {
'@': '/src',
'@style': '/src/style',
'@com': '/src/components',
'@baseline': '/src/components/baseline',
'@business': '/src/components/business',
},
},
server: {
port: 8080,
},
})
这里也顺便提一下tsconfig.json的配置:
{
// 指定要编译的路径列表
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"compilerOptions": {
// target用于指定编译之后的版本目录
"target": "esnext",
"useDefineForClassFields": true,
//module用来指定要使用的模板标准
"module": "esnext",
// 用于选择模块解析策略,有"node"和"classic"两种类型
"moduleResolution": "node",
"strict": true,
// 指定jsx代码用于的开发环境:'preserve','react-native',or 'react
"jsx": "preserve",
// 指定是否将map文件内容和js文件编译在一个同一个js文件中
// 如果设为true,则map的内容会以//#soureMappingURL=开头,然后接base64字符串的形式插入在js文件底部
"sourceMap": true,
"skipLibCheck": true,
"resolveJsonModule": true,
//通过导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
// 指定是否引入tslib里的复制工具函数,默认为false
"importHelpers": true,
// 指定是否将每个文件作为单独的模块,默认为true,他不可以和declaration同时设定
"isolatedModules": true,
// removeComments用于指定是否将编译后的文件注释删掉,设为true的话即删除注释,默认为false
"removeComments": true,
// lib用于指定要包含在编译中的库文件
"lib": ["esnext", "dom"],
//指定全局组件类型:element:"element-plus/global"
"types": ["vite/client", "element-plus/global"],
// ++ 这里加上baseUrl 和 path即可 ++
// 用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响
"baseUrl": "./",
//用于设置模块名到基于baseUrl的路径映射
"paths": {
// 根据别名配置相关路径
"@/*": ["./src/*"],
"@style/*": ["./src/style/*"],
"@com/*": ["./src/components/*"],
"@baseline/*": ["./src/components/baseline/*"],
"@business/*": ["./src/components/business/*"]
}
//****************** 未配置选项 ******************
// 用来指定是否允许编译JS文件,默认false,即不编译JS文件
// "allowJs": false,
// 用来指定是否在编译的时候生成相的d.ts声明文件
// 如果设为true,编译每个ts文件之后会生成一个js文件和一个声明文件,但是declaration和allowJs不能同时设为true
// "declaration": true,
// 用来指定编译时是否生成.map文件
// "declarationMap": true,
// 用来指定输出文件夹,值为一个文件夹路径字符串,输出的文件都将放置在这个文件夹
// "outDir": "./",
// 用来指定输出文件夹,值为一个文件夹路径字符串,输出的文件都将放置在这个文件夹
// "outDir": "./",
// 用于指定输出文件合并为一个文件
//只有设置module的值为amd和system模块时才支持这个配置
// "outFile": "./",
// 是否编译构建引用项目
// "composite": true,
// 不生成编译文件
// "noEmit": true,
// 当target为"ES5"或"ES3"时,为"for-of" "spread"和"destructuring"中的迭代器提供完全支持
// "downlevelIteration": true,
// 用于指定是否启动所有类型检查,如果设为true这回同时开启下面这几个严格检查,默认为false
// "strict": true,
// 如果我们没有一些值设置明确类型,编译器会默认认为这个值为any类型,如果将noImplicitAny设为true,则如果没有设置明确的类型会报错,默认值为false
// "noImplicitAny": true,
// 当设为true时,null和undefined值不能赋值给非这两种类型的值,别的类型的值也不能赋给他们,除了any类型,还有个例外就是undefined可以赋值给void类型
// "strictNullChecks": true,
// 用来指定是否使用函数参数双向协变检查
// "strictFunctionTypes": true,
// 设为true后对bind、call和apply绑定的方法的参数的检测是严格检测
// "strictBindCallApply": true,
// 设为true后会检查类的非undefined属性是否已经在构造函数里初始化,如果要开启这项,需要同时开启strictNullChecks,默认为false
// "strictPropertyInitialization": true,
// 当this表达式的值为any类型的时候,生成一个错误
// "noImplicitThis": true,
// alwaysStrict指定始终以严格模式检查每个模块,并且在编译之后的JS文件中加入"use strict"字符串,用来告诉浏览器该JS为严格模式
// "alwaysStrict": true,
// 用于检查是否有定义了但是没有使用变量,对于这一点的检测,使用ESLint可以在你书写代码的时候做提示,你可以配合使用,他的默认值为false
// "noUnusedLocals": true,
// 用于检测是否在函数中没有使用的参数
// "noUnusedParameters": true,
// 用于检查函数是否有返回值,设为true后,如果函数没有返回值则会提示,默认为false
// "noImplicitReturns": true,
// 用于检查switch中是否有case没有使用break跳出switch,默认为false
// "noFallthroughCasesInSwitch": true,
// 可以指定一个路径列表,在构建时编译器会将这个路径中的内容都放到一个文件夹中
// "rootDirs": [],
// 用来指定声明文件或文件夹的路径列表,如果指定了此项,则只有在这里列出的声明文件才会被加载
// "typeRoots": [],
// types用于指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载
// "types": [],
// 用来指定允许从没有默认导出的模块中默认导入
// "allowSyntheticDefaultImports": true,
// 不把符号链接解析为真实路径,具体可以了解下webpack和node.js的symlink相关知识
// "preserveSymlinks": true,
// 用于指定调试器应该找到TypeScript文件而不是源文件的位置,这个值会被写进.map文件里
// "sourceRoot": "",
// 用于指定调试器找到映射文件而非生成文件的位置,指定map文件的根路径
// 该选项会影响.map文件中的sources属性
// "mapRoot": "",
// s用于指定是否进一步将ts文件的内容也包含到输出文件中
// "inlineSources": true,
// 用于指定是否启用实验性的装饰器特性
// "experimentalDecorators": true,
// 用于指定是否为装上去提供元数据支持
// 关于元数据,也是ES6的新标准,可以通过Reflect提供的静态方法获取元数据
// 如果需要使用Reflect的一些方法,需要引用ES2015.Reflect这个库
// "emitDecoratorMetadata": true,
// 可以配置一个数组列表
// "files":[],
// exclude表示要排除的,不编译的文件
// "exclude":[]
// include也可以指定要编译的路径列表
// "include":[],
//它也可以指定一个列表,规则和include一样,可以是文件可以是文件夹,可以是相对路径或绝对路径,可以使用通配符
// "exclude":[]
// 可以通过指定一个其他的tsconfig.json文件路径,来继承这个配置文件里的配置,继承来的文件的配置会覆盖当前文件定义的配置
// "extends":""
// 如果设为true,在我们编辑了项目文件保存的时候,编辑器会根据tsconfig.json的配置更新重新生成文本,不过这个编辑器支持
// "compileOnSave":true
// 一个对象数组,指定要引用的项目
// "references":[]
//****************** 未配置选项 ******************
}
}
新建src\components\baseline\menu\src\menu.tsx
import { defineComponent, PropType } from 'vue'
import { MenuItem } from './types'
export default defineComponent({
name: 'infiniteMenu',
props: {
//说明:
data: {
required: true,
type: Array as PropType<MenuItem[]>,
},
// 默认选中的菜单
defaultActive: {
type: String,
default: '',
},
//是否是路由模式 router,
//是否启用 vue-router 模式
//启用该模式会在激活导航时以 index 作为 path 进行路由跳转
router: {
type: Boolean,
default: false,
},
},
setup(props, ctx) {
return () => {
return <div>menus</div>
}
},
})
修改src\views\baseline\menu\index.vue
<template>
<div style="width: 2rem">
<!-- <bs-menu :data="data" defaultActive="3-2"></bs-menu> -->
<bs-infinite-menu :data="data2"></bs-infinite-menu>
</div>
</template>
<script lang="ts" setup>
let data = [
{ name: '首页', index: '1', icon: 'el-icon-document' },
{ name: '图标选择器', index: '2', icon: 'el-icon-document' },
{
name: '行政区域选择组件',
index: '3',
icon: 'el-icon-document',
children: [
{ name: '省市选择组件', index: '3-1', icon: 'el-icon-document' },
{ name: '省市区选择组件', index: '3-2', icon: 'el-icon-document' },
{
name: '省市区村选择组件',
index: '3-3',
icon: 'el-icon-document',
},
],
},
0,
]
let data2 = [
{ name: '首页', index: '1', icon: 'el-icon-document' },
{ name: '图标选择器', index: '2', icon: 'el-icon-document' },
{
name: '行政区域选择组件',
index: '3',
icon: 'el-icon-document',
children: [
{
name: '省市选择组件',
index: '3-1',
icon: 'el-icon-document',
children: [
{
name: '组件演示',
index: '3-1-1',
icon: 'el-icon-document',
},
],
},
{ name: '省市区选择组件', index: '3-2', icon: 'el-icon-document' },
{
name: '省市区村选择组件',
index: '3-3',
icon: 'el-icon-document',
},
],
},
0,
]
</script>
<style lang="scss" scoped></style>
发表评论 (审核通过后显示评论):