記錄--有關uni-app如何實現路由攔截的知識分享

来源:https://www.cnblogs.com/smileZAZ/archive/2022/10/10/16776192.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 隨著業務的需求,項目需要支持H5、各類小程式以及IOS和Android,這就需要涉及到跨端技術,不然每一端都開發一套,人力成本和維護成本太高了。團隊的技術棧主要以Vue為主,最終的選型是以uni-app+uview2.0作為跨端技術 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

前言

隨著業務的需求,項目需要支持H5、各類小程式以及IOS和Android,這就需要涉及到跨端技術,不然每一端都開發一套,人力成本和維護成本太高了。團隊的技術棧主要以Vue為主,最終的選型是以uni-app+uview2.0作為跨端技術棧。以前一直聽別人吐槽uni-app怎麼怎麼不好,但是沒什麼概念,這一次需要為團隊開發一個項目的基礎框架和一些示例頁面,主要是支持路由攔截http請求多實例請求數據加密以及登錄功能封裝,發現uni-app的生態不怎麼健全,比如我們項目很需要的路由攔截,http請求攔截,這些都沒有提供,對於跨端的相容問題也挺多的。這篇文章聊聊的路由攔截的調研,以及最終的選擇和實現。

實現路由攔截的方式

  • 使用uni-simple-router
  • 重寫uni-app跳轉方法
  • 對uni-app跳轉方法做進一步的封裝

使用uni-simple-router

uni-simple-router是為uni-app專門提供的路由管理器,使用方式跟vue-router的API一致,可以很方便的上手,Github 也有了六百多的start,它可以說是uni-app用來做路由管理很好的選擇,但是我沒有選擇使用它,個人認為開發h5是可以的,但是如果做跨端,可能會有一些後患,接下來我們聊聊為什麼不使用它的原因。

無法攔截switchTabnavigateBack

這個其實也不算是一個缺點,目前也沒找到可以攔截這兩個事件的路由插件,如果確實需要實現這兩種跳轉方式的攔截,也是可以實現的,可以使用下一種方式,對這兩種方法進行暴力重寫。

沒有解決全部的跨端相容問題

這個其實是我不選擇它的主要原因,根據官方文檔的說明,根據文檔去配置和編寫,基本上能解決所有端上的95%的問題,其他的5%的問題需要去查看編譯到端的說明。代碼還是嚴謹的,缺少1%都是不完美的,更何況是5%。這會導致在以後的使用過程中,可能因為相容問題,導致自己沒辦法去解決,或者為瞭解決這個問題,需要花費大量的時間和精力,有可能得不償失。

編譯app時,不能用'nvue'作為啟動頁面

nvue 不能直接作為啟動頁面。因為在啟動時 uni-app 會檢測啟動頁面是否為原生渲染,原生渲染時不會執行路由跳轉,插件無法正確捕捉頁面掛載。這也是一個問題,我們可以儘量的去避免,但以後有未知的情況,可能我們的啟動頁必須就是以nvue來實現。

暴力重寫uni-app跳轉方法

這種方式雖然有點簡單粗暴,但是效果挺好的,代碼也很簡短,Vue2.0對於數組的響應式監聽也是採用這種方式。雖然實現了,但可能有些同學不知道怎麼使用,直接把這段代碼寫在main.js就可以了,或者也可以在單獨的文件里封裝一個封裝一個函數,然後在main.js引入,然後執行該方法。

const routeInterceptor = () => {
    const methodToPatch = ["navigateTo", "redirectTo", "switchTab", "navigateBack"];
    methodToPatch.map((type) => {
      // 通過遍歷的方式分別取出,uni.navigateTo、uni.redirectTo、uni.switchTab、uni.navigateBack
      // 並且對相應的方法做重寫
      const original = uni[type];
      uni[item] = function (options = {}) {
        if (!token) {
          // 判斷是否存在token,不存在重定向到登錄頁
          uni.navigateTo({
            url: "/login",
          });
        } else {
          return original.call(this, opt);
        }
      };
    });
}

