无码日本H肉黄动漫魔乳密剑贴_无码中文字幕在线三区_无码中文字幕视频一区二区三区_无码中文字幕人妻丝袜美腿_无码中文字幕加勒比高清_无码中文字幕A∨在线_日本久久久久久久久久加勒比

歡迎來到深圳市來科信科技有限公司網(wǎng)站!

已閱讀

基于Vue和TS的Web移動端發(fā)開項目實戰(zhàn)心得

來源:lexintech.com ?? ?? 發(fā)布時間:2019-09-27
筆者在公司用 web 技術開發(fā)移動端應用已經(jīng)有一年多的時間了,開始主要以 vue 技術棧配合 native 為主,目前演進成 vue + react native 技術架構,vue 主要負責開發(fā) OA 業(yè)務,比如報銷、出差、crm 等等,react native 主要負責即時通信部分,是在 mattermost-mobile[1] 的基礎上修改的(mattermost 是一個開源的即時通訊方案)。

因為公司在這方面沒有太多技術沉淀,所以在開發(fā)期間遇到了很多坑,經(jīng)過一年多的技術攻克積累,最終形成了這套比較完善的解決方案,總結出來希望能夠幫助到大家,尤其是對一些中小公司這方面經(jīng)驗不足的(PS: 大公司估計有他們自己的一套方案了)。

好了廢話不多說,先亮下這個庫的 GitHub 地址,后面還會不斷完善,歡迎 star:

mobile-web-best-practice[2]

移動端 web 最佳實踐,基于 vue-cli3[3] 搭建的 typescript[4] 項目,可以用于 hybrid 應用或者純 webapp 開發(fā)。以下大部分內(nèi)容同樣適用于 react[5] 等前端框架。

其中有三個點尚在完善中:領域驅(qū)動設計(DDD)應用、微前端、性能監(jiān)控,后續(xù)完成后會以單獨的文章發(fā)出來。其中性能監(jiān)控還沒有太好的選擇,類似錯誤監(jiān)控 sentry 那種開源免費而且功能強大的工具,如果有人知道的麻煩告知下。文中難免有些錯誤或者更好的方案,也歡迎不吝賜教。

目錄

  • 組件庫[6]

  • JSBridge[7]

  • 路由堆棧管理(模擬原生 APP 導航)[8]

  • 請求數(shù)據(jù)緩存[9]

  • 構建時預渲染[10]

  • Webpack 策略[11]

    • 基礎庫抽離[12]

  • 手勢庫[13]

  • 樣式適配[14]

  • 表單校驗[15]

  • 阻止原生返回事件[16]

  • 通過 UA 獲取設備信息[17]

  • mock 數(shù)據(jù)[18]

  • 調(diào)試控制臺[19]

  • 抓包工具[20]

  • 異常監(jiān)控平臺[21]

  • 常見問題[22]

組件庫

vant[23]

vux[24]

mint-ui[25]

cube-ui[26]

vue 移動端組件庫目前主要就是上面羅列的這幾個庫,本項目使用的是有贊前端團隊開源的 vant。

vant 官方目前已經(jīng)支持自定義樣式主題,基本原理就是在 less-loader[27] 編譯 less[28] 文件到 css 文件過程中,利用 less 提供的 modifyVars[29] 對 less 變量進行修改,本項目也采用了該方式,具體配置請查看相關文檔:

定制主題[30]

推薦一篇介紹各個組件庫特點的文章:

Vue 常用組件庫的比較分析(移動端)[31]

JSBridge

DSBridge-IOS[32]

DSBridge-Android[33]

WebViewJavascriptBridge[34]

混合應用中一般都是通過 webview 加載網(wǎng)頁,而當網(wǎng)頁要獲取設備能力(例如調(diào)用攝像頭、本地日歷等)或者 native 需要調(diào)用網(wǎng)頁里的方法,就需要通過 JSBridge 進行通信。

開源社區(qū)中有很多功能強大的 JSBridge,例如上面列舉的庫。本項目基于保持 iOS android 平臺接口統(tǒng)一原因,采用了 DSBridge,各位可以選擇適合自己項目的工具。

本項目以 h5 調(diào)用 native 提供的同步日歷接口為例,演示如何在 dsbridge 基礎上進行兩端通信的。下面是兩端的關鍵代碼摘要:

安卓端同步日歷核心代碼,具體代碼請查看與本項目配套的安卓項目 mobile-web-best-practice-container[35]

