25.Vue技术栈开发实战-多Tab页开发







点击tab左侧的菜单会对应的选中这个菜单

首先做一点修改,本节课用mock来做拦截。
路由列表做了一些修改,给每一个路由对象都加了meta对象。这是路由源信息对象,每一个都加一个title属性。

并且没一个路由对象都有一个name值,并且是不重复的

嵌套路由

路由列表所有的name都写在这

路由列表生成左侧菜单

宽度改成200

默认是false


store的严格模式先关掉

左侧的菜单之前是这里我们写死的

现在左侧的菜单哟啊根据路由列表去实现。下面这里把menuList删掉。

我们要获取store里面促成黏糊的routers,开面是过滤路由列表权限后的数据

引入mapState


我们还需要对结果进行过滤。匹配的*和 /login这两个页面我们是不希望他出现在左侧的菜单的。

所以在这里做一个简单的过滤,使用filter筛选出来不等于*和不等于login的

筛选出来上之后赋值

加上.meta





现在就是根据路由列表生成的菜单

菜单超出屏幕高度让它滚动
给sider加个类名

高度为100%,超出隐藏,

当前菜单超出被隐藏了,但是它没法滚动了。

它的里面还有一个容器

让ivu的这个元素,y轴超出滚动,水平方向超出 隐藏。

这样就有滚动态条了。

滚动条看起来比较丑,而且mac电脑和window电脑滚动台的样式还不一样。

所以如果不希望看到这个滚动条的话



这样它的宽度就变宽了

超出的部分被它的父容器,超出隐藏了。

所以当前我们看不到滚动条,而且还能滚动。也有个缺点就是有的人看不到滚动条,反而不知道这里可以滚动。根据自己的需求进行添加。

多标签

用到iview的tab组件

复制这里代码

router-view下注释掉。然后代码复制到这里。

tab不放在card里面,在上面嵌套在div里面。

在加一个div,把card和里面的router-view嵌套在里面。



先把标签的内容都去掉。




修改样式

首先是不希望下面有间距





只用TabPane生成标签。在里面不做内容的渲染,本页面还是只有一个router-view ,还是在router-view里面渲染页面。

标签肯定是通过一个数组来v-for循环生成。我们把这个数组存到store里面。在store里面新建tabNav



index引入这个模块。

先来定义一个数组,用来保存打开页面的列表。所有tab的标签是通过这个数组来渲染的。



拿到store里面的tabList,然后循环生成TabPane。这里不应该简单的存一个路由的name,因为这里如果要做动态路由,还有带参数的页面的话,同样name

把这里的name改成params

同样把这里也改成params

这里区别是后面的参数不同,页面其实是不同的页面。这样就没法用params来区别页面。

所以我们把name、params、query都存进去,存一个对象,
这里先把key写上。

待会会来写一下 ,先来解决的简单的,不带参数的路由。不带动态参数的路由。就这样直接用item.name就可以了。


现在是没有标签的

接下来是打开一个页面。现在的菜单点击没有做跳转。

在这里我们直接做跳转,直接push 然后里面传入路由的name就可以了。这是es6的简写形式,

当我们点击菜单

跳转到对应的页面上。







countTo是我们直接在路由列表里面定义的。匹配的就是count-to这个组件。


没有使用layout这个父组件。所以我们点击是跳转到了单独的页面。

当点击这三个菜单的时候

变化的是中间的内容区域

是因为我们使用了layout这个父组件。这三个页面都是作为它的嵌套路由来渲染的,

路径上的component是一级路由,后面的是二级路由。切换的是后面的

演示我们就先用这三个页面来做演示,其他的地方就先不改了。

当前打开的页面保存到tabList里面

定义一个mutation方法。接收一个route

把这个路由对象拿到后,我们添加到tabList里面。
当路由变化的时候添加。所以这里可以用watch,监听路由的变化


引入mapMutations,它是一个方法。

把这个方法引进来

当路由发生变化,把跳转的新的路由对象传进去。




多次点击的效果。有重复添加的问题。

当前tab如果存在,那么就不再添加,我们先来做简单的判断,只有name不同。
用find做判断,没有找到name相同的。那么才去添加。

直接不添加tab了

前面用叹号,后面用 三等号


重复的点击,不会重复的添加了


点击菜单,对应的tab选中效果。tab组件可以绑定一个value

value是name


给他绑定一个value,当前点击的是谁,路由对象就是谁。

所以这里直接局势$router.name

label改成meta.title 标签上显示的name值。

再给他一个name值

这样他就知道你当前选中的是哪一个。这里删掉


切换不同的页

如果你这个页面是动态路由,后面有参数,参数是变化的。根据不同的参数来显示不同的内容。

还有就是带query参数的。如果这两种情况,你都希望不同的参数,打开不同的标签页的话。只通过name是不够的


name值应该包括当前路由的name值,还包裹它的参数。还有query参数相关的信息。

加一个按钮和事件

params加上一个随机数,得到秒。

然后进行跳转



这改成random


点击打开了新的tab页面

这里就是随即生成的参数。

打开多个params的页面,不会重复添加。因为我们tab添加的时候,判断的是tab的name

所以需要一个方法来判断,如果name相同,参数也一样,才算是一样。


用一下之前在tolls内定义的方法



tabList[index]就是当前遍历的项。当前这里也可以用forEach

我们要来定义一个新的方法routeEqual 两个参数,

params1等于route1.params或者是一个空对象。如果route1没有这个params那么就是个空对象。

至少route的name是相同的

&&继续往下判断。如果name相同,那么就继续来判断param。判断这连个params和query我们还要来定义方法。判断两个对象里面的属性名和属性值一一对应相等。

方法定义在tools。判断两个对象的值和属性都相等。

个数也相等,值也相等。作为一个工具方法,定义在tools 内。

用Object.keys获取到它所有的属性名。

属性的个数不相同,那么直接return false


长度都是0,那么就是俩空对象,那么长度就都是相等的。

使用some方法,会遍历这个数组,它里面传入一个回调函数。

如果有任何一个这两个属性的key不同,那么就返回true。



回调函数内有任何一条遍历结果返回true,那么就是true,如果所有的都是false,那么就是false

和some对应的方法还有every 只有所有的回调函数都返回true,它才是true,有任何一条是false,它就是false

只要有一条不相等就是true,所以这里我们要做一个取反。

使用定义好的方法

这个地方来判断。

如果params1和2这俩对象相等

再来判断query1和2

判断当前tabList索引和 当前路由对象是相等,那么就返回true

这样mutations里面判断,就是用我们定义的方法routeExist

还是会出现重复的,好像有点问题

这里改成直接调用方法,取反

这样就不会有重复的了

打开参数页面





再来打开

参数不相同,依然心打开的是tab页

出现的问题,这两个tab都被选中的状态。因为我们的name是相同的,选中的状态我们是通过name来绑定的。


所以这里就不能用当前路由的name来做标识了。


name、params和query里面的信息通通都包含。所以我们要拼一个字符串。定义一个方法getTabNameByRoute

我们把这个方法定义在Utils里

如果想在这里用,我们必须把他挂载到data上面。

$route变量,都是挂载到vue实例上 的。

所以我们必须要把他放到这个vue实例上。才能在template里面使用。

util里面定义方法

首先传过来一个路由对象,把它解析过来。首先把name赋值给res返回的结果。

如果params不是undefined说明你这个路由对象里面是包含了params这个字段的,并且params它的属性,
Object.keys 它会把它里面所有的属性名取出来,然后放到一个数组里面,如果length不为0 说明它里面是有属性的。

包含了params就给这个res拼字符串。拼成下面这种格式的,id是参数用下划线和value分隔开。 &后面拼接的是query的参数。

定义一个方法,取出来params里面的键值对。

Object.entries

直接可以把一个对象变成一个二维数组,没一个元素是一个数组,数组里面第一个值是key,第二只是value,。但是我们这里不用entries方法。
取键值对有个es6的方法



如果有多个属性,那么就是另外一个数组

