小程序進階之路:跨平臺開發避坑指南

簡介:小程序的開發不可避免的會面臨跨平臺開發的問題。各小程序平臺有哪些特點?如何處理各平臺的差異?本文分享淘票票在跨平臺開發上的經驗總結,包含了技術演進及差異控制策略,希望能幫助同學們提前避坑。

image.png
在 2019 年,阿里巴巴文娛的淘票票幾乎涉足了當時市面上所有的小程序,其中在不少平臺上,我們是阿里第一批喫螃蟹的技術團隊。回顧過往,我們做過很多嘗試,也踩過很多坑。

我們特別整理了支付寶小程序、百度小程序、字節跳動小程序、快應用的開發經驗,希望爲你帶來啓發。

一 支付寶小程序

支付寶內的淘票票用戶主要是以購票爲主,工具屬性比較明顯。淘票票入口衆多,均引導跳轉到小程序,引導用戶在小程序內進行購票、娛樂消費、收藏、添加到首頁/桌面、分享等行爲。

淘票票支付寶小程序,經歷了近一年的起步開發,以及一年多的版本迭代,業務開發涵蓋了購票、視頻、資訊、社區等多個場景。

image.png

1 小程序開發

1) 在覈心業務中慎用 web-view

實際項目線上運行情況及用戶反饋表明:web-view 初始化較慢、易受網絡干擾、性能差(對比離線包及普通 H5 頁面)、問題較多,建議不要在覈心業務中使用 web-view 進行承載。

2) 自有城市體系與支付寶城市組件的適配技巧

由於支付寶的城市組件是基於自身城市體系的,淘票票擁有獨立的城市體系,所以需要城市選擇組件適配不同城市體系的場景。經過幾輪推動迭代,淘票票線上已使用城市選擇組件,已支持複寫當前定位城市、歷史訪問城市、熱門城市、城市列表信息等。使用my.chooseCity、my.onLocatedComplete、my.setLocatedCity 三個 JSAPI 可實現對應效果。

3) 如何實現沉浸式效果(透明導航欄)?

  • 首先在 page.json 配置 “transparentTitle” 爲 “auto” 屬性,開啓沉浸式佈局。
  • 其次,頁面佈局適配沉浸式頂部透明導航欄即可,通過 my.getSystemInfo 獲取 titleBarHeight 及 statusBarHeight 可計算出頂部透明高度。

注意:Android 5.0 以下由於不支持沉浸式狀態欄,所以頁面會從狀態欄下開始佈局。

4) 小程序 tabBar 換膚、紅點

主要使用的JSAPI及event:my.setTabBarStyle、my.setTabBarItem、page.onTabItemTap,參數參考官方文檔即可。注意事項如下:

  • 小程序觸發 relaunch 時,tabBar 的樣式會被清除,需要再次設置,目前建議在 app.onShow 裏多次觸發設置邏輯。
  • 儘量使用本地圖片,在線圖片有個下載的過程,體驗不太好,且弱網下圖片載入可能失敗。
  • my.setTabBarItem 的參數每一項均需要賦值,否則 Android 可能會報 “invalid parameter”。

2 小程序開發注意事項

  • 不要使用 tnpm 安裝依賴,tnpm 軟連接目前支持有問題。
  • devDependencies 和 dependencies 需要分開,將 devDependencies 移到項目代碼外層,否則會額外增加包大小。
  • 設置 transparent/pullRefresh 等 window 配置時,跳轉別的頁面會被繼承,需要在 app.json 初始化此類配置信息規避。
  • web-view 裏面的頁面會失去下拉刷新、resume 等特性。
  • Android 低版本不支持 sticky 屬性。
  • 某個值控制 dom 是否渲染,下次更新時此值若爲 undefined 時不會銷燬掉會被忽略掉。
  • window.atob、window.btoa 需要第三方庫來替代。
  • lodash 某些方法不能直接使用,因爲小程序構建時無 global 對象。

3 小程序監控

使用阿里雲的 ARMS 平臺,參考官方文檔接入即可。

  • 優點:有實時大盤,排查用戶日誌方便,上報更自由、豐富。
  • 缺點:有接入成本、需要開發,增加包大小,且要收費。

4 小程序權限

