Vue3.0组件开发の侧边菜单组件
1. 实现目标
- (1)通过树形的菜单结构,生成菜单组件
- (2)传入的菜单结构
export const ComponentList = [
{
type: "group",
name: "通用",
id: "1",
children: [
{
type: "item",
name: '透明输入框',
path: '/docs/inputdoc',
id: "1-1"
},
{
type: "item",
name: '菜单',
path: '/docs/menudoc',
id: "1-2"
}
]
},
{
type: "subItem",
name: "测试",
id: "2",
children: [
{
type: "subItem",
name: '测试',
id: "2-1",
children: [
{
type: "item",
name: '测试',
path: '/docs',
id: "2-1-1",
checked: true
},
{
type: "item",
name: '测试',
path: '/docs',
id: "2-1-2"
}
]
}
]
}
]
- (3)通过以上结构生成
- (4)默认项为:2-1-2
2. 组件设计
- (1)新建Menu菜单,新建Item.vue, menu.vue, menuList.vue, subItem.vue四个组件
- (2)Menu.vue: 组件入口,接收菜单列表,默认项数据。
<template>
<div class="tao_menu">
<div class="menu_header">
</div>
<menu-list :list="menuList" :active="state.active"></menu-list>
</div>
</template>
<style lang="less" scoped>
.menu_header {
height: 48px;
}
</style>
<script>
import { provide, reactive } from 'vue';
import MenuList from './MenuList';
export default {
components: {
'menu-list': MenuList
},
props: {
menuList: {
type: Array,
default: () => []
},
defaultActive: {
type: String,
default: ""
}
},
setup(props, context) {
const state = reactive({
active: props.defaultActive
});
const itemChange = (e) => {
state.active = e;
}
provide('itemChange', itemChange);
return {
state
}
}
}
</script>
- (3) MenuList.vue: 遍历工作,接收菜单列表,通过不同类型渲染不同组件,传递默认项数据。
<template>
<template v-for="item in list" :key="item.id">
<div v-if="item.type === 'group'" class="group">
<div class="group_title">
<div class="text">{{ item.name }}</div>
</div>
<div class="group_content">
<menu-list :active="active" :list='item.children'></menu-list>
</div>
</div>
<template v-else-if="item.type === 'subItem'">
<sub-item :active="active" :data='item'></sub-item>
</template>
<template v-else>
<v-item :active="active" :data='item'></v-item>
</template>
</template>
</template>
<style lang="less" scoped>
.group {
.group_title {
padding: 0 0 0 48px;
color: #888;
.text {
padding: 10px 0 10px 10px;
border-bottom: 1px solid #f0f0f0;
}
}
.group_content {
padding: 0 0 0 48px;
}
}
</style>
<script>
import { onMounted, ref, reactive, inject, watchEffect } from 'vue';
import Item from './Item';
import SubItem from './SubItem';
export default {
name: "MenuList",
components: {
'v-item': Item,
'sub-item': SubItem
},
props: {
list: {
type: Array,
default: () => []
},
active: {
type: String,
default: ""
}
},
setup(props, context) {
onMounted(() => {
});
// watchEffect(() =>{
// console.log(props.active,"watch")
// })
/**
* sub开关
*/
}
}
</script>
类型group为分组,只在最外层存在,类型subItem为可展开项,类型item为基本菜单项,最外层直接开始vfor遍历数组,当type为group时,通过name的定义重新遍历自己,当type为subItem时,调用另一个组件SubItem,当type为item时则调用Item组件,这个过程最外层group遍历完毕(组件不支持内部group),接下来的遍历交给SubItem
- (4)subItem.vue: 可展开菜单项,接收变量data和active,data为当前项的对象,包含名称,children等字段,变量active为传递过来的默认选中项
<template>
<div class="sub_item">
<div class="subitem_title" @click="itemSwitch">
<span class="text">{{ data.name }}</span>
<span class="iconfont icon-arrow-right arrow" :class="{'on_bottom': state.itemShow}"></span>
</div>
<transition name="slide">
<div class="sub_content" v-show="state.itemShow">
<template v-for="item in data.children" :key="item.id">
<template v-if="item.type === 'subItem'">
<sub-item :active="active" :data='item'></sub-item>
</template>
<template v-else>
<v-item :active="active" :data='item'></v-item>
</template>
</template>
</div>
</transition>
</div>
</template>
<style lang="less" scoped>
.sub_item {
.subitem_title {
padding: 10px 10px 10px 48px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
.text {
padding-left: 10px;
}
.arrow {
transition: all 0.5s ease;
}
.on_bottom {
transform: rotate(90deg);
}
}
.subitem_title:hover {
color: #008dfe;
transition: color 0.5s ease;
}
.sub_content {
padding: 0 0 0 48px;
}
}
</style>
<script>
import { onMounted, ref, reactive, inject, watchEffect } from 'vue';
import Item from './Item';
import MenuList from './MenuList';
export default {
name: "SubItem",
components: {
'v-item': Item,
'menu-list': MenuList
},
props: {
data: {
type: Object,
default: () => {}
},
active: {
type: String,
default: ""
}
},
setup(props, context) {
const state = reactive({
itemShow: false // 是否展开list
});
onMounted(() => {
if (props.active.indexOf(props.data.id) === 0) {
state.itemShow = true;
}
})
const change = inject("itemChange");
function itemSwitch() {
state.itemShow = !state.itemShow;
}
return {
itemSwitch,
state
}
}
}
</script>
该组件由标题和子列表两部分组成,子列表根据选中项判断是否显示使用vshow,子列表由transition包裹,vue的动画组件,当标题被选中后执行动画显示子列表,遍历子列表时依然要判断是否是type为subItem,若true继续调用自身(name定义组件名称);
- (5)Item.vue: 基本子菜单,接收变量为本身描述对象data和选中项active
<template>
<div class="item" @click="checkItem">
<router-link :class="{action: data.id === active}" :to="data.path" v-if="data.path">{{ data.name }}</router-link>
<a href="javascript:void()" v-else>地址未传</a>
</div>
</template>
<style lang="less" scoped>
.item {
a {
padding: 10px 0 10px 10px;
color: #222;
text-decoration: none;
width: 100%;
height: 100%;
display: block;
box-sizing: border-box;
transition: all 0.5s ease;
}
a:hover {
color: #008dfe;
}
.action {
background: rgb(230, 247, 255);
color: #008dfe;
}
}
</style>
<script>
import { onMounted, inject } from 'vue';
export default {
props: {
data: {
type: Object,
default: () => {}
},
active: {
type: String,
default: null
}
},
setup(props, context) {
onMounted(() => {
});
/**
* 菜单选择
*/
const change = inject("itemChange");
function checkItem() {
change(props.data.id);
}
return {
checkItem: checkItem
}
}
}
</script>
被单机后更改选项,重新传递更新
github 地址: github.com/z1429170900…