1.需求

  做了几年的MES系统,从ASP.NET WebForm至MVC,系统决定了用户界面必须为标签页方式实现,因为用户在进行一项操作的时候很有可能会进行其它的操作,比如查询之类的。如果按MVC的方式每个页面都去刷新界面的话用户体验就太差了,所以一直以来都是用的多标签页方式,在WebForm或者MVC框架中都是使用的iframe来实现的,网上找了一个H+的图,就是类似的效果。

  

2.寻找解决方案

  虽然用iframe效果是实现了,但是iframe这种缺点也很明显:

    1.加载页面所有的js,css都要全部再加载一遍(虽然有缓存)

    2.与主页面交互麻烦,比如弹出一个Dialog,在iframe里面弹不美观,在外层主页面弹获取数据比较麻烦

  说了这么多废话大家发现都没说到Angular的内容,不要着急,现在进入主题。最近在看MVVM前端框架,在目前流行的几个框架里我选择了Angular(别问我原因,我能说一整天....),发现MVVM框架来实现上面的效果应该不错。优点嘛就是能解决上面这些个缺点:)

  在把官方的教程写了一遍,看了两天的教学视频后开始动手写实现代码,UI方面很简单,因为用的Metronic,所以标签页的样式我借鉴了H+,具体UI的样式和HTML之类的代码我就不放出来了,这个挺简单的,主要是Angular路由的处理。开始的想法是用子路由,页面中多个router-outlet加name来实现,但是深入了解了路由后发现其实是进了死胡同,因为根本实现不了,点击导航跳转页面路由肯定是会变更的,相当是跳转到一个新的页面了,于是在网上找了找有没有相关的解决方案。在找了很久以后终于在园里子发现了一篇文章:

http://www.cnblogs.com/lslgg/p/7700888.html  这里要特别感谢下:smiles  提供给的思路。就是利用路由的重用策略来实现 。

  Angular路由重用网上资料也挺多的,因为接触Angular不久,所以没想到这块,具体原理我就不说了,大家可以查资料。简单的来说就是在路由跳转的时候可以记录下路由当时的快照,然后将快照存放起来,等你下次重新打开这个路由的时候再从快照里取出来显示原来的界面,当然其中的逻辑是自己写的,想怎么写都行。

3.代码实现

  撸起袖子就是干,smiles大神已经把路由重用的代码写好了,我直接复制了下来,然后另外写了一个标签页管理的组件来实现多标签页管理,这里代码我也先不发了,因为大多是从smiles大神的博客里复制过来的,大家要看代码可以点我上面发的链接。我就贴一点主要的代码:

import { RouteReuseStrategy, DefaultUrlSerializer, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router';export class SimpleReuseStrategy implements RouteReuseStrategy {public static handlers: { [key: string]: DetachedRouteHandle }={}/** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断*/public shouldDetach(route: ActivatedRouteSnapshot):boolean{return true;}/** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象*/public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle):void{SimpleReuseStrategy.handlers[route.routeConfig.path]=handle}/** 若 path 在缓存中有的都认为允许还原路由*/public shouldAttach(route: ActivatedRouteSnapshot):boolean{return !!route.routeConfig && !!SimpleReuseStrategy.handlers[route.routeConfig.path]}/** 从缓存中获取快照,若无则返回nul*/public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {if (!route.routeConfig) {return null}returnSimpleReuseStrategy.handlers[route.routeConfig.path]}/** 进入路由触发,判断是否同一路由*/public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot):boolean{return future.routeConfig ===curr.routeConfig}
}

export class AppComponent {//路由列表menuList: Array<{ title: string, module: string, power: string,isSelect:boolean }>=[];constructor(private router: Router,private activatedRoute: ActivatedRoute,private titleService: Title) {//路由事件this.router.events.filter(event => event instanceofNavigationEnd).map(()=> this.activatedRoute).map(route=>{while (route.firstChild) route =route.firstChild;returnroute;}).filter(route=> route.outlet === 'primary').mergeMap(route=>route.data).subscribe((event)=>{//路由data的标题let title = event['title'];this.menuList.forEach(p => p.isSelect=false);var menu = { title: title, module: event["module"], power: event["power"], isSelect:true};this.titleService.setTitle(title);let exitMenu=this.menuList.find(info=>info.title==title);if(exitMenu){//如果存在不添加,当前表示选中this.menuList.forEach(p => p.isSelect=p.title==title);return;}this.menuList.push(menu);});}//关闭选项标签closeUrl(module:string,isSelect:boolean){//当前关闭的是第几个路由let index=this.menuList.findIndex(p=>p.module==module);//如果只有一个不可以关闭if(this.menuList.length==1) return;this.menuList=this.menuList.filter(p=>p.module!=module);//删除复用deleteSimpleReuseStrategy.handlers[module];if(!isSelect) return;//显示上一个选中let menu=this.menuList[index-1];if(!menu) {//如果上一个没有下一个选中menu=this.menuList[index+1];}//console.log(menu);//console.log(this.menuList);this.menuList.forEach(p => p.isSelect=p.module==menu.module );//显示当前路由信息this.router.navigate(['/'+menu.module]);}
}

 