小程序有權限管控,無論是申請 JSAPI 權限還是 H5 域名配置,都是需要打新的包上傳才能生效。

二 百度小程序

1 背景

以微信小程序爲藍本的百度小程序,也同樣具備相似的商業定位。依賴百度這樣的老牌搜索門戶,百度小程序更加偏向目的性強的搜索熱詞進行小程序的關聯調起和互動,這也是百度小程序和其他小程序的區別。

由此,我們在 2018 年底進行了百度小程序的開發工作,由於在前期積累了小程序開發經驗,百度小程序的開發更加的平穩和快速,不到一個月就上線運營了。

2 應用場景

百度小程序入口:

image.png

三種入口:百度App搜索關聯、百度貼吧關聯、其他百度生態搜索關聯。

3 開發實戰

下面是淘票票百度小程序開發過程中遇到的坑和總結:

1)基礎開發

百度小程序的開發與微信、頭條小程序的開發方式和框架概念非常相似,都屬於前端開發的一塊子集,主要可以分爲 4 塊來構建百度小程序的頁面:

  • 第一塊:HTML。構建頁面框架:使用 xxx.swan 文件進行頁面元素框架的搭建,具有獨特的 HTML 標籤如 view,scroll-view 等。
  • 第二塊:CSS。管理頁面樣式:使用 xxx.css 文件進行頁面樣式的管理,基本的 CSS 的樣式都大部分支持。
  • 第三塊:JS。編寫頁面邏輯:使用 xxx.js 文件進行頁面邏輯的書寫,小程序具有其獨特的生命週期管理方法。
  • 第四塊:JSON。組件註冊:百度小程序支持通過組件的方式進行頁面的搭建,通過在 xxx.json 中註冊組件供頁面使用。

2)template 模板使用

與其他的小程序相同,百度小程序也提供了模板 template 的能力,使用模板可以提高工程化和代碼可維護性,開發者可以在模板中設計代碼片段,向外暴露接口注入外界變量之後,可以在合適的時機去使用該代碼片段。

但是在百度小程序使用 template 使用時,需要注意傳遞數據時需要使用 {{{ }}} 三層花括號包裹對象,否則數據注入時會出現異常。

百度小程序的 template 的使用:

<template is="xxx" data="{{{item}}}"/>

作爲對比,頭條、微信小程序 template 需要兩層花括號:

<template is="xxx" data="{{item}}"/>

3)組件屬性的 observer 使用

在使用組件(Component)是大概率會使用到屬性的 observer 方法,當屬性被改變時會執行屬性對應的 observer 方法,此處需要注意在使用 observer 方法時,避免使用下劃線開頭的方法名,可能會造成 observer 方法的循環調用問題。

或者當發現 properties 中的 observer 方法被循環調用時,檢查一下 observer 綁定的方法是否有下劃線。方法命名移除下劃線,大概率可以解決循環調用問題。

會出現 observer 循環調用的情況:

isShowLoadMore: {          
  type: Boolean,          
  value: false,          
  observer: '_isshowChange'      
},

推薦的寫法:

isShowLoadMore: {          
  type: Boolean,          
  value: false,          
  observer: 'isshowChange'      
},

4)scroll-view 的使用

在使用 scroll-view 的開發過程中,對存在多個可滑動區域的頁面且其中一個滑動區域爲 fixed 樣式時,iOS 機型會偶現 scroll-view 空白的問題。

可能存在異常的頁面佈局如下:

<view class='頭部組件' />
<scroll-view class='可滑動區域1'  />
<view class='可滑動區域2' />

其中 “可滑動區域 2” 爲依賴內容撐開的頁面 View,當內容到達一定長度時,頁面 View 會提供滑動能力。如果使用上述寫法可能會出現 scroll-view 空白的問題。

推薦的寫法:

<view class='頭部組件' />
<scroll-view class='可滑動區域1'  />
<scroll-view class='可滑動區域2'  />

5)小程序 DSL 頁與 WebView 頁的登錄流程

小程序的頁面實現方式可以分爲兩種:一種爲小程序原生的頁面;另外一種是使用 WebView 組件,將 H5 頁面展示在小程序中。處理兩種頁面的登錄時一般是先進行 DSL 頁登錄(小程序原生頁面),完成 DSL 頁登錄後,再進行 H5 容器頁的登錄。

