Open lizhongzhen11 opened 5 years ago
昨天接了个项目,维护别人开发好的后台管理系统。 业务场景:点击左侧菜单栏,右侧渲染对应路由页面并在头部有对应的tags标签,点击tag标签也相当于路由跳转到对应页面。
要求:
这种需求其实比较常见,主要是为了操作方便。 但是如何去做?怎么去思考呢?
假设有a,b,c三个菜单,原先功能是点击a,b,c三个菜单,右侧面板会相应的渲染这三个菜单对应的组件/页面
针对要求1,较为简单,vue相关的ui库大多提供了tab/tag组件,这里我选用tag,毕竟用tab相当于把所有页面组件全放到tabs里面渲染,这样并不好。
这个项目比较坑的就是菜单栏是写死的,不是通过config.js配置的,这里浪费了很多时间,我需要把所有路由以及对应的菜单名称都维护到一个数组route里面,然后当我点击菜单时,根据菜单路由去route中找到对应的信息,然后放到tags数组里面,渲染tag标签。 同时,给tag加个click事件,点击进行路由跳转。
route
tags
tag
click
其实目前就是路由跳转重新渲染,暂时还不需要去考虑。
点击tag标签进行路由跳转,但是不能清空数据。 熟悉的vue的人肯定立马想到keep-alive组件。
keep-alive
<keep-alive> <router-view></router-view> </keep-alive>
加上上面代码,试验下,信息确实保留了。
但是,问题也来了! 此时点击菜单栏,无法去重新刷新页面并清空数据了!
如何让点击菜单重新渲染页面和点击tag标签保留页面数据共存呢?
一开始,我想了一种方法:
<keep-alive v-if="isKeepAlive"> <router-view></router-view> </keep-alive> <router-view v-if="!isKeepAlive"></router-view>
通过两个不同的router-view去渲染组件,点击tag标签跳转的走keep-alive,点击菜单跳转不走keep-alive。
router-view
经过试验,依然不行。当我点击a,b,c三个菜单,右侧有对应的三个tag标签后,我通过点击tag标签切换页面并分别在这三个页面上写点数据,然后点击a菜单去重新渲染并清空a页面,然后我点击tag标签切换到b页面,再点击tag标签切回a页面,发现之前数据依然存在!
这是为何呢? 其实我上面用了两个router-view维护了两种组件渲染方式。也就是说点击tag标签时,所有页面组件都是keep-alive的,点击菜单刷新时,所有页面都是正常渲染的,他们彼此间没法互相交互。
其实,我困扰的点主要是如何去动态改变组件keep-alive状态,即a页面通过点击tag标签渲染的话,需要加入keep-alive,但是通过菜单渲染的话要取消keep-alive状态!
这样看有点destroy的意思,但是我不可能在几百个vue组件中去destroy!
destroy
百思不得其解后,试着百度了下,偶然在segmentfault看到个评论: 使用keep-alive的include。
include
我立马去官方文档上查看该属性(以前从没用过):https://cn.vuejs.org/v2/api/#keep-alive
当我看到可以通过v-bind去动态改变include时,心中顿时觉得有希望了,不过我需要在之前的route中继续维护组件名称属性——compontName,同时需要在每个路由对应的组件中把name加上,然后测试了下,果然好了!
v-bind
compontName
name
附上代码:
// main.vue <keep-alive :include="includes"> <router-view class="fadeInRight animated"></router-view> </keep-alive>
// mixins handleTags import route from '@/config/route' const handleTags = { data () { return { tags: [], // 缓存所有打开过的菜单页并用tag展示 includes: [], // 缓存需要keep-alive的组件名,点击菜单刷新页面时从中移除 } }, mounted() { const path = this.currentPath const tags = this.tags !tags.length && this.addTag(path) }, computed: { /** * @description 获取当前页面路由 */ currentPath () { return this.$route.path } }, methods: { /** * @description 选择左侧菜单触发,用于渲染右侧头部tags标签以及去除相应页面的keep-alive */ selectMenu ({index, indexPath}) { const i = this.findIndex(this.tags, index, 'path') if (index && index.indexOf('/') !== -1 && i === -1) { this.addTag(index) } this.spliceInclude(index) }, /** * * @param {*} arr * @param {*} path * @description 判断该路由是否已经存在 */ findIndex (arr, value, property) { return arr.findIndex(item => { return property ? item[property] === value : item === value }) }, /** * * @param {*} path * @description 添加标签 */ addTag (path) { const tags = this.tags route.some(item => { item.path === path && tags.push(item) }) }, /** * @description 点击tag触发。将所有的tag标签对应的路由组件全部加入keep-alive */ handleClickTag (tag) { let componentName const tags = this.tags tags.forEach(item => { componentName = this.getComponentName(item.path) !this.includes.includes(componentName) && this.includes.push(componentName) }) this.$nextTick(() => { this.$router.push(tag.path) }) }, /** * * @param {*} tag * @description 关闭tag触发。 * 如果只剩一个不给删除; * 删除当前tag默认展示前一个tag对应的页面; * 如果前面没有tag了,默认展示当前第一个tag; * 同时从keep-alive中删除; */ handleClose (tag) { const index = this.findIndex(this.tags, tag.path, 'path') this.tags.splice(index, 1) if (this.currentPath === tag.path) { const next = index - 1 >= 0 ? index - 1 : 0 this.$router.push(this.tags[next].path) } this.spliceInclude(tag.path) }, /** * @description 获取当前路由对应的组件名称 */ getComponentName (path) { // 获取在route配置中的位置 const j = this.findIndex(route, path, 'path') if (j !== -1) { return route[j].componentName } return }, /** * @description 从keep-alive状态的组件数组中删除 */ spliceInclude (path) { const componentName = this.getComponentName(path) const includes = this.includes const k = this.findIndex(includes, componentName) k !== -1 && this.includes.splice(k, 1) } }, } export default handleTags // route.js const route = [ { name: 'a', path: '/a', componentName: 'a' }, { name: 'b', path: '/b', componentName: 'b' }, { name: 'c', path: '/c', componentName: 'c' }, ]
叼
需求
昨天接了个项目,维护别人开发好的后台管理系统。 业务场景:点击左侧菜单栏,右侧渲染对应路由页面并在头部有对应的tags标签,点击tag标签也相当于路由跳转到对应页面。
要求:
分析
这种需求其实比较常见,主要是为了操作方便。 但是如何去做?怎么去思考呢?
假设有a,b,c三个菜单,原先功能是点击a,b,c三个菜单,右侧面板会相应的渲染这三个菜单对应的组件/页面
思考要求1
针对要求1,较为简单,vue相关的ui库大多提供了tab/tag组件,这里我选用tag,毕竟用tab相当于把所有页面组件全放到tabs里面渲染,这样并不好。
这个项目比较坑的就是菜单栏是写死的,不是通过config.js配置的,这里浪费了很多时间,我需要把所有路由以及对应的菜单名称都维护到一个数组
route
里面,然后当我点击菜单时,根据菜单路由去route
中找到对应的信息,然后放到tags
数组里面,渲染tag
标签。 同时,给tag
加个click
事件,点击进行路由跳转。思考要求2
其实目前就是路由跳转重新渲染,暂时还不需要去考虑。
思考要求3
点击tag标签进行路由跳转,但是不能清空数据。 熟悉的vue的人肯定立马想到
keep-alive
组件。加上上面代码,试验下,信息确实保留了。
但是,问题也来了! 此时点击菜单栏,无法去重新刷新页面并清空数据了!
如何让点击菜单重新渲染页面和点击tag标签保留页面数据共存呢?
一开始,我想了一种方法:
通过两个不同的
router-view
去渲染组件,点击tag
标签跳转的走keep-alive
,点击菜单跳转不走keep-alive
。经过试验,依然不行。当我点击a,b,c三个菜单,右侧有对应的三个
tag
标签后,我通过点击tag
标签切换页面并分别在这三个页面上写点数据,然后点击a菜单去重新渲染并清空a页面,然后我点击tag
标签切换到b页面,再点击tag
标签切回a页面,发现之前数据依然存在!这是为何呢? 其实我上面用了两个
router-view
维护了两种组件渲染方式。也就是说点击tag
标签时,所有页面组件都是keep-alive
的,点击菜单刷新时,所有页面都是正常渲染的,他们彼此间没法互相交互。我的困扰点
其实,我困扰的点主要是如何去动态改变组件
keep-alive
状态,即a页面通过点击tag
标签渲染的话,需要加入keep-alive
,但是通过菜单渲染的话要取消keep-alive
状态!这样看有点
destroy
的意思,但是我不可能在几百个vue组件中去destroy!解决过程
百思不得其解后,试着百度了下,偶然在segmentfault看到个评论: 使用
keep-alive
的include
。我立马去官方文档上查看该属性(以前从没用过):https://cn.vuejs.org/v2/api/#keep-alive
当我看到可以通过
v-bind
去动态改变include
时,心中顿时觉得有希望了,不过我需要在之前的route
中继续维护组件名称属性——compontName
,同时需要在每个路由对应的组件中把name
加上,然后测试了下,果然好了!附上代码: