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页就算是是开发完成了。
本节代码
结束