routeInterceptor()

這是一個最極簡的方式,需要添加其他參數和判斷邏輯,大家可以自行添加,這裡只是拋磚引玉,給大家提供一個思路。

使用方式

handleDetail() {
    uni.navigateTo({        
        url: '/detail?id=11111111111'
    }) 
}

對uni-app跳轉方法做進一步的封裝

這個是 uView提供的一種路由封裝方式,對於路由傳參做了進一步的封裝,使用起來更加方便,但是不涉及到uni-app跳轉方式的重寫,所以也談不上改了路由跳轉的跨端相容,所以還是具有uni-app一致的相容性。但是官方文檔沒有說明提供了路由攔截,但這個還是我們特別需要的功能,去查看源碼,發現還是提供了這個功能。現在還存在的一個問題是,這個功能是跟uView強耦合的,可能我們並不想使用uView,所以我們可以將這個功能獨立抽離。

 目錄結構

/router/index.js

這個文件主要提供路由攔截函數,具體的實現,可以大家可以根據自己的需求實現,最後向外暴露一個包含install方法的對象,使用的時候可以直接用Vue.use進行註冊。

routeConfig這個參數是路由相關的配置,resolve 傳遞一個true或者false表示是否允許跳轉。

routeConfig屬性

參數名類型預設值是否必填說明
type String navigateTo false navigateToto對應uni.navigateToredirectredirectTo對應uni.redirectToswitchTabtab對應uni.switchTabreLaunch對應uni.reLaunchnavigateBackback對應uni.navigateBack
url String - false typenavigateToredirectToswitchTabreLaunch時為必填
delta Number 1 false typenavigateBack時用到,表示返回的頁面數
params Object - false 傳遞的對象形式的參數,如{name: 'lisa', age: 18}
animationType String pop-in false 只在APP生效,詳見視窗動畫(opens new window)
animationDuration Number 300 false 動畫持續時間,單位ms
import route from "./route";
// 配置白名單
const whiteList = ["/pages/login/index"];
const install = function (Vue, options) {
  uni.$e = { route };
  Vue.prototype.route = route;
  uni.$e.routeIntercept = (routeConfig, resolve) => {
    const path = routeConfig.url.split("?")[0];
    if (!whiteList.includes(path) && !uni.getStorageSync("token")) {
      uni.$e.route("/pages/login/index");
      return;
    }
    resolve(true);
  };
};
export default {
  install,
};

/router/route.js

這個文件,主要是對於uni-app跳轉做了封裝,主要做的還是傳參部分,實現跟vue-router一致的傳參方式,使用起來更加方便優雅,同時提供一個uni.$e.routeIntercept路由攔截方法。

/**
 * 路由跳轉方法,該方法相對於直接使用uni.xxx的好處是使用更加簡單快捷
 * 並且帶有路由攔截功能
 */

import { queryParams, deepClone, deepMerge, page } from "./utils";
class Router {
  constructor() {
    // 原始屬性定義
    this.config = {
      type: "navigateTo",
      url: "",
      delta: 1, // navigateBack頁面後退時,回退的層數
      params: {}, // 傳遞的參數
      animationType: "pop-in", // 視窗動畫,只在APP有效
      animationDuration: 300, // 視窗動畫持續時間,單位毫秒,只在APP有效
      intercept: false, // 是否需要攔截
    };
    // 因為route方法是需要對外賦值給另外的對象使用,同時route內部有使用this,會導致route失去上下文
    // 這裡在構造函數中進行this綁定
    this.route = this.route.bind(this);
  }

  // 判斷url前面是否有"/",如果沒有則加上,否則無法跳轉
  addRootPath(url) {
    return url[0] === "/" ? url : `/${url}`;
  }