public class JsApi {
    /**
     * 同步日歷接口
     * msg 格式如下:
     * ...
     */
    @JavascriptInterface
    public void syncCalendar(Object msg, CompletionHandler handler) {
        try {
            JSONObject obj = new JSONObject(msg.toString());
            String id = obj.getString("id");
            String title = obj.getString("title");
            String location = obj.getString("location");
            long startTime = obj.getLong("startTime");
            long endTime = obj.getLong("endTime");
            JSONArray earlyRemindTime = obj.getJSONArray("alarm");
            String res = CalendarReminderUtils.addCalendarEvent(id, title, location, startTime, endTime, earlyRemindTime);
            handler.complete(Integer.valueOf(res));
        } catch (Exception e) {
            e.printStackTrace();
            handler.complete(6005);
        }
    }
}

h5 端同步日歷核心代碼(通過裝飾器來限制調(diào)用接口的平臺)

class NativeMethods {
  // 同步到日歷
  @p()
  public syncCalendar(params: SyncCalendarParams) {
    const cb = (errCode: number) => {
      const msg = NATIVE_ERROR_CODE_MAP[errCode];

      Vue.prototype.$toast(msg);

      if (errCode !== 6000) {
        this.errorReport(msg, 'syncCalendar', params);
      }
    };
    dsbridge.call('syncCalendar', params, cb);
  }

  // 調(diào)用 native 接口出錯向 sentry 發(fā)送錯誤信息
  private errorReport(errorMsg: string, methodName: string, params: any) {
    if (window.$sentry) {
      const errorInfo: NativeApiErrorInfo = {
        error: new Error(errorMsg),
        type: 'callNative',
        methodName,
        params: JSON.stringify(params)
      };
      window.$sentry.log(errorInfo);
    }
  }
}

/**
 * @param {platforms} - 接口限制的平臺
 * @return {Function} - 裝飾器
 */
function p(platforms = ['android', 'ios']) {
  return (target: AnyObject, name: string, descriptor: PropertyDescriptor) => {
    if (!platforms.includes(window.$platform)) {
      descriptor.value = () => {
        return Vue.prototype.$toast(
          `當前處在 ${window.$platform} 環(huán)境,無法調(diào)用接口哦`
        );
      };
    }

    return descriptor;
  };
}

另外推薦一個筆者之前寫的一個基于安卓平臺實現(xiàn)的教學版 JSBridge[36],里面詳細闡述了如何基于底層接口一步步封裝一個可用的 JSBridge:

JSBridge 實現(xiàn)原理[37]

路由堆棧管理(模擬原生 APP 導航)

vue-page-stack[38]

vue-navigation[39]

vue-stack-router[40]

在使用 h5 開發(fā) app,會經(jīng)常遇到下面的需求:從列表進入詳情頁,返回后能夠記住當前位置,或者從表單點擊某項進入到其他頁面選擇,然后回到表單頁,需要記住之前表單填寫的數(shù)據(jù)。可是目前 vue 或 react 框架的路由,均不支持同時存在兩個頁面實例,所以需要路由堆棧進行管理。

其中 vue-page-stack 和 vue-navigation 均受 vue 的 keepalive 啟發(fā),基于 vue-router[41],當進入某個頁面時,會查看當前頁面是否有緩存,有緩存的話就取出緩存,并且清除排在他后面的所有 vnode,沒有緩存就是新的頁面,需要存儲或者是 replace 當前頁面,向棧里面 push 對應的 vnode,從而實現(xiàn)記住頁面狀態(tài)的功能。

而邏輯思維前端團隊的 vue-stack-router 則另辟蹊徑,拋開了 vue-router,自己獨立實現(xiàn)了路由管理,相較于 vue-router,主要是支持同時可以存活 A 和 B 兩個頁面的實例,或者 A 頁面不同狀態(tài)的兩個實例,并支持原生左滑功能。但由于項目還在初期完善,功能還沒有 vue-router 強大,建議持續(xù)關注后續(xù)動態(tài)再做決定是否引入。

本項目使用的是 vue-page-stack,各位可以選擇適合自己項目的工具。同時推薦幾篇相關文章:

【vue-page-stack】Vue 單頁應用導航管理器 正式發(fā)布[42]

Vue 社區(qū)的路由解決方案:vue-stack-router[43]

請求數(shù)據(jù)緩存

mem[44]

