import { Injectable, Component } from '@angular/core'; // API http://ionicframework.com/docs/v2/api/navigation/NavController/ import { NavController, NavOptions, ViewController, DeepLinker } from 'ionic-angular'; import { Route } from './route' import { Log} from '../utils/log' import { stringifyUrl as originStringifyUrl, parseUrl, hasHost } from '../utils/url' import { pathToPageName } from '../utils/letter' const log = new Log('HistoryCreator'); const INIT_ZINDEX = 100 function isPresent(val: any): val is any { return val !== undefined && val !== null; } // 类型字符串配置 const typeOptions = { ignoreLock:{ignoreLock:true},// 忽略锁定,强制跳转 forward: {direction: 'forward'}, back: {direction: 'back'}, none: {direction: 'none',animate:false}, wap: {target:'wap'}, replace: {replace:true}, wp: {animation:'wp-transition'}, md: {animation:'md-transition'}, cache:{cache:true} // 跳转时,跳转前页面缓存 } // history全局锁 let lockHistory = false; export interface HistoryOptions extends NavOptions{ target?:string; // 跳转的目标,分为wap/inner replace?:boolean;// 是否替换,如果是替换则不做回退,会先remove后push } // 过滤配置 export const filterOptions = (opts: any):HistoryOptions => { if(typeof opts === 'string') { let options = {}; if(opts.indexOf('|')>-1){ let types = opts.split('|'); for (let i = 0, n = types.length; i < n; i++) { let type = types[i]; let typeOption = Object.assign({},typeOptions[type]); if(typeOption) options = Object.assign({}, options, typeOption); } }else{ let type = opts; options = Object.assign({},typeOptions[type]); } return options; }else if(typeof opts === 'number'){ return { duration: opts } }else{ return opts || {}; } } // 创建history export class HistoryCreator { config constructor(public navCtrl,public deepLinker, config) { this.config = config; // 重写_postViewInit this.navCtrl._postViewInit = (enteringView: ViewController, leavingView: ViewController, ti) => { const opts = ti.opts || {}; const insertViews = ti.insertViews; const removeStart = ti.removeStart; const removeCount = ti.removeCount; let view: ViewController; let i: number; let destroyQueue: ViewController[]; // log.info('ti',ti); if (isPresent(removeStart)) { destroyQueue = []; for (i = 0; i < removeCount; i++) { view = this.navCtrl._views[i + removeStart]; if (view && view !== enteringView && view !== leavingView) { destroyQueue.push(view); } } opts.direction = opts.direction || 'back'; } const finalBalance = this.navCtrl._views.length + (insertViews ? insertViews.length : 0) - (removeCount ? removeCount : 0); if (finalBalance === 0 && !this.navCtrl._isPortal) { console.warn(`You can't remove all the pages in the navigation stack. nav.pop() is probably called too many times.`, this, this.navCtrl.getNativeElement()); throw 'navigation stack needs at least one root page'; } if (insertViews) { if (isPresent(opts.id)) { enteringView.id = opts.id; } // add the views to the for (i = 0; i < insertViews.length; i++) { view = insertViews[i]; this.navCtrl._insertViewAt(view, ti.insertStart + i); } if (ti.enteringRequiresTransition) { opts.direction = opts.direction || 'forward'; } } //log.info('destroyQueue',destroyQueue); if (destroyQueue && destroyQueue.length > 0) { this.navCtrl._zone.run(() => { for (i = 0; i < destroyQueue.length; i++) { view = destroyQueue[i]; this.navCtrl._willLeave(view, true); this.navCtrl._didLeave(view); this.navCtrl._willUnload(view); } }); // 重写 for (i = 0; i < destroyQueue.length; i++) { if(this._isCacheView(destroyQueue[i],this.config)){ this._moveView(destroyQueue[i],enteringView); }else{ this.navCtrl._destroyView(destroyQueue[i]); } } } if (ti.requiresTransition && !opts.animation) { if (isPresent(ti.removeStart)) { opts.animation = (leavingView || enteringView).getTransitionName(opts.direction); } else { opts.animation = (enteringView || leavingView).getTransitionName(opts.direction); } } ti.opts = opts; } // 重写_cleanup,当页面主菜单时根据opts不做销毁处理 this.navCtrl._cleanup = (activeView: ViewController) => { const activeViewIndex = this.navCtrl._views.indexOf(activeView); const views = this.navCtrl._views; let reorderZIndexes = false; let view: ViewController; let i: number; // log.info('opts---->',opts); // log.info('activeViewIndex',activeViewIndex); // log.info('viewsLength',views.length); for (i = views.length - 1; i >= 0; i--) { view = views[i]; if (i > activeViewIndex) { // 大于当前view的全部清理 this.navCtrl._willUnload(view); if(this._isCacheView(view,this.config)){ this._moveView(view,activeView); }else{ this.navCtrl._destroyView(view); } } else if (i < activeViewIndex && !this.navCtrl._isPortal) { view._domShow(false, this.navCtrl._renderer); } if (view._zIndex <= 0) { reorderZIndexes = true; } } if (!this.navCtrl._isPortal && reorderZIndexes) { for (i = 0; i < views.length; i++) { view = views[i]; view._setZIndex(view._zIndex + INIT_ZINDEX + 1, this.navCtrl._renderer); } } log.info('final views length',views && views.length); } } _isCacheView = (view: ViewController,SConfig) => { return this.isNavView(view, SConfig); } // 判断是否会需缓存的view isNavView = (view: ViewController,SConfig) => { if(!SConfig || !SConfig.navs) return false; for (let i = 0, n = SConfig.navs.length; i < n; i++) { let url = SConfig.navs[i].url; // 导航类的view需缓存 const pageName = pathToPageName(url); const data = view.data || {}; //log.info('data.componentName',data.componentName); //log.info('data.componentName == pageName',data.componentName == pageName); if(data.componentName == pageName) return true; } return false; } // 移动视图 _moveView = (view: ViewController,enteringView: ViewController) => { if(view.data && enteringView.data && view.data.componentName == enteringView.data.componentName) return; // if(view.name == enteringView.name) return; const views = this.navCtrl._views; const index = views.indexOf(view); const enterIndex = views.indexOf(enteringView); //log.info('enterIndex',enterIndex); if (index >= 0) { const insertView = views.splice(index,1); // 插入的视图其zIndex要确保小于enteringView insertView[0]._setZIndex(enteringView._zIndex - 1, this.navCtrl._renderer); insertView[0]._domShow(false, this.navCtrl._renderer); if(enterIndex > 0){ views.splice(enterIndex, 0, insertView[0]); }else{ // 未大于0则直接添加至数组头部 views.unshift(insertView[0]); } } } // 手动删除页面,不会触发动画 _destroyViewByIndex = (index) => { const view = this.getByIndex(index); this.navCtrl._destroyView(view); } // 锁住跳转,一般用于监控 lockAll = () => { // log.info('lock history!',null,true); lockHistory = true; setTimeout(()=>{ lockHistory = false; },3000) // 3秒后解锁 } unLockAll = (delay = 0) => { log.info('unLock history!',null,true); return new Promise((resolve)=>{ setTimeout(()=>{ lockHistory = false; resolve(); },delay); // 延迟多少秒解锁 }) } // 内部链接跳转 goTo = (url: any, params:any = {}, opts?:any, done?: Function) => { const route = typeof url == 'string' ? Route.pathToRoute(url) : url; const urlObj = parseUrl(url, route.url); params = Object.assign({},urlObj.params,params); // 在参数中添加组件名称,用于在路由切换时识别组件名(因压缩后view中返回的名称都变为缩写) params.componentName = pathToPageName(route.url); const Page = route.page; return this.goToPage(Page,params,opts,done); } // 跳转内部原生页面 goToPage = (Page: any, params?:{}, opts?:any, done?: Function) => { opts = filterOptions(opts); if(lockHistory && !opts.ignoreLock) return new Promise(()=>{}); const index = this.indexOf(Page, params, opts); // 查找views中有没有Page的实例并返回索引 const maxIndex = this.length() - 1; // 获取views的最后一个Page实例的索引 log.info('history index',index,true); log.info('history maxIndex',maxIndex,true); log.info('history params',params,2); log.info('history opts',opts,true); //log.info('replace',opts && opts.replace); // 如果路由堆栈为空,则置为root页 if(maxIndex == -1) return this.setRoot(Page,params,opts,done); // 要去的索引=当前页面的索引判断replace是否为true,为false则返回 if(index == maxIndex) { if(!opts || !opts.replace) return new Promise(()=>{}); opts.direction = opts.direction || 'forward'; if(maxIndex == 0){ return this.setRoot(Page,params,opts,done); }else{ return this.push(Page,params,opts,done).then(()=>{ return this._destroyViewByIndex(maxIndex); }); } } // 记录中不存在则push,如果replace 为true则替换当前页或设置root页 if(index === -1){ if(opts && opts.replace && maxIndex == 0){ // 当路由堆栈中只有1个时无法删除,因此将新路由设置为root页 return this.setRoot(Page,params,opts,done); }else{ if(opts && opts.replace && maxIndex > 0){ // replace则删除当前页面 return this.push(Page,params,opts,done).then(()=>{ return this._destroyViewByIndex(maxIndex); }); }else{ return this.push(Page,params,opts,done); } } }else if(index < maxIndex){ // 历史记录中存在索引,则pop,如果cache为true则不pop而是用后退后再插入 if(index == 0 && (!opts || (opts && !opts.replace))) { return this.popTo(this.getByIndex(0), opts, done); }else if(opts && opts.replace && index >= 0){ log.info('_destroyViewByIndex'); opts.direction = opts.direction || 'back'; this._destroyViewByIndex(index); return this.push(Page,params,opts).then(()=>{ return this._destroyViewByIndex(maxIndex - 1); // 前面已删1个所以这里减1 }); }else{ return this.popTo(this.getByIndex(index),opts,done); } } } /** * 后退跳转方法,支持数字,page和viewctrl * @param step * @param params * @param opts * @param done */ goBack = (step = -1,params?:{}, opts?: any, done?: Function) => { opts = filterOptions(opts); const maxIndex = this.length() - 1; if(maxIndex <= 0) return new Promise((resolve)=>{resolve({maxIndex})}); const backIndex = maxIndex + step; if(backIndex <= -1) return new Promise((resolve)=>{resolve({maxIndex})}); const currViewCtrl = this.getByIndex(maxIndex); const backViewCtrl = this.getByIndex(backIndex); const isCacheView = this._isCacheView(currViewCtrl,this.config); opts.direction = opts.direction || (isCacheView ? 'none' : 'back'); return this.popTo(backViewCtrl,opts,done).then(()=>{ return {maxIndex} }); } // 拼接url,考虑route匹配 stringifyUrl = (url: any, params:any = {}) => { const route = Route.pathToRoute(url); return originStringifyUrl(url, params, route ? route.url : null); } filterOptions = filterOptions isViewController(page:any) { return page && !!page.component; } push(page: any, params?: any, opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.push(page,params,opts,done); } insert(insertIndex: number, page: any, params?: any, opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.insert(insertIndex,page,params,opts,done); } insertPages(insertIndex: number, insertPages: Array<{ page: any; params?: any; }>, opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.insertPages(insertIndex,insertPages,opts,done); } pop(opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.pop(opts,done); } popToRoot(opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.popToRoot(opts,done); } popTo(page: any, params?: any, opts?: any, done?: any): Promise{ return this.navCtrl.popTo(page,params,opts,done); } popAll(): Promise{ return this.navCtrl.popAll(); } remove(startIndex: number, removeCount?: number, opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.remove(startIndex,removeCount,opts,done); } removeView(viewController: ViewController, opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.removeView(viewController,opts,done); } setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.setRoot(pageOrViewCtrl,params,opts,done); } setPages(pages: any[], opts?: NavOptions, done?: Function): Promise{ return this.navCtrl.setPages(pages,opts,done); } getByIndex(index: number): ViewController{ return this.navCtrl.getByIndex(index); } getActive(includeEntering?: boolean): ViewController{ return this.navCtrl.getActive(includeEntering); } isActive(view: ViewController): boolean{ return this.navCtrl.isActive(view); } getPrevious(view?: ViewController): ViewController{ return this.navCtrl.getPrevious(view); } first(): ViewController{ return this.navCtrl.first(); } last(): ViewController{ return this.navCtrl.last(); } // 解析webview路径 parseWebviewPath = (url) => { if(!url) return null; const urlObj = parseUrl(url); const urlParams = urlObj.params; const originUrl = urlParams['url']; if(!originUrl) return null; const originObj = parseUrl(originUrl); return originObj.path; } indexOf(Page: any, params?, opts?): number{ if(this.isViewController(Page)){ // 判断传入的值是不是Page的实例 return this.navCtrl.indexOf(Page); }else{ const views = this.getViews(); for (let i = 0, n = views.length; i < n; i++) { const component = views[i].component; const instance = views[i].instance; const componentIsEqual = Page === component; const instanceIsEqual = instance && Page === instance.constructor; if(opts.webview && params){ // webview则需判断url是否一致 const webViewUrl = views[i].data ? views[i].data.url : null; const originPath = this.parseWebviewPath(params.url); const webViewOriginPath = this.parseWebviewPath(webViewUrl); if(originPath && originPath === webViewOriginPath) return i; }else{ if(componentIsEqual || instanceIsEqual) return i; } } return -1; } } length(): number{ return this.navCtrl.length(); } getViews(): Array{ return this.navCtrl.getViews(); } getActiveChildNav(): any{ return this.navCtrl.getActiveChildNav(); } isTransitioning(includeAncestors?: boolean): boolean{ return this.navCtrl.isTransitioning(includeAncestors); } canSwipeBack(): boolean{ return this.navCtrl.canSwipeBack(); } canGoBack(): boolean{ return this.navCtrl.canGoBack(); } registerChildNav(nav: any): any{ return this.navCtrl.registerChildNav(nav); } resize(){ return this.navCtrl.resize(); } }