  // 整合路由參數
  mixinParam(url, params) {
    url = url && this.addRootPath(url);

    // 使用正則匹配,主要依據是判斷是否有"/","?","="等,如“/page/index/index?name=mary"
    // 如果有url中有get參數,轉換後無需帶上"?"
    let query = "";
    if (/.*\/.*\?.*=.*/.test(url)) {
      // object對象轉為get類型的參數
      query = queryParams(params, false);
      // 因為已有get參數,所以後面拼接的參數需要帶上"&"隔開
      return (url += `&${query}`);
    }
    // 直接拼接參數,因為此處url中沒有後面的query參數,也就沒有"?/&"之類的符號
    query = queryParams(params);
    return (url += query);
  }

  // 對外的方法名稱
  async route(options = {}, params = {}) {
    // 合併用戶的配置和內部的預設配置
    let mergeConfig = {};

    if (typeof options === "string") {
      // 如果options為字元串,則為route(url, params)的形式
      mergeConfig.url = this.mixinParam(options, params);
      mergeConfig.type = "navigateTo";
    } else {
      mergeConfig = deepClone(options, this.config);
      // 否則正常使用mergeConfig中的url和params進行拼接
      mergeConfig.url = this.mixinParam(options.url, options.params);
    }

    // 如果本次跳轉的路徑和本頁面路徑一致,不執行跳轉,防止用戶快速點擊跳轉按鈕,造成多次跳轉同一個頁面的問題
    if (mergeConfig.url === page()) return;

    if (params.intercept) {
      this.config.intercept = params.intercept;
    }
    // params參數也帶給攔截器
    mergeConfig.params = params;
    // 合併內外部參數
    mergeConfig = deepMerge(this.config, mergeConfig);
    // 判斷用戶是否定義了攔截器
    if (typeof uni.$e.routeIntercept === "function") {
      // 定一個promise,根據用戶執行resolve(true)或者resolve(false)來決定是否進行路由跳轉
      const isNext = await new Promise((resolve, reject) => {
        uni.$e.routeIntercept(mergeConfig, resolve);
      });
      // 如果isNext為true,則執行路由跳轉
      isNext && this.openPage(mergeConfig);
    } else {
      this.openPage(mergeConfig);
    }
  }

  // 執行路由跳轉
  openPage(config) {
    // 解構參數
    const { url, type, delta, animationType, animationDuration } = config;
    if (config.type == "navigateTo" || config.type == "to") {
      uni.navigateTo({
        url,
        animationType,
        animationDuration,
      });
    }
    if (config.type == "redirectTo" || config.type == "redirect") {
      uni.redirectTo({
        url,
      });
    }
    if (config.type == "switchTab" || config.type == "tab") {
      uni.switchTab({
        url,
      });
    }
    if (config.type == "reLaunch" || config.type == "launch") {
      uni.reLaunch({
        url,
      });
    }
    if (config.type == "navigateBack" || config.type == "back") {
      uni.navigateBack({
        delta,
      });
    }
  }
}

export default new Router().route;

/router/uitls.js

這個文件主要是為路由封裝提供一些工具函數

/**
 * @description 對象轉url參數
 * @param {object} data,對象
 * @param {Boolean} isPrefix,是否自動加上"?"
 * @param {string} arrayFormat 規則 indices|brackets|repeat|comma
 */
