界面框架:
我采用了flex布局,先分左右,然后右侧再分上下。
步骤:
1. 首先实现简单的菜单
1.1 菜单是个菜单项数组 []
1.2 菜单项结构 例子
{
id:'001',
name: '历史轨迹', // 菜单名称
isTitle: true, // 表示可以展开
level: 1, // level控制缩进,vue动态class使用
expand: true, // 展开状态,
icon: require('@/assets/svg/track.svg'),
children:[ // 根据需要配置
{
id:'002',
name: '人员轨迹',
isTitle: false, // false表示是子节点,点击访问,而不是展开
level: 2, //
icon: require('@/assets/svg/person.svg'),
route:{
name:'historyTrack' // 这个name要和路由中配置的name保持一致,这个很关键
}
}
]
}
1.3 新建一个菜单,就叫 TreeMenu
下面代码就是TreeView
菜单组件,里面递归了自己
<template>
<ul>
<li v-for="item in menu" :key="item.id">
<div :class='[item.isTitle? "menu-title": "menu-item", "level"+ item.level]' @click="toggleMenu(item)
<img v-show="item.icon" :src="item.icon" />
<span>{{item.name}}</span>
</div>
<TreeMenu v-if="item.children && item.expand" :menu="item.children" />
</li>
</ul>
</template>
"level-"+ item.level
: 这是我设计的样式缩进level-1到level-5, panding-left每次增加12pxTreeMenu
递归调用自己绘制形成多级菜单TreeMenu
有一个props
,传递的数据结构就是 上面1.2定义的菜单项结构li
标签里的div中添加click事件toggleMenu
,负责处理是新打开一个菜单项对应的路由还是展开菜单,如:
toggleMenu(item){
const isTitle = item.isTitle
if(isTitle){ // 展开 or 折叠
this.$emit('expand', item) // 把该数据传给父组件,因为菜单数据是props从父组件传过来的,子组件修改会有警告,所以传事件给父组件,让父组件修改
}else{ // 打开菜单页面
if(this.$router.currentRoute.name !== item.route.name){ // 这个是为了防止点击已打开的页面,导致route模块告警说打开同一路由
this.$route.replace(item.route.name)
}
}
}
2. 然后实现AppMain:就是上面界面框架的内容部分
2.1 主要是<router-view></router-view>
来展现菜单对应的路由里组件的内容
```HTML
<template>
<section>
<keep-alive :max="10" :include="cacheRoute">
<router-view :key="key" />
</keep-alive>
</section>
</template>
<script>
// 省略乱七八糟的 ...
conputed:{
cacheRoute(){
const route = this.$store.getters['history/cacheRoute'] // 这个是Store中保存的已打开的路由,下面介绍,是个路由数组
return route.map(e=>e.name)
},
key(){
return this.$route.path
}
}
</script>
```
keep-alive
表示保活,就是你在打开其他组件的时候,当前组件不会销毁,下次打开不经历beforecreate、created、...、mounted等生命周期,从vue-devtools中也可以看到,他依然挂在AppMain下面,但是它是灰色的。注意:需要保活的组件vue配置的name属性必须配置且唯一。 详细信息见:keep-alive 官方文档keep-alive
的:max="10
表示最多保活十个组件,第十一个进来,第一个则移除,不再保活,防止占用系统过多资源,根据实际需要调整该数keep-alive
的:include="cacheRoute"
表示保活的组件包括这些。如果想让某个组件销毁,不再保活,则将组件对应的路由名称从cacheRoute
中移除即可router-view
个人理解就是用来展示当前路由对应组件的内容。 对这个理解不是太深,看了文档,模模糊糊的。详细信息见:router-view 官方文档router-view
的:key="key"
是打开的路由路径
3. 所打开页面的tag
<template>
<!-- 这个是上面的一长条,就是界面框架右上方的空间 -->
<div>
<!-- 这个就是一个个小的tag标签 -->
<route-link v-for="item in cacheRoute" :ref="item.path" :key="item.path" :to="{path:item.path, fullPath:item.fullPath}" custom :class="{'active-tab':activeRoute === item.name}" @click.middle.native="closeTab(item)">
<div>
{{item.meta.title}} <!-- 路由配置的标题 -->
<i class="close-btn" v-if="item.meta.closable!== false" @click.prevent="closeTab(item)" /> <!-- 关闭按钮,根据路由中配置的属性来决定是否显示关闭按钮从而操作该tag是否可以关闭,路由相关的数据结构下面贴出来 -->
</div>
</route-link>
</div>
</template>
route-link
:它组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的<a>
标签,可以通过配置 tag 属性生成别的标签(高版本中会抛出警告,说不再支持这个属性,请使用custom)。详细信息见:router-link 官方文档<route-link v-for="item in cacheRoute"
:cacheRoute
中保存的是在keep-alive
保活的组件路由,使用store(VueX)管理closeTab
方法可以将当前tag所代表的路由,从cacheRoute
中移除,然后触发keep-alive的includes所绑定的数据,从keep-alive中移除,移除后会自动执行生命周期的destroyed 钩子进行销毁
4. 路由数据结构
4.1 例子
{
path:'/home',
name:'home', // 这个name要和1-2的菜单配置里route{name}一致
meta:{
title: '历史主页', // router-link 显示的tag标题
closable: false, // 这个是是否可以关闭
},
component:()=>import('@/views/home/home.vue'),
}
name
: 1-2的菜单配置里route{name} 要和它一致meta
:它可以根据自己需要填充属性,title
为 tag显示内容,closable
是是否可以关闭
5. Store(VueX)
5.1 相关Store(VueX)的数据结构
{
cacheRoute: Array<Route>, // 实现:添加、删除(在组件被挂载、销毁的时候操作)
activeRoute: string,// 在挂载的时候操作,主要是为了修改css,高亮当前选择的菜单项和tag
}
6. mixin
6.1 mixin 内容
export default({
created(){
const cacheRoute = this.$route.getters['history/cacheRoute'] // 5.1 中的 cacheRoute,是个路由对象数组
if(cacheRoute.length === 0){ // 如果有路由是不可被关闭的,那么他将一直存在cacheRoute中,如果不在,那么就添加进去
// 找到不可关闭的路由,这里似乎可以通过route的mata属性配置的closable进行判定,我这样是写死的,固定住了
const route=this.$router.getRoutes().find(e=>e.name == "home")
this.$store.commit('history/addCacheRoute', route) // 添加进cacheRoute,这样就能显示在tag里面了
}
this.$store.commit('history/addCacheRoute', this.$route.currentRoute) // 将当前的路由加入 cacheRoute
this.$store.commit('history/setActiveRoute', this.$route.currentRoute.name)// 将当前路由设为活动的,便于高亮tag和菜单项
},
actived(){ // 这个生命周期的钩子是keep-alive的,激活当前组件时触发,还有个deactivated,取消激活当前组件时触发
this.$store.commit('history/setActiveRoute', this.$route.currentRoute.name)// 将当前路由设为活动的,便于高亮tag和菜单项
}
})
注意:该mixin需要在组件中混入