在我們的應用中,會存在一些很少改動的數(shù)據(jù),而這些數(shù)據(jù)有需要從后端獲取,比如公司人員、公司職位分類等,此類數(shù)據(jù)在很長一段時間時不會改變的,而每次打開頁面或切換頁面時,就重新向后端請求。為了能夠減少不必要請求,加快頁面渲染速度,可以引用 mem 緩存庫。

mem 基本原理是通過以接收的函數(shù)為 key 創(chuàng)建一個 WeakMap,然后再以函數(shù)參數(shù)為 key 創(chuàng)建一個 Map,value 就是函數(shù)的執(zhí)行結果,同時將這個 Map 作為剛剛的 WeakMap 的 value 形成嵌套關系,從而實現(xiàn)對同一個函數(shù)不同參數(shù)進行緩存。而且支持傳入 maxAge,即數(shù)據(jù)的有效期,當某個數(shù)據(jù)到達有效期后,會自動銷毀,避免內(nèi)存泄漏。

選擇 WeakMap 是因為其相對 Map 保持對鍵名所引用的對象是弱引用,即垃圾回收機制不將該引用考慮在內(nèi)。只要所引用的對象的其他引用都被清除,垃圾回收機制就會釋放該對象所占用的內(nèi)存。也就是說,一旦不再需要,WeakMap 里面的鍵名對象和所對應的鍵值對會自動消失,不用手動刪除引用。

mem 作為高階函數(shù),可以直接接受封裝好的接口請求。但是為了更加直觀簡便,我們可以按照類的形式集成我們的接口函數(shù),然后就可以用裝飾器的方式使用 mem 了(裝飾器只能修飾類和類的類的方法,因為普通函數(shù)會存在變量提升)。下面是相關代碼:

import http from '../http';
import mem from 'mem';

/**
 * @param {MemOption} - mem 配置項
 * @return {Function} - 裝飾器
 */
export default function m(options: AnyObject) {
  return (target: AnyObject, name: string, descriptor: PropertyDescriptor) => {
    const oldValue = descriptor.value;
    descriptor.value = mem(oldValue, options);
    return descriptor;
  };
}

class Home {
  @m({ maxAge: 60 * 1000 })
  public async getUnderlingDailyList(
    query: ListQuery
  ): Promise<{ total: number; list: DailyItem[] }> {
    const {
      data: { total, list }
    } = await http({
      method: 'post',
      url: '/daily/getList',
      data: query
    });

    return { total, list };
  }
}

export default new Home();

 

構建時預渲染

針對目前單頁面首屏渲染時間長(需要下載解析 js 文件然后渲染元素并掛載到 id 為 app 的 div 上),SEO 不友好(index.html 的 body 上實際元素只有 id 為 app 的 div 元素,真正的頁面元素都是動態(tài)掛載的,搜索引擎的爬蟲無法捕捉到),目前主流解決方案就是服務端渲染(SSR),即從服務端生成組裝好的完整靜態(tài) html 發(fā)送到瀏覽器進行展示,但配置較為復雜,一般都會借助框架,比如 vue 的 nuxt.js[45],react 的 next[46]

其實有一種更簡便的方式--構建時預渲染。顧名思義,就是項目打包構建完成后,啟動一個 Web Server 來運行整個網(wǎng)站,再開啟多個無頭瀏覽器(例如 Puppeteer[47]、Phantomjs[48] 等無頭瀏覽器技術)去請求項目中所有的路由,當請求的網(wǎng)頁渲染到第一個需要預渲染的頁面時(需提前配置需要預渲染頁面的路由),會主動拋出一個事件,該事件由無頭瀏覽器截獲,然后將此時的頁面內(nèi)容生成一個 HTML(包含了 JS 生成的 DOM 結構和 CSS 樣式),保存到打包文件夾中。

根據(jù)上面的描述,我們可以其實它本質(zhì)上就只是快照頁面,不適合過度依賴后端接口的動態(tài)頁面,比較適合變化不頻繁的靜態(tài)頁面。

實際項目相關工具方面比較推薦 prerender-spa-plugin[49] 這個 webpack 插件,下面是這個插件的原理圖。不過有兩點需要注意:

一個是這個插件需要依賴 Puppeteer,而因為國內(nèi)網(wǎng)絡原因以及本身體積較大,經(jīng)常下載失敗,不過可以通過 .npmrc 文件指定 Puppeteer 的下載路徑為國內(nèi)鏡像;