a) DSL 頁登錄

先進行小程序的登錄授權,獲取到小程序的登錄憑證,拿着登錄憑證去自己的業務服務端獲取真實的小程序登錄信息,當開發者完成上述流程之後,將登錄態信息加密後存儲在本地。下次進行需要登錄校驗的頁面時,進行本地登錄信息的登錄校驗,則 DSL 頁的登錄流程就完成了。

b) WebView 容器頁登錄

由於百度小程序無法操作到 WebView 容器的 cookie 信息,所以在 WebView 容器頁進行登錄時,勢必要進行一次從服務端獲取登錄 cookie 的過程。目前可以在進入需要登錄校驗登錄的 WebView 容器頁之前,先發起獲取 cookie 的服務端請求,服務端處理好用戶登錄信息校驗之後就可以提供一個同步 cookie 的專用頁面。當接口返回鏈接之後,小程序的 WebView 容器需要做的就是訪問這條鏈接將服務端返回的 cookie 同步到 WebView 容器中,這樣 WebView 容器就具備了可供校驗的登錄信息。

完成上述頁面的登錄操作之後,小程序登錄流程就結束了。

4 百度小程序總結

本文着重描述的是開發過程中大概率會遇到的問題和解決方案,最好結合官方文檔一起查看。

三 字節跳動小程序

1 背景

字節跳動小程序是基於字節跳動全產品矩陣開發, 與圖文、視頻等場景有着天然的搭配性,帶動小程序分發,由內容爲小程序帶量以及裂變。作爲一個大流量且高度活躍的平臺,具有很大用戶增量挖掘空間。

對於頭條系應用,同一小程序可以同步上線多個宿主端,目前已開放今日頭條、抖音、頭條極速版。無論是抖音,還是今日頭條,都屬於內容分發平臺,相比公衆號讀者,抖音用戶相對更年輕,而頭條則擁有大量三四線城市讀者,這正好契合了電影作爲內容消費的特質,幫助淘票票更好的拉動下沉用戶。基於頭條、抖音平臺自身的優勢,我們在 2019 年上線了淘票票字節跳動小程序。

2 應用場景

  • 今日頭條的六個主要場景:
  • 信息流推薦
  • 搜索直達
  • 頭條號掛載小程序
  • 分享
  • 中心化入口
  • 留存入口

image.png

抖音的四個主要場景:

  • 小視頻掛載
  • 企業號主頁
  • 搜索展示
  • 留存入口

image.png

3 基礎介紹

字節跳動小程序基本開發思路類似於前端開發,並增強調用大量端能力,性能體驗優於普通 Web 。上層架構基於 JS 開發,可以輔助開發者進行良好得開發。框架結構和開放式類似於支付寶小程序、微信小程序和百度小程序。

目錄結構:主要分爲以下幾類的文件:

  • .json 爲後綴的 JSON 配置文件,這個文件配置了小程序所有頁面的路徑和界面展現樣式等。
  • .ttml 結尾的模板文件,用來描述當前這個頁面的文件結構,類似於網頁中的 HTML 文件。
  • .ttss 結尾的樣式文件,描述頁面樣式,類似於網頁中的 CSS 文件。
  • .js 結尾的 JS 文件,處理這個頁面和用戶的交互。

image.png

四 快應用卡片

1 概述

當前,基於超級 APP 推出的各種小程序,對手機廠商的分發能力及話語權有明顯削弱趨勢。因此國內各手機廠商在推出快應用後,也逐漸對外開放手機負一屏的能力,爲快應用及其他服務提供直接的入口。

2 卡片類型

快應用的卡片類型可以分爲:應用類型的卡片、服務類型的卡片和其它類型的卡片。

  • 應用類型的卡片:是用戶訂閱的一種卡片,內容相對固定。
  • 服務類型的卡片:針對用戶關心數據的狀態,內容實時變更。
  • 其它類型的卡片:自定義卡片,根據實現對應內容展示及跳轉。

3 應用卡片的具體接入

卡片的開發基於快應用,所使用的 API 是快應用的子集,部分 API 不能在卡片中使用。目前已知的 vivo,OPPO,NUBIA 都需要卡片的開發不依賴主 rpk,也就是需要保證卡片能脫離主 rpk 獨立渲染,爲保證卡片的獨立渲染,不能使用 this.$app 相關對象及文件 app.ux 中聲明的工具類或生成的對象。

