You are on page 1of 31

Chrome extension 實作

⼀、⽤途
增加瀏覽網⾴時的額外功能
推送額外的訊息給使⽤者
改變網⾴的內容
網⾴機器⼈
其他
延伸閱讀
http://tonytonyjan.net/2012/05/25/get-start-with-chrome-extension/

⼆、參考案例
朕知道了
https://chrome.google.com/webstore/detail/%E6%9C%95%E7%9F%A5%E9%
81%93%E4%BA%86/igoniplmkmcggoogbmkmdmnmkablnmnm?hl=zh-TW
inLiveTW
https://chrome.google.com/webstore/detail/inlivetw/ hcffinobmpdchcoapdeoin
hdmlihiok
柯⽂哲關鍵字⼩幫⼿
https://chrome.google.com/webstore/detail/%E6%9F%AF%E6%96%87%E5%
93%B2%E9%97%9C%E9%8D%B5%E5%AD%97%E5%B0%8F%E5%B9%AB
%E6%89%8B/moldcnjkjmceelkphffaeoodjknbn hn

三、環境基礎
需先安裝 Chrome 瀏覽器
Chrome extension 是以⼀般的網⾴程式語⾔:HTML、CSS、Javascript 即可撰寫,
設定檔部分為 JSON 格式
四、範例實作 (⼀)
範例網址
https://github.com/goooooooogle/chrome-extension-example/tree/master/hello

範例題⽬
建⽴⼀個最基礎的 Chrome extension,並了解其運作架構與撰寫⽅式。
在本範例中,將撰寫⼀個 extension 的下拉 popup,並在其中顯⽰「Hello World!」⽂字
參考教學網址
. https://developer.chrome.com/extensions/getstarted
. http://blog.longwin.com.tw/2014/01/chrome-extensions-plugin-develop-2014/
本範例使以上述網址提供之檔案架構改寫⽽成。原案例是以取得 之 ,取得最新之 flickr api
照⽚,並提供給使⽤者,由於有 之問題,故在本範例中將此部份去除,僅留下最基
api key
本的 popup呈現
基礎架構
請先在任意處創建⼀個放置 extension files 的資料夾,並在其中創建如下所⽰之檔案(或
是直接從上⽅參考教學網址中下載範例檔案),在本範例中將資料夾名稱命名為
「hello」:
/hello
  ﹂ icon.png        // 套件的 icon
  ﹂ manifest.json   // 控制套件的相關參數都寫在這裡
  ﹂ popup.html      // popup 的內容

開始撰寫
依照上述架構完成後,請將各檔案內容如下⽅所述撰寫:
1. 套件 Icon的部份可以設置為⾃⼰喜歡的任意圖⽚,尺⼨為 19x19px,這個圖⽚會顯⽰在
套件列上,如下所⽰:
 
2. manifest.json 內容設置如下:
{
  "manifest_version": 2,

  "name": "Chrome Extension Example",


  "description": "This is my first Chrome extension",
  "version": "1.0",

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  }
}
關於 manifest 的部份想了解更多請參考此網址
3. popup.html ,如同⼀般 HTML 的撰寫,在此將內容設置如下:
<!doctype html>
<html>
  <head>
    <title>Chrome Extension Example</title>
    <style>
      body {
        width: 200px;
        overflow-x: hidden;
      }
    </style>
  </head>
  <body>
    <h2>Hello World!</h2>
    <p>This is my first Chrome extension</p>
  </body>
</html>

⾄此已完成最基礎的 Chrome extension 撰寫。


安裝套件
. 開啟 Chrome > 更多⼯具 > 擴充功能
. 勾選右上⾓的「開發⼈員模式」

. 點選「載⼊未封裝擴充功能」按鈕 > 選擇你的套件資料夾 > 確認


 

. 完成後會出現在下⽅擴充功能清單裡
 

. Chrome 將⾃動顯⽰套件於瀏覽器右上⾓的套件區域裡,點擊 icon 會⾃動顯⽰剛才


popup.html 的內容

 
. 恭喜你完成了⼈⽣第⼀⽀ Chrome extension 的撰寫

五、範例實作 (⼆)
範例網址
https://github.com/goooooooogle/chrome-extension-example/tree/master/unfold-
links