另一個是需要設置路由模式為 history 模式(即基于 html5 提供的 history api 實現(xiàn)的,react 叫 BrowserRouter,vue 叫 history),因為 hash 路由無法對應到實際的物理路由。(即線上渲染時 history 下,如果 form 路由被設置成預渲染,那么訪問 /form/ 路由時,會直接從服務端返回 form 文件夾下的 index.html,之前打包時就已經(jīng)預先生成了完整的 HTML 文件 )

本項目已經(jīng)集成了 prerender-spa-plugin,但由于和 vue-stack-page/vue-navigation 這類路由堆棧管理器一起使用有問題(原因還在查找,如果知道的朋友也可以告知下),所以 prerender 功能是關閉的。

同時推薦幾篇相關文章:

vue 預渲染之 prerender-spa-plugin 解析(一)[50]

使用預渲提升 SPA 應用體驗[51]

Webpack 策略

基礎庫抽離

對于一些基礎庫,例如 vue、moment 等,屬于不經(jīng)常變化的靜態(tài)依賴,一般需要抽離出來以提升每次構建的效率。目前主流方案有兩種:

一種是使用 webpack-dll-plugin[52] 插件,在首次構建時就講這些靜態(tài)依賴單獨打包,后續(xù)只需引入早已打包好的靜態(tài)依賴包即可;

另一種就是外部擴展 Externals[53] 方式,即把不需要打包的靜態(tài)資源從構建中剔除,使用 CDN 方式引入。下面是 webpack-dll-plugin 相對 Externals 的缺點:

  1. 需要配置在每次構建時都不參與編譯的靜態(tài)依賴,并在首次構建時為它們預編譯出一份 JS 文件(后文將稱其為 lib 文件),每次更新依賴需要手動進行維護,一旦增刪依賴或者變更資源版本忘記更新,就會出現(xiàn) Error 或者版本錯誤。

  2. 無法接入瀏覽器的新特性 script type="module",對于某些依賴庫提供的原生 ES Modules 的引入方式(比如 vue 的新版引入方式)無法得到支持,沒法更好地適配高版本瀏覽器提供的優(yōu)良特性以實現(xiàn)更好地性能優(yōu)化。

  3. 將所有資源預編譯成一份文件,并將這份文件顯式注入項目構建的 HTML 模板中,這樣的做法,在 HTTP1 時代是被推崇的,因為那樣能減少資源的請求數(shù)量,但在 HTTP2 時代如果拆成多個 CDN Link,就能夠更充分地利用 HTTP2 的多路復用特性。

不過選擇 Externals 還是需要一個靠譜的 CDN 服務的。

本項目選擇的是 Externals,各位可根據(jù)項目需求選擇不同的方案。

更多內(nèi)容請查看這篇文章(上面觀點來自于這篇文章):

Webpack 優(yōu)化——將你的構建效率提速翻倍[54]

手勢庫

hammer.js[55]

AlloyFinger[56]

在移動端開發(fā)中,一般都需要支持一些手勢,例如拖動(Pan),縮放(Pinch),旋轉(zhuǎn)(Rotate),滑動(swipe)等。目前已經(jīng)有很成熟的方案了,例如 hammer.js 和騰訊前端團隊開發(fā)的 AlloyFinger 都很不錯。本項目選擇基于 hammer.js 進行二次封裝成 vue 指令集,各位可根據(jù)項目需求選擇不同的方案。

下面是二次封裝的關鍵代碼,其中用到了 webpack 的 require.context 函數(shù)來獲取特定模塊的上下文,主要用來實現(xiàn)自動化導入模塊,比較適用于像 vue 指令這種模塊較多的場景:

// 用于導入模塊的上下文
export const importAll = (
  context: __WebpackModuleApi.RequireContext,
  options: ImportAllOptions = {}
): AnyObject => {
  const { useDefault = true, keyTransformFunc, filterFunc } = options;

  let keys = context.keys();

  if (isFunction(filterFunc)) {
    keys = keys.filter(filterFunc);
  }

  return keys.reduce((acc: AnyObject, curr: string) => {
    const key = isFunction(keyTransformFunc) ? keyTransformFunc(curr) : curr;
    acc[key] = useDefault ? context(curr).default : context(curr);
    return acc;
  }, {});
};