1)卡片的初始化配置

a) 配置文件

所有的卡片都需要和快應用中聲明頁面一樣在 manifest.json 中聲明。具體是在 router.widgets 中配置,各廠商之間有部分差異,但差異不大。

b) 卡片配置文件注意事項

  • widgets 中配置的 key 值請使用路徑名字,如果路徑爲兩級(例:/A/B),則 key 值配置爲 "A/B",且該值最終對應到廠商後臺上傳卡片時填入的卡片路徑,基於此廠商才能正確解析到上傳到聯盟上統一的快應用包中對應的卡片。
  • 卡片的屬性 features 中需要聲明該卡片會用到的系統 API,這些 API 在外層應用的 features 中已經聲明過,此處需要再次聲明,否則不能使用。

2)應用類型卡片接入(以 vivo 爲例)

image.png

a) 卡片的聲明

在 manifest.json 中的除了上面提到的配置之外,對於卡片需要注意卡片屬性中的以下字段:

  • params 字段用來配置卡片更爲詳細的參數,及特有的支持參數,需要按照文檔進行配置。
  • targetManufactorys 爲對應廠商適配標明該卡片匹配的廠商,具體參看文檔。

b) 卡片的開發的不同點(所使用的 API 及組件爲其子集具體參看官方文檔)

  • vivo 卡片是單獨工程,不能配置在快應用工程中,需要另外建立新的工程。
  • 對應包打出也需要單獨配置和主快應用不相同,需要用到 vivo 給出的相關工具。
  • 卡片有單獨設置主題的功能。
  • 卡片有摺疊功能。
  • 卡片視覺稿由內容提供方給出。
  • 卡片開發只需開發內容區域,title 區域無需開發(由 vivo 負一屏容器完成繪製)同時意味着下半部分的圓角需要自己繪製。

上傳卡片包時需要提供對應的 icon。

c) 卡片調試

卡片調試需要使用 vivo 方提供的工具打出來的 rpk 文件,同時需要使用 vivo 方提供的專用內核及容器,具體按照文檔執行即可。

d) 卡片提交

首先需要完成自測,自測之後需要使用 vivo 提供的專用打包工具,打包之後到 Jovi 後臺地址提交,同時需要提供一個應用圖標。

4 負一屏服務類型卡接入

以下以 OPPO 服務卡接入爲例:

觸發場景:用戶在淘票票快應用中購票之後,在影片上映前的固定時間內觸發該卡片內容展示,進而提醒用戶取票,即消息觸發場景。
image.png

1)OPPO服務卡卡片的聲明

在 manifest.json 中的 router 字段中添加 widgets 字段,並在該字段中添加對應的配置,與 OPPO 應用卡片完全相同。

2) 卡片開發

OPPO 服務卡開發涉及用戶關心數據狀態改變觸發卡片的場景,因此整體需要解決以下幾個問題:首先是觸發時機問題,然後是要確認觸發的卡片 ID,還要解決要觸發哪一個 OPPO 用戶。

3)OPPO 服務卡整體開發流程

前提:要開通 OPPO 賬號服務,保證在快應用中能夠拿到 OPPO 當前登錄的用戶的授權碼。

a) 賬號綁定,即 OPPO 賬號和快應用賬號的綁定

賬號綁定的入口:該入口由 OPPO 負一屏容器統一提供,位置如下圖左:

image.png

該入口對應一個快應用內的綁定頁面。

b) 綁定頁面開發,該頁面是快應用頁面,主要提供綁定功能

作用是讓內容商服務端知道自己的賬號和 OPPO 測的對應關係,及換取發消息到 OPPO 端時所需要的 TOKEN 值。

c) 觸發對應 OPPO 用戶負一屏的卡片

內容服務商在用戶關心數據變更時,觸發推送消息到 OPPO 服務端,該消息按 OPPO 文檔約定,帶上對應的 TOKEN 值,要觸發的卡片 ID,消息內容,要觸發的時機及時長,OPPO 服務端會根據該 TOKEN 找到對應的用戶下發消息,並在需要的時機拉起對應 ID 的卡片。

d) 卡片消失

由發送消息中定義的卡片時長決定,展示時間到點後,負一屏容器會自動移除該卡片。