範例題⽬
建⽴⼀個 Chrome extension,於 popup 中顯⽰常⽤⼯具的連結清單,並使⽤外部的
stylesheet 樣式

基礎架構
本範例之基礎架構與範例(⼀)相同。在本範例中將資料夾名稱命名為「unfold-links」:
/unfold-links
  ﹂ icon.png
  ﹂ manifest.json
  ﹂ popup.html

開始撰寫
依照上述架構完成後,請將各檔案內容如下⽅所述撰寫:
. 套件 Icon的部份與範例(⼀)相同
. manifest.json 內容設置如下:
{
  "manifest_version": 2,

  "name": "unfold Tools",


  "description": "提供常用的開發工具清單",
  "version": "1.0",

  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html"
  },

  "permissions": [
    "http://*maxcdn.bootstrapcdn.com*", "webRequest"
  ]
}
在這個範例中,我們將使⽤外連的 bootstrap stylesheet,以加快開發速度,因此需另
外設定外部連結的存取權限
. popup.html ,在此將內容設置如下:
<!doctype html>
<html class="no-js" lang="zh-TW">
  <head>
    <meta charset="utf-8">
    <title>unfold Tools</title>
    <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.co
m/bootstrap/3.3.1/css/bootstrap.min.css">
    <style>
      body {
        width: 200px;
        overflow-x: hidden;
      }
      a:focus {
        outline: none;
      }
      .list-group {
        margin-bottom: 0;
      }
    </style>
  </head>
  <body>
    <div class="list-group">
      <a href="https://app.asana.com/" class="list-group-item"
target="_blank">
        Asana
      </a>
      <a href="https://hackpad.com/" class="list-group-item" t
arget="_blank">
        Hackpad
      </a>
      <a href="http://www.qdm.ks.edu.tw/computer/webpage/" cla
ss="list-group-item" target="_blank">
        首頁製作百寶箱
      </a>
    </div>
  </body>
</html>
⾄此完成整個 Chrome extension 的撰寫
安裝套件
請依照範例(⼀)的⽅式安裝套件,完成後成果如下所⽰:
 

點擊清單連結後,會⾃動開啟新分⾴,並連結⾄該網站。
六、範例實作 (三)
範例網址

參考範例
https://github.com/lunastorm/ivereadthat

範例題⽬
建⽴⼀個 Chrome extension,在臉書發⽂框加⼊⼀個按鈕,點擊之後會⾃動在輸⼊框內容
最前⾯加⼊柯P的⼝頭禪
基礎架構
在本範例中將資料夾名稱命名為「facebook-kp-mantra-button」:
/facebook-kp-mantra-button
  ﹂ jquery.min.js
  ﹂ kpmantra.js
  ﹂ manifest.json

開始撰寫
這⽀ Chrome extension,直接以參考範例來作重作
. 研究臉書輸⼊框架構,歸納如下
a. 主要輸⼊框

b. 留⾔輸⼊框
 

. manifest.json 內容設置如下:
{
  "manifest_version": 2,

  "name": "不過我覺得是這樣啦吼",
  "description": "讓你快速在臉書上引用柯P口頭禪",
  "version": "1.0.5566",

  "permissions": [
    "https://www.facebook.com/"
  ],
  "content_scripts": [
    {
      "matches": ["https://www.facebook.com/*"],
      "js": ["jquery.min.js"],
      "run_at": "document_end"
    },
    {
      "matches": ["https://www.facebook.com/*"],
      "js": ["kpmantra.js"],
      "run_at": "document_end"
    }
  ]
}
. kpmantra.js,在此將內容設置如下:
MutationObserver = window.MutationObserver || window.WebKitMut
ationObserver
var observer = new MutationObserver(function(mutations, observ
er) {

  //在主發文框嵌入柯P按鈕
  if($("._52lb div .kpmantra").length == 0) {
    $("._52lb div:first").prepend('<div class="lfloat kpmantr
a"><a data-hover="tooltip" class="_1dsq _1dsv" aria-label="柯
P 口吻" title="柯 P 口吻" id="u_jsonp_2_a"><span class="_1dsr" s
tyle="background-image: url(http://graph.facebook.com/http://g
raph.facebook.com/'.$value['id'].'/picture?type=square&height=
8&width=8/picture?type=square);background-position:center;"><s
pan class="_4-px ellipsis">柯 P 口吻</span></span></a></div>');
  }

  //下面這個物件在focus comment area時才會產生


  //調整回覆區塊輸入框的寬度
  $("._5yk2").each(function() {
    if($(this).css('padding-right') !== '82px') {
      $(this).css('padding-right','82px');
    }
  });

  //在回覆區塊嵌入柯P按鈕
  $(".UFICommentAttachmentButtons").each(function() {
    if($(this).find('.kpmantraComment').length == 0) {
      $(this).prepend('<div aria-label="柯 P 口吻" class="UFICo
mmentStickerButton kpmantraComment" data-hover="tooltip" data-
tooltip-alignh="center"><div class="UFICommentStickerIcon" sty
le="background-image: url(http://graph.facebook.com/http://gra
ph.facebook.com/'.$value['id'].'/picture?type=square&height=8&
width=8/picture?type=square);background-position:center;backgr
ound-size:14px"></div></div>');
    }
  });

})