export const queryParams = (
  data = {},
  isPrefix = true,
  arrayFormat = "brackets"
) => {
  const prefix = isPrefix ? "?" : "";
  const _result = [];
  if (["indices", "brackets", "repeat", "comma"].indexOf(arrayFormat) == -1)
    arrayFormat = "brackets";
  for (const key in data) {
    const value = data[key];
    // 去掉為空的參數
    if (["", undefined, null].indexOf(value) >= 0) {
      continue;
    }
    // 如果值為數組,另行處理
    if (value.constructor === Array) {
      // e.g. {ids: [1, 2, 3]}
      switch (arrayFormat) {
        case "indices":
          // 結果: ids[0]=1&ids[1]=2&ids[2]=3
          for (let i = 0; i < value.length; i++) {
            _result.push(`${key}[${i}]=${value[i]}`);
          }
          break;
        case "brackets":
          // 結果: ids[]=1&ids[]=2&ids[]=3
          value.forEach((_value) => {
            _result.push(`${key}[]=${_value}`);
          });
          break;
        case "repeat":
          // 結果: ids=1&ids=2&ids=3
          value.forEach((_value) => {
            _result.push(`${key}=${_value}`);
          });
          break;
        case "comma":
          // 結果: ids=1,2,3
          let commaStr = "";
          value.forEach((_value) => {
            commaStr += (commaStr ? "," : "") + _value;
          });
          _result.push(`${key}=${commaStr}`);
          break;
        default:
          value.forEach((_value) => {
            _result.push(`${key}[]=${_value}`);
          });
      }
    } else {
      _result.push(`${key}=${value}`);
    }
  }
  return _result.length ? prefix + _result.join("&") : "";
};

/**
 * 是否數組
 */
function isArray(value) {
  if (typeof Array.isArray === "function") {
    return Array.isArray(value);
  }
  return Object.prototype.toString.call(value) === "[object Array]";
}

/**
 * @description 深度克隆
 * @param {object} obj 需要深度克隆的對象
 * @returns {*} 克隆後的對象或者原值(不是對象)
 */
export const deepClone = (obj) => {
  // 對常見的“非”值,直接返回原來值
  if ([null, undefined, NaN, false].includes(obj)) return obj;
  if (typeof obj !== "object" && typeof obj !== "function") {
    // 原始類型直接返回
    return obj;
  }
  const o = isArray(obj) ? [] : {};
  for (const i in obj) {
    if (obj.hasOwnProperty(i)) {
      o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
    }
  }
  return o;
};

/**
 * @description JS對象深度合併
 * @param {object} target 需要拷貝的對象
 * @param {object} source 拷貝的來源對象
 * @returns {object|boolean} 深度合併後的對象或者false(入參有不是對象)
 */
export const deepMerge = (target = {}, source = {}) => {
  target = deepClone(target);
  if (typeof target !== "object" || typeof source !== "object") return false;
  for (const prop in source) {
    if (!source.hasOwnProperty(prop)) continue;
    if (prop in target) {
      if (typeof target[prop] !== "object") {
        target[prop] = source[prop];
      } else if (typeof source[prop] !== "object") {
        target[prop] = source[prop];
      } else if (target[prop].concat && source[prop].concat) {
        target[prop] = target[prop].concat(source[prop]);
      } else {
        target[prop] = deepMerge(target[prop], source[prop]);
      }
    } else {
      target[prop] = source[prop];
    }
  }
  return target;
};

/**
 * @description 獲取當前頁面路徑
 */
export const page = () => {
  const pages = getCurrentPages();
  // 某些特殊情況下(比如頁面進行redirectTo時的一些時機),pages可能為空數組
  return `/${pages[pages.length - 1]?.route ?? ""}`;
};

路由配置

在main.js引入

import router from "./router";

Vue.use(router);

使用方式

更全的使用方式可以查看 uView路由跳轉文檔

全局使用

uni.$e.route('/pages/info/index');

vue文件中使用

this.route('/pages/info/index');

攔截switchTab、navigateBack

現在的方式還是沒辦法支持攔截switchTab、navigateBack,所以需要藉助第二種方式,重寫這兩種方法,具體實現,完善 /router/index.js

// /router/index.js

import route from "./route";
// 配置白名單
const whiteList = ["/pages/login/index"];

const handleOverwirteRoute = () => {
  // 重寫switchTab、navigateBack
  const methodToPatch = ["switchTab", "navigateBack"];
  methodToPatch.map((type) => {
    // 通過遍歷的方式分別取出,uni.switchTab、uni.navigateBack
    // 並且對相應的方法做重寫
    const original = uni[type];
    uni[type] = function (options = {}) {
      const { url: path } = options;
      if (!whiteList.includes(path) && !uni.getStorageSync("token")) {
        // 判斷是否存在token,不存在重定向到登錄頁
        uni.$e.route("/pages/login/index");
      } else {
        return original.call(this, options);
      }
    };
  });
};