e) 調試

  • 首先,需要 OPPO 服務端參與
  • 其次,需要 OPPO 提供負一屏開發環境版本,以保證 OPPO 服務端日常環境的數據能夠到達。
  • 再次,需要提供初步測試完成包含服務卡的 rpk 到 OPPO 側,把該 rpk 配置到 OPPO 對應的環境。

f) 提交市場

測試完成可以在 OPPO 後臺提交該卡片,同時同步正式生成的卡片標示到自己的服務端用來推送消息使用。

g) 綜上涉及各端的開發工作如下:

  • 首先,涉及服務端賬號綁定開發,TOKEN 刷新維護,觸發的消息推送到 OPPO。
  • 其次,涉及前端的服務卡片開發以及綁定頁面開發。
  • 涉及其他:OPPO 賬號服務開通。

5 踩坑記錄

  • 負一屏的 UA 和快應用中不同如果有與 UA 相關的配置需要注意。
  • 對於調試時更新了 rpk 之後,實際打開對應更改沒有體現時,可以嘗試清除對應卡片容器的 cache,同時保證該容器有相應的讀取存儲的權限。
  • 對於同一個業務,由於各廠商適配不同及平臺不同,需要多處代碼編寫,但基本業務邏輯基本一致,唯一不同是 UI 展示,所以在一開始還是需要抽離數據邏輯,不同廠商給不一樣的 UI 展示即可。

四 淘票票小程序構建演進

我們做了很多的小程序,對多端同構也做了一些嘗試。

1 從一端到多端

1) 起步:小程序原生開發

2018 年,隨着小程序平臺爆發,我們首次踏出了淘寶域內,進行了頭條小程序的嘗試。這次嘗試,使用的是原生的小程序 DSL 語法編寫。爲了方便複用已有的 H5 樣式,加入了 Gulp,用來編譯 Less。

這種開發方式輕快,但是同時也暴露出了很多問題:

  • 包體積很難控制。
  • 原生 DSL 沒有任何複用性,並且需要重新學習。
  • 無法使用 NPM,一些很常用的社區包,團隊基礎工具鏈無法使用。
  • 機型兼容性不好,沒有 babel 支持。

2) 摸索:兩個端,一套代碼?

在開發百度小程序的過程中,吸取了第一次的教訓,加入了 webpack 來做一層編譯,一是解決了包引入問題,二是加入了 babel 插件,解決 JS 兼容性問題,開啓 CommonChunk 插件,解決包大小問題。

總體上,從輸出一端變爲輸出兩端,所以出現一些差異。對這些差異,編寫了一個插件,對業務層抹平。比如微信端引入 index.wx.js,頭條端引入 index.tt.js。

脫離了刀耕火種,開發效率明顯提升,但是還不夠好,視圖層還是兩份,而且以後每新增一端就要新拉出來一份。

3) 優化:走向社區,跨多端

在開發頭條和百度小程序時,業內也已經有了在小程序 DSL 上封裝的框架,但是當時看都不是很成熟,基本都是專注於一個平臺,沒有什麼跨端能力,就沒有用到生產環境,而是持續關注更新近況。

2019 年進軍微信小程序,再次看市面上的框架,發展的很快,同時也注意到跨端開發這個需求點,選擇了 Taro 作爲主力框架。這種框架橫評就不展開了,市面上很多,簡單說幾個選擇 Taro 的原因:社區相對活躍、支持漸進式切換、TS、react like。

a) 平滑遷移

Taro 支持漸進式切換,也就是 Taro 和 DSL 混寫的能力,所以遷移成本可以接受。我們先將首頁 Taro 化,後面慢慢迭代將所有的頁面都切換爲了 Taro,這裏值得一提的是,Taro 的跨端差異化處理和我們之前的處理思路一樣,因此 Util 遷移起來幾乎 0 成本,成本主要集中在視圖層。

b) 好處是什麼,缺點在哪裏

使用 Taro 的好處是解決了我們之前遇到的主要問題,是一個一攬子解決方案。

同時這種上層框架在擴展新端時成本低,機動性很高,框架提供了新平臺包,適配成本低。

當然也遇到了一些新的問題,比較嚴重的是調試,因爲代碼被轉譯過一次,同時不支持 Soucemap,導致 debug 時體驗很差。