在我将所有代码嵌入到我写的项目中的时候发现,效果实现了,跟我之前想的一模一样。这里再次感谢下大神

4.遇到问题

  首先,我发现大神写的路由存储用的key是用的路由的path属性,而且要在路由配置里写好:

就是data属性的module属性。这样虽然没什么问题,但是路由多的话要写的内容很多,而且按path去判断会出现问题,因为有主路由和子路由存在的话,path的值取出来都是子路由的path,很有可能不同的主路由会存在相同名称的子路由。所以我稍微改动了下代码:

在路由重用中加了一个方法:

private getRouteUrl(route: ActivatedRouteSnapshot){return route['_routerState'].url.replace(/\//g,'_')
}

获取路由的从主路由开始的路径,相当于location.pathname,然后把其中的  "/"字符换成了下划线。存储路由和判断路由都是用的这个方法的返回值来判断。比如说:

SimpleReuseStrategy.handlers[this.getRouteUrl(route)] = handle

  问题解决了,然而我并没有高兴太久,因为我又遇到了一个问题:有些页面虽然是用的同一个路由,但是有可能是参数不一样,比如说:/detail/1   或者 /detail/2来显示详情界面。

  于是开始查找问题,发现是路由重用组件导致的。我们来看下判断路由是否为同一路由的代码:  

/** 进入路由触发,判断是否同一路由*/public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot):boolean{return future.routeConfig ===curr.routeConfig
}

  这里来判断是否是同一路由是用的 ActivatedRouteSnapshot的routeConfig对象,这个就是配置的路由,详情页面肯定是用的一个路由,只是参数不一样,但是这里直接判断.routeConfig显然是有问题的,具有不同的参数也会认为是同一个路由,导致会将之前的路由拿出来复用,其实并不是一个页面。然后我稍微修改了下这个判断:

/** 进入路由触发,判断是否同一路由*/public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot):boolean{return future.routeConfig===curr.routeConfig &&JSON.stringify(future.params)==JSON.stringify(curr.params);}

加了参数的判断在里面,这里问题解决。

  然而..没多久又出现问题了,刚打开一个新的标签页,然后你并没有切换标签页直接点击标签页上的X把这个标签页又给干掉了,然后你再打开发现还原来的快照,关掉并没有成功清除掉快照。话不多说继续找问题。

  发现导致这个问题是因为,路由快照是在离开这个路由的时候才会被记录,打开新的标签页而且没有切换标签的情况下,快照并没有记录,然而在关闭标签页的事件里删除快照显然就有问题了,因为这个时候你快照还没生成,怎么能删除呢,而且标签一关闭跳到其它标签页的时候,这里又触发了快照的保存。

  想了一下解决方案,用了一个临时变量记录了下这种情况下待删除的路由,最终的路由复用代码:

import { RouteReuseStrategy, DefaultUrlSerializer, ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router';export class SimpleReuseStrategy implements RouteReuseStrategy {public static handlers: { [key: string]: DetachedRouteHandle }={}private static waitDelete:string/** 表示对所有路由允许复用 如果你有路由不想利用可以在这加一些业务逻辑判断*/public shouldDetach(route: ActivatedRouteSnapshot):boolean{return true;}/** 当路由离开时会触发。按path作为key存储路由快照&组件当前实例对象*/public store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle):void{if(SimpleReuseStrategy.waitDelete && SimpleReuseStrategy.waitDelete==this.getRouteUrl(route)){//如果待删除是当前路由则不存储快照SimpleReuseStrategy.waitDelete=nullreturn;}SimpleReuseStrategy.handlers[this.getRouteUrl(route)] =handle}/** 若 path 在缓存中有的都认为允许还原路由*/public shouldAttach(route: ActivatedRouteSnapshot):boolean{return !!SimpleReuseStrategy.handlers[this.getRouteUrl(route)]}/** 从缓存中获取快照,若无则返回nul*/public retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {if (!route.routeConfig) {return null}return SimpleReuseStrategy.handlers[this.getRouteUrl(route)]}/** 进入路由触发,判断是否同一路由*/public shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot):boolean{return future.routeConfig===curr.routeConfig &&JSON.stringify(future.params)==JSON.stringify(curr.params);}private getRouteUrl(route: ActivatedRouteSnapshot){return route['_routerState'].url.replace(/\//g,'_')}
public static deleteRouteSnapshot(name:string):void{if(SimpleReuseStrategy.handlers[name]){deleteSimpleReuseStrategy.handlers[name];}else{SimpleReuseStrategy.waitDelete=name;}}
}

   至此,整个功能的实现就完成了。经过多次测试也再也没有发现其实问题(如果有人发现有其它问题,还请发站内信给我)