需要对它进行排序,因为你的param是一个对象。里面所有属性读出来顺序是不一定的。所以如果你直接拿来拼字符串,虽然里面键值对可能都相同,但是你最后拼出来的顺序不同。也会导致最后比较不正确。所以这里先进行排序。

传两个参数,a[0]减去b[0]。 这是比较两个数组第一个值做排序,

拍完序后做遍历,这里有个参数 就是遍历当前的数组,它里面有个值,第一个值是key,第二个值是value,所以这里用解构赋值。获取到第一个值用key来表示,第二个值用value来表示。

然后用下划线,把他们凭借起来。

接下来处理query。前面 &符号来拼接。

最后把res返回,这样这个方法就定义好了。








故意把参数改成26,会选中26的tab

实现tab页被点击时,事件触发,同时选中左侧菜单

添加tab事件,绑定一个handleClickTab

参数是点击的tab的name值。拿到这个name值,我们首先要做跳转。

先做跳转push传入name是不行的。

这几个参数的tab的name都是argu,只不过他们的params不同,所以直接push一个name是不行的 ,

所以我们要根据name得到一个路由对象。这个name是我们拼接出来的里面包含了param和query信息。


getRouteById这个方法我们在util里面


先用includes判断是不是包含&符号。说明就包含query,

分割

为了保险起见,取数组长度减一,就是这个数组的最后一个元素。

在用下划线来切割。



如果我们的属性分别是a和b 。。那么拼接出来的就是下面这样。

应下划线分割出来就是下面这样

所以每次循环我们+2

query和这部分一样,所以我们把这部分代码拆出来。拆成一个函数。

封城成一个函数

有冒号就表示有params

自后这个res就是包含name或者包含params或者包含query的这样一个对象。

那么通过id我们就可以拿到一个对象了。这里输出看一下



点击tab输出,

这里就得到了一个对象

这里有点问题

我们把这个id也输出来看下

看一下拿到的id




这里分隔字符串,改成用变量

点击,路由也发生了改变


点击表格,路由在变,

菜单的联动

菜单我们用的iview的menu组件。它有个属性叫做。它有个属性叫做active-name这里的值取的是路由的name
现在点击文件夹,左侧的菜单也联动的被选中了。

希望点击里面的子菜单的时候,他的父级 可以展开。

用到菜单的另一个属性:openName

展开父级别菜单

这是一个计算属性,我们要通过一个当前打开激活的路由菜单,展开他的父级menu。
所以当前的$router变化了,它的展开就应该也要变化,

根据当前打开的路由对象的name值,然后把这个routers传进去。





这是一个计算属性。这是在state的router模块里面。

下面来封装这个方法

传入两个参数,一个name,一个是routerList

先定义一个结果数组arr,然后遍历这个routerList。如果当前点击的name和路由里面路由对象是相同的。

push到arr数组。为什么这里用some ,而不是用foreach呢,因为如果用foreach 他会遍历所有的元素。就算你中间满足条件已经找到这个元素了。还是会把后面的元素都遍历了。而这里用some,一旦下面return 返回了true。那么后面没有遍历到的元素,他都不会再进行遍历了。这样节省一些时间。

判断当前有子菜单,并且子菜单的数量不为0.

递归,调用下自身。

name还是那个name。参数2 就是item的children了。

如果childArr的长度不为0的话,说明 上面是返回了。说明你当前遍历的这个路由对象,它的children里面,有一个路由对象它是当前激活的路由对象,

把返回来的childArr和 arr 合并。

最后把这个arr返回。



这里是vuex,之前写错



点击没展开


如果激活的是表格的话,那么得到的应该有component.

输出最后得到的值




展开,iview的组件,需要手动的触发更新才会展开



做一个监听。给菜单加一个ref

监听,openNames这个变量。nextTick会保证你视图渲染完之后。再调用里面的逻辑。

这样就自动打开父级菜单了。

当前菜单是关闭的,当我点击文件夹这个tab

点击后会展开父级别的菜单。

标签关闭

加一个可关闭的属性




这里点击关闭后。实际上tabList里面的数据是没有清空的。

所以我们不用它自带的关闭。这里有个on-tab-remove。当你点击差号关闭的时候,它会触发这个事件。但是还存在一个问题。你怎么在这里面处理你的tabList数组。