// directives 文件夾下的 index.ts
const directvieContext = require.context('./', false, /.ts$/);
const directives = importAll(directvieContext, {
  filterFunc: (key: string) => key !== './index.ts',
  keyTransformFunc: (key: string) =>
    key.replace(/^.//, '').replace(/.ts$/, '')
});

export default {
  install(vue: typeof Vue): void {
    Object.keys(directives).forEach((key) =>
      vue.directive(key, directives[key])
    );
  }
};

// touch.ts
export default {
  bind(el: HTMLElement, binding: DirectiveBinding) {
    const hammer: HammerManager = new Hammer(el);
    const touch = binding.arg as Touch;
    const listener = binding.value as HammerListener;
    const modifiers = Object.keys(binding.modifiers);

    switch (touch) {
      case Touch.Pan:
        const panEvent = detectPanEvent(modifiers);
        hammer.on(`pan${panEvent}`, listener);
        break;
      ...
    }
  }
};

另外推薦一篇關于 hammer.js 和一篇關于 require.context 的文章:

H5 案例分享:JS 手勢框架 —— Hammer.js[57]

使用 require.context 實現(xiàn)前端工程自動化[58]

樣式適配

postcss-px-to-viewport[59]

Viewport Units Buggyfill[60]

flexible[61]

postcss-pxtorem[62]

Autoprefixer[63]

browserslist[64]

在移動端網(wǎng)頁開發(fā)時,樣式適配始終是一個繞不開的問題。對此目前主流方案有 vw 和 rem(當然還有 vw + rem 結合方案,請見下方 rem-vw-layout 倉庫),其實基本原理都是相通的,就是隨著屏幕寬度或字體大小成正比變化。因為原理方面的詳細資料網(wǎng)絡上已經(jīng)有很多了,就不在這里贅述了。下面主要提供一些這工程方面的工具。

關于 rem,阿里無線前端團隊在 15 年的時候基于 rem 推出了 flexible 方案,以及 postcss 提供的自動轉(zhuǎn)換 px 到 rem 的插件 postcss-pxtorem。

關于 vw,可以使用 postcss-px-to-viewport 進行自動轉(zhuǎn)換 px 到 vw。postcss-px-to-viewport 相關配置如下:

"postcss-px-to-viewport": {
  viewportWidth: 375, // 視窗的寬度,對應的是我們設計稿的寬度,一般是375
  viewportHeight: 667, // 視窗的高度,根據(jù)750設備的寬度來指定,一般指定1334,也可以不配置
  unitPrecision: 3,  // 指定`px`轉(zhuǎn)換為視窗單位值的小數(shù)位數(shù)(很多時候無法整除)
  viewportUnit: 'vw', // 指定需要轉(zhuǎn)換成的視窗單位,建議使用vw
  selectorBlackList: ['.ignore', '.hairlines'], // 指定不轉(zhuǎn)換為視窗單位的類,可以自定義,可以無限添加,建議定義一至兩個通用的類名
  minPixelValue: 1, // 小于或等于`1px`不轉(zhuǎn)換為視窗單位,你也可以設置為你想要的值
  mediaQuery: false // 媒體查詢里的單位是否需要轉(zhuǎn)換單位
}

下面是 vw 和 rem 的優(yōu)缺點對比圖:

關于 vw 兼容性問題,目前在移動端 iOS 8 以上以及 Android 4.4 以上獲得支持。如果有兼容更低版本需求的話,可以選擇 viewport 的 pollify 方案,其中比較主流的是 Viewport Units Buggyfill[65]

本方案因不準備兼容低版本,所以直接選擇了 vw 方案,各位可根據(jù)項目需求選擇不同的方案。

另外關于設置 css 兼容不同瀏覽器,想必大家都知道 Autoprefixer(vue-cli3 已經(jīng)默認集成了),那么如何設置要兼容的范圍呢?推薦使用 browserslist,可以在 .browserslistrc 或者 pacakage.json 中 browserslist 部分設置兼容瀏覽器范圍。因為不止 Autoprefixer,還有 Babel,postcss-preset-env 等工具都會讀取 browserslist 的兼容配置,這樣比較容易使 js css 兼容瀏覽器的范圍保持一致。下面是本項目的 .browserslistrc 配置:

iOS >= 10  //  即 iOS Safari
Android >= 6.0 // 即 Android WebView
last 2 versions // 每個瀏覽器最近的兩個版本

最后推薦一些移動端樣式適配的資料:

rem-vw-layout[66]

細說移動端 經(jīng)典的 REM 布局 與 新秀 VW 布局[67]

如何在 Vue 項目中使用 vw 實現(xiàn)移動端適配[68]

表單校驗

async-validator[69]

vee-validate[70]

由于大部分移動端組件庫都不提供表單校驗,因此需要自己封裝。目前比較多的方式就是基于 async-validator 進行二次封裝(elementUI 組件庫提供的表單校驗也是基于 async-validator ),或者使用 vee-validate(一種基于 vue 模板的輕量級校驗框架)進行校驗,各位可根據(jù)項目需求選擇不同的方案。

本項目的表單校驗方案是在 async-validator 基礎上進行二次封裝,代碼如下,原理很簡單,基本滿足需求。如果還有更完善的方案,歡迎提出來。

其中 setRules 方法是將組件中設置的 rules(符合 async-validator 約定的校驗規(guī)則)按照需要校驗的數(shù)據(jù)的名字為 key 轉(zhuǎn)化一個對象 validator,value 是 async-validator 生成的實例。validator 方法可以接收單個或多個需要校驗的數(shù)據(jù)的 key,然后就會在 setRules 生成的對象 validator 中尋找 key 對應的 async-validator 實例,最后調(diào)用實例的校驗方法。當然也可以不接受參數(shù),那么就會校驗所有傳入的數(shù)據(jù)。

import schema from 'async-validator';
...

class ValidatorUtils {
  private data: AnyObject;
  private validators: AnyObject;

  constructor({ rules = {}, data = {}, cover = true }) {
    this.validators = {};
    this.data = data;
    this.setRules(rules, cover);
  }

  /**
   * 設置校驗規(guī)則
   * @param rules async-validator 的校驗規(guī)則
   * @param cover 是否替換舊規(guī)則
   */
  public setRules(rules: ValidateRules, cover: boolean) {
    if (cover) {
      this.validators = {};
    }

    Object.keys(rules).forEach((key) => {
      this.validators[key] = new schema({ [key]: rules[key] });
    });
  }

  public validate(
    dataKey?: string | string[]
  ): Promisestring | string[] | undefined> {
    // 錯誤數(shù)組
    const err: ValidateError[] = [];

    Object.keys(this.validators)
      .filter((key) => {
        // 若不傳 dataKey 則校驗全部。否則校驗 dataKey 對應的數(shù)據(jù)(dataKey 可以對應一個(字符串)或多個(數(shù)組))
        return (
          !dataKey ||
          (dataKey &&
            ((_.isString(dataKey) && dataKey === key) ||
              (_.isArray(dataKey) && dataKey.includes(key))))
        );
      })
      .forEach((key) => {
        this.validators[key].validate(
          { [key]: this.data[key] },
          (error: ValidateError[]) => {
            if (error) {
              err.push(error[0]);
            }
          }
        );
      });

    if (err.length > 0) {
      return Promise.reject(err);
    } else {
      return Promise.resolve(dataKey);
    }
  }
}

阻止原生返回事件

開發(fā)中可能會遇到下面這個需求:當頁面彈出一個 popup 或 dialog 組件時,點擊返回鍵時是隱藏彈出的組件而不是返回到上一個頁面。

為了解決這個問題,我們可以從路由棧角度思考。一般彈出組件是不會在路由棧上添加任何記錄,因此我們在彈出組件時,可以在路由棧中 push 一個記錄,為了不讓頁面跳轉(zhuǎn),我們可以把跳轉(zhuǎn)的目標路由設置為當前頁面路由,并加上一個 query 來標記這個組件彈出的狀態(tài)。

然后監(jiān)聽 query 的變化,當點擊彈出組件時,query 中與該彈出組件有關的標記變?yōu)?true,則將彈出組件設為顯示;當用戶點擊 native 返回鍵時,路由返回上一個記錄,仍然是當前頁面路由,不過 query 中與該彈出組件有關的標記不再是 true 了,這樣我們就可以把彈出組件設置成隱藏,同時不會返回上一個頁面。

APP開發(fā) 網(wǎng)站開發(fā) 產(chǎn)品設計 微信公眾號 APP開發(fā)公司 用戶體驗 APP運營 微信小程序 產(chǎn)品經(jīng)理 網(wǎng)站設計
叙永县| 多伦县| 昌吉市| 安福县| 兴城市| 新宾| 桃江县| 馆陶县| 加查县| 大田县| 林周县| 高邮市| 长泰县| 临邑县| 莲花县| 塔河县| 罗山县| 枞阳县| 延庆县| 丹东市| 棋牌| 金秀| 图们市| 商城县| 高青县| 云和县| 吴桥县| 武威市| 炎陵县| 齐齐哈尔市| 股票| 湟中县| 西平县| 东乡县| 呼玛县| 鲁山县| 屏边| 汉中市| 崇阳县| 雷波县| 浦县|