//主發文區塊的柯P按鈕的觸發event
$('body').delegate('.kpmantra','click',function(){
  var content =  $('input[name="xhpc_message"]').val();
  var keydown = new Event('keydown');
  document.querySelector('[name=xhpc_message_text]').dispatchE
vent(keydown);
  var messageBox = document.querySelector('[name=xhpc_message_
text]');
  messageBox.dispatchEvent(new Event('focus'));
  messageBox.value = '不過我倒覺得是這樣啦吼,'+content;
  messageBox.dispatchEvent(new Event('keydown'));
  $('input[name="xhpc_message"]').val('不過我倒覺得是這樣啦吼,'+co
ntent);
  $('textarea[name="xhpc_message_text"]').focus();
});

//回復區塊的柯P按鈕的觸發event
$('body').delegate('.kpmantraComment','click',function(){
  var $editableDiv = $(this).parent().parent().find('.UFIAddCo
mmentInput').find('._54-z'),
      $element = $(this).parent().parent().find('.UFIAddCommen
tInput').find('span span:first'),
      $elementInput = $(this).parent().parent().find("input[na
me=add_comment_text]"),
      content = $element.text();
  $elementInput.trigger("keypress");
  $editableDiv.trigger("keypress");
  $editableDiv.focus();
  if($element.length == 0) {
    var span = $(this).parent().parent().find('.UFIAddCommentI
nput').find('span'),
        placeholder = $(this).parent().parent().find('.UFIAddC
ommentInput').find('._5ywb'),
        reactid = span.data('reactid');
    placeholder.addClass('_5ywc');
    span.html('<span data-reactid="'+reactid+'">不過我倒覺得是這
樣啦吼,</span>');
  }
  else {
    $element.text('不過我倒覺得是這樣啦吼,'+content);
  }
});

observer.observe(document, {
  subtree: true,
  attributes: true
})

實際效果
 

點擊後
 

七、範例實作 (四)
範例網址

參考範例
https://github.com/goooooooogle/chrome-extension-example/tree/master/ b-
kxmantra
https://github.com/kp-is-everywhere/kp-is-everywhere.github.io
https://github.com/jsoma/tabletop

範例題⽬
建⽴⼀⽀ Chrome extension,⾃動於 Facebook ⾴⾯的發⽂區塊中,加⼊⼀個下拉字句清
單,字句清單擷取字遠端google db,點擊項⽬後會⾃動加⼊發⽂⽂字內容
基礎架構
在本範例中將資料夾名稱命名為「 b-kxmantra」:
/fb-kxmantra
  ﹂ manifest.json
  ﹂ /css
      ﹂ style.css
  ﹂ /icons
      ﹂ icon16.png
      ﹂ icon48.png
      ﹂ icon128.png
      ﹂ icon256.png
  ﹂ /js
      ﹂ jquery-2.1.1.min.js
      ﹂ jquery-2.1.1.min.map
      ﹂ tabletop.js
      ﹂ kxmantra.js
      ﹂ inject.js

Query from Google Spreadsheets


參考此網址
本範例的試算表網址:
https://docs.google.com/spreadsheets/d/1TYidWSSP_iREXeCfSI745Xc6Q_oQk9tgr
VFubLhOL14/pubhtml?gid=0&single=true

開始撰寫
這⽀ Chrome extension,以 javascript class + jquery plugin 的模式撰寫,並借助以
tabletop.js 這⽀ plugin 來快速簡單的取得 Google Spreadsheets 的資料
. ⾸先要申請Google Spreadsheets作為資料庫使⽤,步驟如下:
a. 先到Google⾴⾯,新增⼀個試算表⽂件
 

b. 將資料填⼊,在這裡要注意在第⼀⾏的部份,之後將作為欄位名稱使⽤(可使⽤中
⽂),⽬前的案例中總共有五欄資料
 

c. 接下來,請按「檔案」=>「發布到網路」
 

d. 藍⾊那顆按鈕給他⼤⼒的點下去
 

e. 然後我們會得到⼀個網址,這個網址就是之後串街資料庫時使⽤的網址,⾄此資料庫
設定完成。這邊得到的網址如下:
https://docs.google.com/spreadsheets/d/1TYidWSSP_iREXeCfSI745Xc6Q_o
Qk9tgrVFubLhOL14/pubhtml
 

. 在 manifest.json 我們將 extension 設置如下


{
  "name": "皇上語錄",
  "version": "0.0.1",
  "manifest_version": 2,
  "description": "讓你快速在臉書發文時引用皇上語錄",
  "permissions": [
    "https://www.facebook.com/"
  ],
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "content_scripts": [
    {
      "matches": [
        "https://www.facebook.com/*",
        "https://*.google.com/*"
      ],
      "css": [
        "css/style.css"
      ],
      "js": [
        "js/jquery-2.1.1.min.js",
        "js/tabletop.js",
        "js/kxmantra.js",
        "js/inject.js"
      ]
    }
  ]
}

為本 extension 的主要部分,以 jquery plugin + javascript class 的模式


. kxmantra.js
來撰寫,內容如下:
(function() {
  var mantraGenerator, googleKey, spreadsheetsUrl;
  
  // google api 的資訊
  googleKey = '1TYidWSSP_iREXeCfSI745Xc6Q_oQk9tgrVFubLhOL14';
  spreadsheetsUrl = 'https://docs.google.com/spreadsheets/d/'+
googleKey+'/pubhtml';

  // 在這邊以 javascript 模擬 class 的模式


  // 命名一個 function 為 mantraGenerator
  mantraGenerator = function() {
    this.init();
  }
  
  // 在這邊設定 mantraGenerator 的所需參數與 function
  mantraGenerator.prototype = {
    init: function() {
      this.body = $('body');
      this.kxmantra = $('#kxmantra');
      this.bind();
    },
    generate: function() {
      if(this.kxmantra.length == 0) {
        this.getSentences();
      }
    },
    // 在 getSentences 中為本 plugin 的核心部分
    // 我們從這邊取得 google database 並封裝為 html 塞到頁面中
    getSentences: function() {
      Tabletop.init({
        key: spreadsheetsUrl,
        simpleSheet: true,
        callback: function(data) {
          var i,list='',html,popup;

          for(i=0; i < data.length; i++) {


            list += '<li class="kxsentence">'+data[i].item+'</
li>';
          }
          html = '<li class="_92" id="kxmantra"><a class="_9l
b"><span class="uiIconText _51z7"><i class="img sp_z0K5L71twmI
sx_fff369" style="background-image: url(https://graph.faceboo
k.com/1536627543250498/picture?type=square);background-positio
n:center;background-size:18px;"></i>皇上語錄<i class="_2wr"></i
></span></a></li>';
          $('._1dsl').append(html);
          $('._1dsp .clearfix').css('position','relative');
          popup = '<div id="kxmantraList" class="frame"><ul>'+
list+'</ul></div>';
          $('._1dsp .clearfix').append(popup);
        }
      });
    },
    
    // 在 bind 中設置各個使用者觸發的 event 動作與方法
    bind: function() {
      this.body.on('click', '#kxmantra', function(event) {
        event.stopPropagation();
        if($('textarea[name="xhpc_message_text"]').length > 0)
          $('textarea[name="xhpc_message_text"]').focus();
        else
          $('textarea[name="xhpc_message"]').focus();
        $('#kxmantraList').show();
      });
      this.body.on('click', this.body, function() {
        $('#kxmantraList').hide();
      });
      this.body.on('click', '.kxsentence', function() {
        var sentence,content,keydown,messageBox
        sentence = $(this).text();
        content =  $('input[name="xhpc_message"]').val();
        keydown = new Event('keydown');
        document.querySelector('[name=xhpc_message_text]').dis
patchEvent(keydown);
        messageBox = document.querySelector('[name=xhpc_messag
e_text]');
        messageBox.dispatchEvent(new Event('focus'));
        messageBox.value = content+sentence;
        messageBox.dispatchEvent(new Event('keydown'));
        $('input[name="xhpc_message"]').val(content+sentence);
        $('textarea[name="xhpc_message_text"]').focus();
      });
    }
  }

  $.fn.kxmantra = function() {
    var mantra = new mantraGenerator();
    mantra.generate();
    return this;
  };

}).call(this);

. 上述 plugin 撰寫完成後,我們在 inject.js 中,將這⽀ plugin 引⽤到⾴⾯中,在下⾯這


段程式碼中,我們等待整個⾴⾯載⼊完畢後,啟動 kxmatra
(function() {
  chrome.extension.sendMessage({}, function(response) {
    var readyStateCheckInterval;

    return readyStateCheckInterval = setInterval(function() {


      if (document.readyState === "complete") {
        clearInterval(readyStateCheckInterval);
        return $('body').kxmantra();
      }
    }, 500);
  });
}).call(this);

.完成
實際效果
 

⼋、範例(四)的延伸與更新
在接下來的部分,我們會以範例四作為基礎,作延伸的更新,更新項⽬如下:
讓「回覆」、「聊天室」也能使⽤
⽀援多個「語錄庫」之間的切換,讓「語錄庫」可以無限擴充
精簡程式碼
困難點與解決⽅法
. 上週有討論到關於因為臉書在「回覆區塊」,是使⽤reactjs,所以無法如同主要發⽂區
塊的⽅式達成輸⼊框區域內容的改動。
因為⽬前尚不熟悉reactjs,甚或了解之後可能也無法輕易操控臉書本⾝的code,所以在
這邊我以折衷的⽅式來達到相同的⽬的,即是讓使⽤者在點擊清單中的項⽬,幫使⽤者
將項⽬內容複製到clipboard,然後提⽰使⽤者⾃⾏貼上
. 「聊天室」的部份,雖然沒有回覆區塊這麼複雜,但是因為雖然可以更改<textarea>的
內容,也可以順利submit,但是無法取得使⽤者實際⾃⾏輸⼊的內容,有使⽤者經驗不
良的疑慮(無法取得實際內容=>無法在輸⼊點插⼊內容,與主輸⼊框之間的體驗不
同),因此在這邊,我採取與「回覆區塊」相同的⽅式實作,讓使⽤者⾃⾏貼上內容
套件架構
架構與原範例四基本上⼀樣
/fb-kxmantra
  ﹂ manifest.json
  ﹂ /css
      ﹂ style.css
  ﹂ /images
      ﹂ kx.png
  ﹂ /icons
      ﹂ icon16.png
      ﹂ icon48.png
      ﹂ icon128.png
      ﹂ icon256.png
  ﹂ /js
      ﹂ jquery-2.1.1.min.js
      ﹂ jquery-2.1.1.min.map
      ﹂ tabletop.js
      ﹂ kxmantra.js
      ﹂ inject.js

更新內容
. 在 manifest.json 我們將 extension 設置如下
更改的重點包含:
a. permissions,新增兩個 copy 動作所需的權限
b. 將 button 的圖⽚改為提取本機端的圖⽚,因此需設定web_accessible_resources
{

  "name": "朕語錄",
  "version": "0.0.6",
  "manifest_version": 2,
  "description": "讓你快速在臉書發文時引用皇上的語錄",
  "permissions": [
    "https://www.facebook.com/",
    "clipboardRead",
    "clipboardWrite"
  ],
  "icons": {
    "16": "icons/icon16.png",
    "48": "icons/icon48.png",
    "128": "icons/icon128.png"
  },
  "web_accessible_resources": ["images/kx.png"],
  "content_scripts": [
    {
      "matches": [
        "*://*.facebook.com/*",
        "*://*.google.com/*"
      ],
      "css": [
        "css/style.css"
      ],
      "js": [
        "js/jquery-2.1.1.min.js",
        "js/tabletop.js",
        "js/kxmantra.js",
        "js/inject.js"
      ],
      "run_at": "document_end"
    }
  ]
}

. kxmantra.js 做了⼤幅度的調整與程式碼優化步驟,
因為篇幅過⻑,內容部分請直接造訪 Github ,更新重點如下:
a. DEBUG 模式:由於在過程中錯誤不斷,在這邊我參考了⾹蕉王的神code,撰寫了
DEBUG 模式,當參數設定為 true 時,會⾃動吐出程序的執⾏狀況
b. 在 mantraGenerator prototype 的部份,做了調整與優化:將generator與
getSentense結合,並將generator調整為「專注在取得資料庫與產⽣相應的html
code」,並將重複使⽤的 function 獨⽴出來
c. 新增⼀個「install」的 prototype,專⾨處理將 html 塞到⾴⾯中的動作
d. 將 bind 與 MutationObserver (這東⻄我第⼀次碰到,還不太懂)分開,bind 主
要處理使⽤者觸發的 event 內容;observer 則為當偵測到⾴⾯弄容改變時,作相應
的動作
e. 將重複⽤到的參數做簡化與統整:即 _el0,_ela,_el1,_el2,_el3 ... 這部份,讓程式碼更
精簡(會降低閱讀性,但是因為 facebook 的 class name 基本上也沒什麼閱讀性)
並提升⼀點執⾏效率
f. 在變數名稱上作規劃:例如「__」開頭的變數代表 function;「_html」開頭的代表
⼀個 html ⽚段;「_el」開頭代表⼀個精簡⽤的變數項⽬,el代表 element。
g. 選單項⽬操作時,新增「鍵盤動作」,讓使⽤者也可以⽤「上」、「下」、
「enter」來作選取的動作,請參考下⽅程式碼:
    this.body.on('keydown', '.kxmantraList .kxsentence,.kxmant
raCommentList .kxsentence,.kxmantraChatList .kxsentence', func
tion(event) {
        code = event.keyCode || event.which;
        if(code == 13) {
          event.preventDefault();
          $(this).trigger('click');
        }
        else if(code == 40) {
          var ul = $(this).parent();
          ul.find('li').removeClass('active');
          $(this).nextAll(_eld).first().addClass('active');
          $(this).nextAll(_eld).first().find(_elh).focus();
        }
        else if(code == 38) {
          var ul = $(this).parent();
          ul.find('li').removeClass('active');
          $(this).prevAll(_eld).first().addClass('active');
          $(this).prevAll(_eld).first().find(_elh).focus();
        }
      });
h. 項⽬搜尋功能,有現成的js套件可以使⽤,但是這邊我以最簡單的 jquery 達成,請
參考下⽅程式碼:
      this.body.on('click', _elb, function(event) {
        event.stopPropagation();
      });
      this.body.on('change blur keyup', _elb, function() {
        var filter = $(this).val(),
            list = $(this).parent().find('ul:visible');
        if (filter) {
          list.find('span:not(:contains(' + filter + '))').par
ent().hide();
          list.find('span:contains(' + filter + ')').parent().
show();
          if(list.find('span:contains(' + filter + ')').length
== 0)
            list.find(_elf).show();
          else
            list.find(_elf).hide();
        } else {
          list.find(_elg).show();
          list.find(_elf).hide();
        }
      });

. 在 style.css 的部份,需注意的部份為「聊天室區塊」的樣式調整,因為「主發⽂區
塊」、「回覆區塊」皆為下拉的⽅式,但是聊天室位於⾴⾯最底端,無法下拉,所以必
須相對的調整 stylesheets
實際效果
. 主發⽂區塊

. 切換語錄庫
 

. 切換成功
 

. 回覆區塊的效果
 

. 回覆區塊提⽰使⽤者貼上
 

. 聊天室區塊,搜尋框在下⽅
 

已知BUG
. ⼀般狀況輸⼊⽂字時會變鈍鈍的,因為程式偵測網⾴變化次數過度頻繁
. 部分情況下,按鈕嵌⼊會失效,但是刷新後⼜正常

You might also like