2 多端差異

多端必然會有一些差異,業務的差別、端上 API 的差異等,比如微信上的分享能力,抖音上的抖音拍攝器,百度的 feed 流等。最終落在業務上,差別可以分爲三部分,輸出不同的頁面、不使用同的組件(有的端使用原生組件),細到不同的邏輯。

1) 輸出不同的頁面

在使用 Taro 時發現不支持,想到可以使用 babel-preval 來編譯時輸出頁面配置,這樣包體積也不會受影響,最後我們也反哺回社區。

使用不同的組件,不同的邏輯。根據端上不同的組件我們使用的最多的是多態模式,底層組件對外暴露相同的接口,端上調用時不需要考慮端上的差異,在 import 層會根據不同的端來引入不同的具體組件。

2) 端上邏輯

如果是一些簡單的邏輯差異,可以直接使用環境變量來做控制,走不同的邏輯。這種方式針對小一些的邏輯還可以,不過這種代碼一多,就不容易維護。

3) 針對維護性的建議

這裏推薦幾種維護性比較強的差異處理方式:

設計組件時插件化:比如路由,不同端在跳轉前後需要有一些不同的操作,實現了插件化後,每一個端只需掛載不同的插件即可。

配置抽離:針對一些端上不同的配置,比如一些文案、固定內容等,可以抽離到一個統一的地方維護,可以少很多 ifelse 邏輯。

用好函數 hook:針對不同端相同的邏輯放在函數中,有差異的邏輯可以單拆函數作爲 beforeHook 和 afterHook。

3 項目維護策略

項目輸出多端後,每次改動迴歸就成爲一個比較重要的問題,如何保證自己的代碼不會再其它端上出問題?每次改一個小程序其他都要立即迴歸嗎?如何快速整理其他端的改動?下面針對多端項目的維護總結了一些經驗。

1) 單測

針對核心邏輯編寫測試,unit test 和 snapshot test,我們內部維護了一個針對端上 API 的 mock 測試庫,整個測試可以在 node 環境中運行,保證運行效率。

2) Commit 規範

指定一個 commit message 規範,可一眼看出你在做什麼,在改哪一個端,以及後面迴歸策略時用到。

3) git-hook

image.png

使用 githook 能保證上面的規範能夠真正的遵循,保證每次提交的質量。pre-commit 時跑一下 Eslint,然後校驗一下 commit-message 是否符合規則,最後 push 時會跑一次整體的測試。

4) 多端的迴歸策略

沒有做 E2E 的主要原因是小程序限制,case 編寫難度比較大,並且維護性低,無法自動化。

目前我們是人工迴歸的,如何保證代碼不會再其他端上出問題?難道每一次改一個小程序其它都要立即迴歸嗎?回答下這兩個問題,編寫代碼時考慮影響面,提交時提供足夠的改動信息,合併時主要測當前端即可,不需要回歸所有端。等另一個端需要發佈時,拉出版本的 commit-message,然後梳理出變更範圍,在該端做迴歸即可。這樣做減少對測試的集中消耗,保障質量。

4 展望

以上是我們對跨端項目的經驗總結,包含技術演進歷史以及差異控制策略。跨端項目的難點就是處理差異。

  • 端上能力的參差不齊、業務針對不同場景的定製,一旦控制不好,整個人項目的維護性就會大大降低。
  • 業務方面要思考清楚,不同的端,是相似的更多,還是差異多。
  • 框架方面,最近看到有開發者已經給 W3C 提小程序的白皮書了,總體朝着良性方向發展,這是一個好的開始,期待能夠標準化小程序框架。

原文鏈接:https://developer.aliyun.com/article/765361?

版權聲明:本文中所有內容均屬於阿里雲開發者社區所有,任何媒體、網站或個人未經阿里雲開發者社區協議授權不得轉載、鏈接、轉貼或以其他方式複製發佈/發表。申請授權請郵件[email protected],已獲得阿里雲開發者社區協議授權的媒體、網站,在轉載使用時必須註明"稿件來源:阿里雲開發者社區,原文作者姓名",違者本社區將依法追究責任。 如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至:[email protected] 進行舉報,並提供相關證據,一經查實,本社區將立刻刪除涉嫌侵權內容。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章