5.后话

  写这篇文章主要是想记录下自己在实现的过程遇到的问题,分享出来 ,希望能帮助到其他有类似需求的人,因为这方面的资料实在是太少了。

转载于:https://www.cnblogs.com/lovesangel/p/7853364.html

Angular实现多标签页效果(路由重用)相关推荐

  1. JS实现标签页效果(配合css)不同标签下对应不同div

    显示页面tab.jsp <%@   page   language = "java"   import = "java.util.*"   pageEnc ...

  2. 用css和jquery实现标签页效果(一)

    用css和jQuery实现一个简单的标签页效果,用css实现斜边导航的效果,除了ie6其他的浏览器都支持, 其效果如下     css样式: <style type="text/css ...

  3. 【Axure交互教程】 可滑动的标签页效果

    作品名称:可滑动的标签页效果 作品编号:Case002 软件版本:Axure9 作品类型:交互案例 原型预览链接(附源文件下载链接):http://daisyaxure.com/demo/Case00 ...

  4. css3和jQuery实现一个简单的标签页效果

    主要用css3另外一种jQuery思路方法来实现标签页的切换效果,主要用css3来实现一些渐变,阴影和圆角效果, css代码如下: body{width: 600px;margin: 100px au ...

  5. 【033】Bootstrap实现标签页效果

    版本 Bootstrap v3.3.6 jQuery v1.11.3 实现方法 给显示标签页内容的DIV分别设置上ID: tab_1 和 tab_2.在标签页按钮上的 href 属性设置 #tab_1 ...

  6. 标签页使用及bug解决

    标签页使用 1.点击菜单项生成新的标签页 2.点击标签页项实现页面跳转 3.bug 标签页放在main中 标签页el-tabs主要属性: (1)editableTabsValue:高亮表示被选中的标签 ...

  7. WPF自适应可关闭的TabControl 类似浏览器的标签页

    原文:WPF自适应可关闭的TabControl 类似浏览器的标签页 效果如图: 虽然说是自适应可关闭的TabControl,但TabControl并不需要改动,不如叫自适应可关闭的TabItem. 大 ...

  8. 点击button按钮打开新的标签页

    //1.开启新的标签页 <button class="ui button" type="submit" onclick="window.open ...

  9. Ant Design Blazor 组件库的路由复用多标签页介绍

    前言 Blazor 是 .NET 最新的前端框架,可以基于 WebAssembly 或 SignalR (WebSocket)构建前端应用程序,基于 WebAssembly 托管模型的 Blazor ...

  10. php 标签页切换,vue.js实现标签页切换效果

    第二个实例是关于标签页切换的,先看一下效果: 这也是一个很常见的交互效果,以往正常的javascript写法是给各个按钮绑定事件来切换不同的层,当然也可以用纯css写,给上面的三个切换的层分别添加一个 ...

最新文章

  1. 2019 年ML NLP领域十大研究热点
  2. 17原理图查找连接的管脚接口_第三节 主板原理图之标识的作用
  3. mysql5.7.19设置_MySQL5.7.19安装配置
  4. 在Python中使用Seaborn和WordCloud可视化YouTube视频
  5. 用python怎么下载_如何使用python下载视频
  6. 解决TypeError: string indices must be integers, not str
  7. Android 系统(229)---OTA
  8. 根据xml文件生成对应javabean类
  9. Windows下Netcat安装
  10. LVM逻辑卷管理学习
  11. 数据库创建索引的规则
  12. 2017年下半年综合素质作文
  13. sdn的用处_SDN是什么?SDN的好处有哪些?
  14. 资深架构师推荐 21 本技术好书
  15. php新增的特性,PHP7新增特性
  16. setsockopt()改善socket网络程序的健壮性
  17. 适合学龄前孩子看的动画片 小蜜蜂(蜂来乐)值得推荐
  18. js随机数,随机从数组里面去一个或多个元素
  19. 申宝投资-市场行情整体比较差
  20. 学的中专计算机专业可以考大专吗,我是中专计算机系毕业的,可以考哪些国家职业资格证书...

热门文章

  1. android tabhost 跳转,TabHost中跳转到指定Tab页问题
  2. 7340怎么更换墨盒_打印显示墨盒托架被卡住怎么办?请按下边步骤正确解决
  3. python开发人工智能要不要很高数学_CFA、FRM持证人的“秘密武器”—Python,连潘石屹都在学,你确定不要了解一下吗?...
  4. Web前端的学习路线到底是什么,看完秒懂!
  5. [Objective-C]ARC中NSString *与CFStringRef的相互转换
  6. 更好的使用Java集合(三)
  7. atitit.提高开发效率---mda 革命性的软件开发方法
  8. MonkeyRunner源码分析之工作原理图
  9. 那些唱衰智能电视的砖家们可以闭嘴了
  10. 图论+dp poj 1112 Team Them Up!