美文网首页
栈+单向链表实现Angular 11访客浏览脚印

栈+单向链表实现Angular 11访客浏览脚印

作者: xiaofanku | 来源:发表于2021-01-16 20:59 被阅读0次

    应用中需要浏览脚印功能实现导航条的后退,登录成功后的跳转,404页面中的:返回上一页功能。当浏览时(非后退操作时)将数据压入栈, 后退时弹出栈顶; 用单向链表来存储数据,使用:ngx-webstorage-service将数据存储在客户端。数据结据为:

    //单向链表
    export interface TrackItem {
      //上一页的连接
      previous: string;
      //当前页的连接
      value: string;
    }
    

    A: 保存

    import { ChangeDetectionStrategy, Component, OnInit} from '@angular/core';
    import { NavigationEnd, Router } from '@angular/router';
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styles: [``],
      changeDetection: ChangeDetectionStrategy.Default
    })
    export class AppComponent implements OnInit {
      constructor(
        private router: Router,
        private footMark: FootmarkTrackService) {
        this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: any) => {
          //console.log('[App]prev url:', event.url);
          this.footMark.save(event.url);
        });
      }
    }
    

    FootmarkTrackService的代码在后面附上

    B: 导航的后退

    后退通过指令实现,只要a元素的class定义中含有: historyBack, 404映射的模板示例:

    <a href="javascript:;" role="button" class="btn historyBack">上一页</a>
    

    指令定义如下:

    import { Directive, HostListener } from '@angular/core';
    import { Params, Router } from '@angular/router';
    @Directive({
      selector: 'a.historyBack'
    })
    export class HistoryBackDirective {
      private currentURL: string;
    
      constructor(private router: Router, private footMark: FootmarkTrackService) {
        this.currentURL = router.url;
      }
    
      @HostListener('click', ['$event.target'])
      public backHistory($event: Event): void {
        let previousURL: string | null = this.footMark.getPrevious();
        let data: { path: string, queryParams: Params } = this.footMark.processURL(previousURL || '/home');
        this.router.navigate([data.path], { queryParams: data.queryParams });
      }
    }
    

    C: 登录时获取来源: Referer

    import { Component, OnInit } from '@angular/core';
    import { Params, Router } from '@angular/router';
    @Component({
      selector: 'app-login',
      templateUrl: './login.component.html',
      styles: [``]
    })
    export class LoginComponent implements OnInit {
      public member: { names: string, pswd: string, redirect: string } = {
        names: '',
        pswd: '',
        redirect: ''
      };
      private previousUrl!: string | null;
      
      constructor(private router: Router, private footMark: FootmarkTrackService) {}
    
      ngOnInit(): void {
          this.previousUrl = this.footMark.getReferer();
      }
      //登录成功后的回调函数
      private storeMember(): void {
         //ETC
         //处理完后跳转
         this.processRedirectURL(this.member.redirect || this.previousUrl);
      }
      private processRedirectURL(argDedirectURL: string | null): void {
        //是否有参数
        let redirectURL: string = argDedirectURL || '/home';
        let data: { path: string, queryParams: Params } = this.footMark.processURL(redirectURL);
        this.router.navigate([data.path], { queryParams: data.queryParams });
      }
    }
    

    D: FootmarkTrackService

    import { Injectable, Inject } from '@angular/core';
    import { Params } from '@angular/router';
    import { StorageService, SESSION_STORAGE } from 'ngx-webstorage-service';
    @Injectable({
      providedIn: 'root'
    })
    export class FootmarkTrackService {
      private ftKey: string = 'ftStack';
    
      //存储会员浏览地址的路线图
      //用途: 1)后退功能.正向压栈,后退弹栈; 2)获取当前地址的referer 
      constructor(@Inject(SESSION_STORAGE) private storage: StorageService) { }
    
      /**
       * 保存/压栈
       * @param url 
       */
      public save(url: string): void {
        let data: TrackItem[] = [];
        let lastEle: TrackItem | undefined = undefined;
        if (this.exist()) {
          data = this.get();
          lastEle = data[data.length - 1];
        }
        //不存在 或 存在但一样
        let previousURL: string = lastEle?.value ?? '';
        if (previousURL === url) { //后退时会发生;
          return;
        }
        let pr: TrackItem = { previous: previousURL, value: url };
        data.push(pr);
        this.storage.set(this.ftKey, data);
      }
    
      /**
       * 是否忽略地址
       * :/member/login(|register|offline); :/404
       * @param url 
       * @returns
       */
      private isIgnoreURL(url: string): boolean {
        return url.startsWith('/member/login') || url.startsWith('/member/register') || url.startsWith('/member/offline') || url.startsWith('/404');
      }
    
      /**
       * 是否是第一次保存/栈是否存在
       * @returns
       */
      private exist(): boolean {
        return this.storage.has(this.ftKey);
      }
    
      /**
       * (2)获取当前地址的Referer
       * 注意:LoginComponent.ngOnInit方法中调用;若在constructor方法中调用会取到错误的值
       * @returns
       */
      public getReferer(): string | null {
        if (!this.exist()) {
          return null;
        }
        //
        let data: TrackItem[] = this.get();
        //栈顶
        let lastEle: TrackItem | undefined = data[data.length - 1];
        return lastEle?.previous ?? null;
      }
    
      /**
       * 返回存储的数组
       * @returns
       */
      private get(): TrackItem[] {
        return this.storage.get(this.ftKey);
      }
    
      /**
       * (1)返回前一个地址
       * 注意:方法存在一个缺陷, 例:1>A->login, 2>login->A 此时调用又回到了A,产生在A(2>)上调用回退无作用的假象.getPreviousRef方法修复此缺陷
       * @returns
       */
      public getPrevious(): string | null {
        if (!this.exist()) {
          return null;
        }
        let data: TrackItem[] = this.get();
        //弹栈
        let result: string | null = null;
        do {
          let lastEle: TrackItem | undefined = data.pop();
          if (lastEle && typeof (lastEle.previous) !== 'undefined') {
            result = lastEle.previous;
            if (this.isIgnoreURL(result)) {
              result = null;
            }
          }
        } while (result === null);
        //覆盖掉
        this.storage.set(this.ftKey, data);
        return result;
      }
    
      /**
       * (1)查看以参考地址为界的前一个地址
       * 修复getPrevious方法在忽略地址前后调用后退无作用的假像
       * @param refUrl 
       * @returns
       */
      public getPreviousRef(refUrl: string): string | null {
        if (!this.exist()) {
          return null;
        }
        let data: TrackItem[] = this.get();
        //地址最后一次出现在哪
        let lastShowIndex: number = -1;
        for (let i: number = data.length - 1; i >= 0; i--) {
          if (data[i].previous === refUrl) {
            lastShowIndex = i;
            break;
          }
        }
        //出现过后,开始截取前部分
        if(lastShowIndex > 0){
          data = data.slice(0, lastShowIndex);
        }
        //往前推一级
        let lastEle: TrackItem | undefined = data.pop();
        let result: string | null = lastEle?.previous ?? null;
        //若是忽略的地址再往上推一层
        if (result !== null && this.isIgnoreURL(result)) {
          result = data.pop()?.previous ?? null;
        }
        //覆盖掉
        this.storage.set(this.ftKey, data);
        return result;
      }
    
      //处理redirect
      public processURL(redirectURL: string): { path: string, queryParams: Params } {
        let p: any;
        let qs: Params = {};
        if (redirectURL.indexOf('?') == -1) {
          p = redirectURL;
        } else {
          p = redirectURL.substring(0, redirectURL.indexOf('?'));
          let queryString = redirectURL.substring(redirectURL.indexOf('?') + 1);
          if (queryString) {
            let segment: string[] = queryString.split('&');
            segment.forEach(ele => {
              let kv: string[] = ele.split('=');
              if (kv.length == 2) {
                qs[kv[0]] = kv[1];
              }
            });
          }
        }
    
        return { path: p, queryParams: qs };
      }
    }
    //单向链表
    export interface TrackItem {
      //上一页的连接
      previous: string;
      //当前页的连接
      value: string;
    }
    

    附图:


    2021-01-16 190944.jpg

    相关文章

      网友评论

          本文标题:栈+单向链表实现Angular 11访客浏览脚印

          本文链接:https://www.haomeiwen.com/subject/eakoaktx.html