自己实现tab的关闭

label可以传入render函数。

这里传一个labelRender函数,给它传一个render函数。

默认参数是h就是我们的渲染函数

这里我们需要给render函数传递一个当前的item参数


所以这里我们要用到一个闭包。返回一个函数。

在这里面做渲染

渲染一个div

看页面效果。tab是被渲染的。

加一个图标,是关闭按钮。区别原来的差号,我们给它一个图标。


给图标绑定一个事件。icon组件本身是没有事件的,所以我们要用nativeOn这个前缀来表示给icon组件最外层的标签绑定一个click事件。

要给他传参数,这里要用bind

关闭要去tabList里面删掉一个路由对象。还是要通过name、params、query这些信息找出来。

把item传进去,调用方法getTabNameByRoute 上面方法接收id。

当设置点击事件的时候,它其实会触发父级的点击事件。

所以这里不希望事件冒泡。接收一个event对象。调用stopPropagation方法来阻止冒泡。


移除方法,我们定义到store的action里面。
我们返回一个Promise,因为我们还要做后续的操作。

route加上$符号

首先通过id获取当前的路由对象

获取到这个路由对象之后呢,我们来找它的索引,这个路由对象在tabList数组里面是在哪个位置。使用findIndex方法,可以传入一个回调函数,通过这个函数传入你查找的条件,最后它会返回给你索引值。




我们来比较一下,如果相等就返回。

最后找到这个路由后,把它删掉。删除操作我们放到mutations里面去操作。

commit提交一个mutation 传入索引

页面内使用action




测试

点击关闭


报了个错误


输出看下id是什么


再打印下,得到的路由




传递的参数是一个对象,传入id 然后当前的路由对象。

然后方法的名字也写错了,重新改一下

点击关闭

tab被关闭了。

关闭了标签后,应该跳到别的页面

例如有两个tab。关闭了一个tab 后,应该默认选中另外一个tab。


所以在这要判断一下,这就是为什么要传当前那个路由对象

做操作。关闭标签后,我们应该跳转到哪个页面。

你要关闭的就是要打开的这个页的话。表示右边还有标签 那么就是下一个。


如果当前打开的这个标签是最后一个。那么nextRoute就是它的前一个。




最后获取下name、params、query.。如果取不到了 就默认用home_index来做跳转。

把这三个值返回 回去,是个对象


接收promise进行跳转

测试


关闭后 自动跳转到下一个。


如果关闭的不是当前

那么就直接关掉,不用走跳转。


关闭最后一个

那么现实的就是左边的tab

本地存储tabList

tabList是存在store里面的。一刷新页面就没了。

utils里面定义两个方法



tabLlist默认应该存localStorage中读取,如果有就读取,没有就是空数组。

在这保存

我们只存有用的,得到一个能够王localStorage里面存的列表。然后存map后的对象。



这里删除后,也重新存一下


刷新


刷新后 依然存在,而且还能做跳转。



在首页这,清空浏览器的缓存




添加标签是在watch里面,监听route的变化。如果变化了就往里面添加。如果有就不往里面添加。



点击表格,并没有往下面添加tab。好像没有监听到变化一样。


ruter里面,首页是layout

这里的父组件也是layout

当下面的路由做变化的时候,/component这一级就变了。 这里你配置的是component.

这里配置的是home

这个路由变了,那么这个组件就会重新去渲染。

它是在app.vue这里。是这个地方渲染改变了。

所以这个layout组件。watch都没有进行,这个组件已经被注销又重新去渲染了。

所以这里是没有生效的


如果想让他有效果。把这个逻辑放到。app.vue

替换这里




方法也引用一下,复制过去








layout之类相关的删掉就可以了

再次测试

先清空缓存。





默认登陆页也被添加进来了。

这里简单的做下过滤


再次清空缓存,刷新页面重新来测试












每次点击也都是做的跳转。


以上多tab页就算是是开发完成了。

本节代码


 

结束

 

posted @ 2020-09-04 00:02  高山-景行  阅读(1555)  评论(1编辑  收藏  举报