const install = function (Vue, options) {
  uni.$e = { route };
  Vue.prototype.route = route;
  // 重寫uni方法
  handleOverwirteRoute();
  // 路由攔截器
  uni.$e.routeIntercept = (routeConfig, resolve) => {
    const path = routeConfig.url.split("?")[0];
    if (!whiteList.includes(path) && !uni.getStorageSync("token")) {
      uni.$e.route("/pages/login/index");
      return;
    }
    resolve(true);
  };
};
export default {
  install,
};

補充

在系統第一進入的時候,是不會觸發攔截事件的,需要在App.js的onLanch去做進一步的實現。

onLaunch: function () {
    if (!uni.getStorageSync("token")) {
      uni.navigateTo({ url: "/pages/login/index" });
    }
},

本文轉載於:

https://juejin.cn/post/7119274924149047327

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 點亮 ⭐️ Star · 照亮開源之路 GitHub:https://github.com/apache/dolphinscheduler ​ 版本發佈 感謝本次的 Release Manager --@zhuangchong,是他主導了我們這個版本的發佈流程,引導社區進行版本內容溝通,發版前的問題 ...
  • 摘要:GaussDB(DWS) 中鎖等待可以設置等待超時相關參數,一旦等鎖的時間超過參數配置值會拋錯。 本文分享自華為雲社區《GaussDB(DWS) 鎖相關參數及視圖詳解》,作者: yd_220527686。 一、鎖相關參數 GaussDB(DWS) 中鎖等待可以設置等待超時相關參數,一旦等鎖的時 ...
  • 先說一下為什麼需要備份MySQL數據? 一句話總結就是:為了保證數據的安全性。 如果我們把數據只存儲在一個地方,如果物理機器損壞,會導致數據丟失,無法恢復。 還有就是我們每次手動修改線上數據之前,為了安全起見,都需要先備份數據。防止人為的誤操作,導致弄髒數據或弄丟數據。 ...
  • MatrixOne從入門到實踐——物聯網平臺架構升級 公司介紹 西安天能軟體科技有限責任公司,成立於2018年,公司自成立起集中力量精心打造物聯網平臺,擁有集自主研發、終端生產、銷售、服務一體的物聯網平臺及服務團隊,已為國內外300多家物聯網企業、千萬級物聯網設備提供合作支持。 公司在物聯網領域擁有 ...
  • 在應用開發的早期,數據量少,開發人員開發功能時更重視功能上的實現,隨著生產數據的增長,很多SQL語句開始暴露出性能問題,對生產的影響也越來越大,有時可能這些有問題的SQL就是整個系統性能的瓶頸。 ...
  • [Android開發學iOS系列] iOS寫UI的幾種方式 作為一個現代化的平臺, iOS的發展也經歷了好幾個時代. 本文講講iOS寫UI的幾種主要方式和各自的特點. iOS寫UI的方式 在iOS中寫UI有多種選擇, 大的分類: 使用UIKit還是SwiftUI. 在使用UIKit的情形下, 還根據 ...
  • 一、新建原生工程和Flutter Module 1、新建Android工程 搭建一個空的Android工程FlutterDemo_Android模擬已經存在的原有工程 Android項目配置: 2、新建iOS工程 搭建一個空的iOS工程FlutterDemo_iOS模擬已經存在的原有工程 Xcode ...
  • 鬥魚直播相信大家都聽說過,打開鬥魚官網就可以直接在瀏覽器中觀看直播。那麼鬥魚是如何實現瀏覽器視頻直播的呢?本篇文章就來解析鬥魚是如何實現直播的,以及它是如何節省 80% 的 CDN 流量,要知道視頻直播流量費並不便宜,鬥魚每個月光這些流量費都要支付幾個億,節省 CDN 流量就是省錢。 直播技術方案 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...