記錄--使用Vue開發Chrome插件

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

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 環境搭建 Vue Web-Extension - A Web-Extension preset for VueJS (vue-web-extension.netlify.app) npm install -g @vue/cli npm i ...


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

環境搭建

Vue Web-Extension - A Web-Extension preset for VueJS (vue-web-extension.netlify.app)

npm install -g @vue/cli
npm install -g @vue/cli-init
vue create --preset kocal/vue-web-extension my-extension
cd my-extension
npm run server

會提供幾個選項,如Eslint,background.js,tab頁,axios,如下圖

選擇完後,將會自動下載依賴,通過npm run server將會在根目錄生成dist文件夾,將該文件拖至Chrome插件管理便可安裝,由於使用了webpack,所以更改代碼將會熱更新,不用反覆的編譯導入。

項目結構

├─src
|  ├─App.vue
|  ├─background.js
|  ├─main.js
|  ├─manifest.json
|  ├─views
|  |   ├─About.vue
|  |   └Home.vue
|  ├─store
|  |   └index.js
|  ├─standalone
|  |     ├─App.vue
|  |     └main.js
|  ├─router
|  |   └index.js
|  ├─popup
|  |   ├─App.vue
|  |   └main.js
|  ├─override
|  |    ├─App.vue
|  |    └main.js
|  ├─options
|  |    ├─App.vue
|  |    └main.js
|  ├─devtools
|  |    ├─App.vue
|  |    └main.js
|  ├─content-scripts
|  |        └content-script.js
|  ├─components
|  |     └HelloWorld.vue
|  ├─assets
|  |   └logo.png
├─public
├─.browserslistrc
├─.eslintrc.js
├─.gitignore
├─babel.config.js
├─package.json
├─vue.config.js
├─yarn.lock

根據所選的頁面,併在src與vue.config.js中配置頁面信息編譯後dist目錄結構如下

├─devtools.html
├─favicon.ico
├─index.html
├─manifest.json
├─options.html
├─override.html
├─popup.html
├─_locales
├─js
├─icons
├─css

安裝組件庫

安裝elementUI

整體的開發和vue2開發基本上沒太大的區別,不過既然是用vue來開發的話,那肯定少不了組件庫了。

要導入Element-ui也十分簡單,Vue.use(ElementUI); Vue2中怎麼導入element,便怎麼導入。演示如下

不過我沒有使用babel-plugin-component來按需引入,按需引入一個按鈕打包後大約1.6m,而全量引入則是5.5左右。至於為什麼不用,因為我需要在content-scripts.js中引入element組件,如果使用babel-plugin-component將無法按需導入組件以及樣式(應該是只支持vue文件按需引入,總之就是折騰了我一個晚上的時間)

安裝tailwindcss

不過官方提供瞭如何使用TailwindCSS,這裡就演示一下

在 Vue 3 和 Vite 安裝 Tailwind CSS - Tailwind CSS 中文文檔

推薦安裝低版本,最新版有相容性問題

npm install tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

創建postcss.config.js文件

// postcss.config.js
module.exports = {
  plugins: [
    // ...
    require('tailwindcss'),
    require('autoprefixer'), // if you have installed `autoprefixer`
    // ...
  ]
}

創建tailwind.config.js文件

// tailwind.config.js
module.exports = {
  purge: {
    // Specify the paths to all of the template files in your project
    content: ['src/**/*.vue'],
  
    // Whitelist selectors by using regular expression
    whitelistPatterns: [
        /-(leave|enter|appear)(|-(to|from|active))$/, // transitions
        /data-v-.*/, // scoped css
    ],
  }
  // ...
}

在src/popup/App.vue中導入樣式,或在新建style.css在mian.js中import "../style.css";

<style>
/* purgecss start ignore */
@tailwind base;
@tailwind components;
/* purgecss end ignore */

@tailwind utilities;
</style>

從官方例子導入一個登陸表單,效果如下

項目搭建

頁面搭建

頁面搭建就沒什麼好說的了,因為使用的是element-ui,所以頁面很快就搭建完畢了,效果如圖

懸浮窗

懸浮窗其實可有可無,不過之前寫Chrome插件的時候就寫了懸浮窗,所以vue版的也順帶寫一份。

要註意的是懸浮窗是內嵌到網頁的(且在document載入前載入,也就是"run_at": "document_start"),所以需要通過content-scripts.js才能操作頁面Dom元素,首先在配置清單manifest.json與vue.confing.js中匹配要添加的網站,以及註入的js代碼,如下

  "content_scripts": [
    {
      "matches": ["https://www.bilibili.com/video/*"],
      "js": ["js/jquery.js", "js/content-script.js"],
      "css": ["css/index.css"],
      "run_at": "document_start"
    },
    {
      "matches": ["https://www.bilibili.com/video/*"],
      "js": ["js/jquery.js", "js/bilibili.js"],
      "run_at": "document_end"
    }
  ]

 

	contentScripts: {
          entries: {
            'content-script': ['src/content-scripts/content-script.js'],
            bilibili: ['src/content-scripts/bilibili.js'],
          },
        },

由於是用Vue,但又要在js中生成組件,就使用document.createElement來進行創建元素,Vue組件如下(可拖拽)

:::danger

如果使用babel-plugin-component按需引入,組件的樣式將無法載入,同時自定義組件如果編寫了style標簽,那麼也同樣無法載入,報錯:Cannot read properties of undefined (reading 'appendChild')

大致就是css-loader無法載入對應的css代碼,如果執意要寫css的話,直接在manifest.json中註入css即可

:::

完整代碼
// 註意,這裡引入的vue是運行時的模塊,因為content是插入到目標頁面,對組件的渲染需要運行時的vue, 而不是編譯環境的vue (我也不知道我在說啥,反正大概意思就是這樣)
import Vue from 'vue/dist/vue.esm.js';
import ElementUI, { Message } from 'element-ui';
Vue.use(ElementUI);

// 註意,必須設置了run_at=document_start此段代碼才會生效
document.addEventListener('DOMContentLoaded', function() {
  console.log('vue-chrome擴展已載入');

  insertFloat();
});

// 在target頁面中新建一個帶有id的dom元素,將vue對象掛載到這個dom上。
function insertFloat() {
  let element = document.createElement('div');
  let attr = document.createAttribute('id');
  attr.value = 'appPlugin';
  element.setAttributeNode(attr);
  document.getElementsByTagName('body')[0].appendChild(element);

  let link = document.createElement('link');
  let linkAttr = document.createAttribute('rel');
  linkAttr.value = 'stylesheet';
  let linkHref = document.createAttribute('href');
  linkHref.value = 'https://unpkg.com/element-ui/lib/theme-chalk/index.css';
  link.setAttributeNode(linkAttr);
  link.setAttributeNode(linkHref);
  document.getElementsByTagName('head')[0].appendChild(link);

  let left = 0;
  let top = 0;
  let mx = 0;
  let my = 0;
  let onDrag = false;

  var drag = {
    inserted: function(el) {
      (el.onmousedown = function(e) {
        left = el.offsetLeft;
        top = el.offsetTop;
        mx = e.clientX;
        my = e.clientY;
        if (my - top > 40) return;

        onDrag = true;
      }),
        (window.onmousemove = function(e) {
          if (onDrag) {
            let nx = e.clientX - mx + left;
            let ny = e.clientY - my + top;
            let width = el.clientWidth;
            let height = el.clientHeight;
            let bodyWidth = window.document.body.clientWidth;
            let bodyHeight = window.document.body.clientHeight;

            if (nx < 0) nx = 0;
            if (ny < 0) ny = 0;

            if (ny > bodyHeight - height && bodyHeight - height > 0) {
              ny = bodyHeight - height;
            }

            if (nx > bodyWidth - width) {
              nx = bodyWidth - width;
            }

            el.style.left = nx + 'px';
            el.style.top = ny + 'px';
          }
        }),
        (el.onmouseup = function(e) {
          if (onDrag) {
            onDrag = false;
          }
        });
    },
  };

  window.kz_vm = new Vue({
    el: '#appPlugin',
    directives: {
      drag: drag,
    },
    template: `
      <div class="float-page" ref="float" v-drag>
        <el-card class="box-card" :body-style="{ padding: '15px' }">
          <div slot="header" class="clearfix" style="cursor: move">
            <span>懸浮窗</span>
            <el-button style="float: right; padding: 3px 0" type="text" @click="toggle">{{ show ? '收起' : '展開'}}</el-button>
          </div>
          <transition name="ul">
            <div v-if="show" class="ul-box">
              <span> {{user}} </span>
            </div>
          </transition>
        </el-card>
      </div>
      `,
    data: function() {
      return {
        show: true,
        list: [],
        user: {
          username: '',
          follow: 0,
          title: '',
          view: 0,
        },
      };
    },
    mounted() {},
    methods: {
      toggle() {
        this.show = !this.show;
      },
    },
  });
}
因為只能在js中編寫vue組件,所以得用template模板,同時使用了directives,給組件添加了拖拽的功能(尤其是window.onmousemove,如果是元素綁定他自身的滑鼠移動事件,那麼拖拽滑鼠將會十分卡頓),還使用了transition來進行緩慢動畫效果其中註入的css代碼如下
.float-page {
  width: 400px;
  border-radius: 8px;
  position: fixed;
  left: 50%;
  top: 25%;
  z-index: 1000001;
}

.el-card__header {
  padding: 10px 15px !important
}

.ul-box {
  height: 200px;
  overflow: hidden;
}

.ul-enter-active,
.ul-leave-active {
  transition: all 0.5s;
}
.ul-enter,
.ul-leave-to {
  height: 0;
}

相關邏輯可自行觀看,這裡不在贅述了,並不複雜。

也順帶是複習一下HTML中滑鼠事件和vue自定義命令了

功能實現

主要功能

  • 檢測視頻頁面,輸出對應up主,關註數以及視頻標題播放(參數過多就不一一顯示了)

  • 監控關鍵詞根據內容判斷是否點贊,例如文本出現了下次一定,那麼就點贊。

輸出相關信息

這個其實只要接觸過一丟丟爬蟲的肯定都會知道如何實現,通過右鍵審查元素,像這樣

然後使用dom操作,選擇對應的元素,輸出便可

> document.querySelector("#v_upinfo > div.up-info_right > div.name > a.username").innerText
< '老番茄'

當然使用JQuery效果也是一樣的。後續我都會使用JQuery來進行操作

在src/content-script/bilibili.js中寫下如下代碼

window.onload = function() {
  console.log('載入完畢');

  function getInfo() {
    let username = $('#v_upinfo > div.up-info_right > div.name > a.username').text();
    let follow = $(`#v_upinfo > div.up-info_right > div.btn-panel > div.default-btn.follow-btn.btn-transition.b-gz.following > span > span > span`).text();
    let title = $(`#viewbox_report > h1 > span`).text();
    let view = $('#viewbox_report > div > span.view').attr('title');

    console.log(username, follow, title, view);
  }
  
  getInfo();
};

重新載入插件,然後輸出查看結果

載入完畢
bilibili.js:19 老番茄 1606.0萬 頂級畫質 總播放數2368406

這些數據肯定單純的輸出肯定是沒什麼作用的,要能顯示到內嵌懸浮視窗,或者是popup頁面上(甚至發送ajax請求到遠程伺服器上保存)

對上面代碼微改一下

window.onload = function() {
  console.log('載入完畢');

  function getInfo() {
    let username = $('#v_upinfo > div.up-info_right > div.name > a.username').text().trim()
    let follow = $(`#v_upinfo > div.up-info_right > div.btn-panel > div.default-btn.follow-btn.btn-transition.b-gz.following > span > span > span`).text();
    let title = $(`#viewbox_report > h1 > span`).text();
    let view = $('#viewbox_report > div > span.view').attr('title');

    //console.log(username, follow, title, view);
    window.kz_vm.user = {
      username,
      follow,
      title,
      view,
    };

  }
  getInfo();
};
其中window.kz_vm是通過window.kz_vm = new Vue() 初始化的,方便我們操作vm對象,就需要通過jquery選擇元素在添加屬性了。如果你想的話也可以直接在content-script.js上編寫代碼,這樣就無需使用window對象,但這樣導致一些業務邏輯都堆在一個文件里,所以我習慣分成bilibili.js 然後註入方式為document_end,然後在操作dom元素嗎,實現效果如下 如果像顯示到popup頁面只需要通過頁面通信就行了,不過前提得先popup打開才行,所以一般都是通過background來進行中轉,一般來說很少 content –> popup(因為操作popup的前提都是popup要打開),相對更多的是content –> background 或 popup –> content

實現評論

這邊簡單編寫了一下頁面,通過popup給content,讓content輸入評論內容,與點擊發送,先看效果


bilibili_comment

同樣的,找到對應元素位置

// 評論文本框
$('#comment > div > div.comment > div > div.comment-send > div.textarea-container > textarea').val("要回覆的內容");
// 評論按鈕
$('#comment > div > div.comment > div > div.comment-send > div.textarea-container > button').click();

接著就是寫頁面通信的了,可以看到是popup向content發送請求

window.onload = function() {
  console.log('content載入完畢');

  function comment() {
    chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
      let { cmd, message } = request;
      if (cmd === 'addComment') {
        $('#comment > div > div.comment > div > div.comment-send > div.textarea-container > textarea').val(message);
        $('#comment > div > div.comment > div > div.comment-send > div.textarea-container > button').click();
      }
  
      sendResponse('我收到了你的消息!');
    });
  }
  
  comment();
};

 

<template>
  <div>
    <el-container>
      <el-header height="24">B站小工具</el-header>
      <el-main>
        <el-row :gutter="5">
          <el-input
            type="textarea"
            :rows="2"
            placeholder="請輸入內容"
            v-model="message"
            class="mb-5"
          >
          </el-input>

          <div>
            <el-button @click="addComment">評論</el-button>
          </div>
        </el-row>
      </el-main>
    </el-container>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      message: '',
      list: [],
      open: false,
    }
  },
  created() {
    chrome.storage.sync.get('list', (obj) => {
      this.list = obj['list']
    })
  },
  mounted() {
    chrome.runtime.onMessage.addListener(function (
      request,
      sender,
      sendResponse
    ) {
      console.log('收到來自content-script的消息:')
      console.log(request, sender, sendResponse)
      sendResponse('我是後臺,我已收到你的消息:' + JSON.stringify(request))
    })
  },
  methods: {
    sendMessageToContentScript(message, callback) {
      chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
        chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
          if (callback) callback(response)
        })
      })
    },
    addComment() {
      this.sendMessageToContentScript(
        { cmd: 'addComment', message: this.message },
        function () {
          console.log('來自content的回覆:' + response)
        }
      )
    },
  },
}
</script>

代碼就不解讀了,調用sendMessageToContentScript方法即可。相關源碼可自行下載查看

實現類似點贊功能也是同理的。

整體體驗

當時寫Chrome插件的效率不能說慢,反正不快就是了,像一些tips,都得自行封裝。用過Vue的都知道寫網頁很方便,寫Chrome插件未嘗不是編寫一個網頁,當時的我在接觸了Vue後就萌發了使用vue來編寫Chrome的想法,當然肯定不止我一個這麼想過,所以我在github上就能搜索到相應的源碼,於是就有了這篇文章。

如果有涉及到爬取數據相關的,我肯定是首選使用HTTP協議,如果在搞不定我會選擇使用puppeteerjs,不過Chrome插件主要還是增強頁面功能的,可以實現原本頁面不具備的功能。

本文僅僅只是初步體驗,簡單編寫了個小項目,後期有可能會實現一個百度網盤一鍵填寫提取碼,Js自吐Hooke相關的。(原本是打算做pdd商家自動回覆的,客戶說要用客戶端而不是網頁端(客戶端可以多號登陸),無奈,這篇博客就拿B站來演示了)

本文轉載於:

https://juejin.cn/post/7009128182007742495

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

 


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

-Advertisement-
Play Games
更多相關文章
  • 前序 前段時間由於項目需要用到MongoDB,但是MongoDB不建議Collection join 查詢,網上很多例子查詢都是基於linq 進行關聯查詢。但是在stackoverflow找到一個例子,程式員的朋友們請善於利用google搜索。主要介紹一個查詢角色的所有用戶的例子。MongoDB創建 ...
  • Bomb Lab 引言:主要任務是“拆炸彈”。所謂炸彈,其實就是一個二進位的可執行文件,要求輸入六個字元串,每個字元串對應一個phase。如果字元串輸入錯誤,系統就會提示BOOM!!!解決這次實驗需要將二進位文件反彙編,通過觀察理解彙編語言描述的程式行為來猜測符合條件的字元串。可以看出該可執行程式要 ...
  • 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是i.MXRT中FlexSPI外設不常用的讀選通採樣時鐘源 - loopbackFromSckPad。 最近碰到一個客戶,他們在 i.MXRT500 上使能了 FlexSPI->MCR0[RXCLKSRC] = 2(即 loopbackF ...
  • 前言:在zookeeper學習的時候,執行jsp命令查看zookpper運行狀態的時候發現報錯: -bash: jps: command not found 翻閱了一大批文章,不是東拼西湊,就是缺斤少兩,於是乎,本人萌生了第一次寫博客的想法,復盤的同時,順便記錄一下此次踩坑的經過,開始吧,GOGOG ...
  • ST-LINK 連接失敗的因素,以我個人的經歷而言有兩種:一個是驅動問題,一個是插線問題。連接正常的情況如下圖所示,SWDIO 能顯示你的設備信息: 註意使用 SW 埠,JTAG 埠導致無法識別設備。還有一個註意點:使用 STM32CubeMX 配置工程的時候要把 SYS -> Debug 設置 ...
  • 在最新一屆國際資料庫頂級會議 ACM SIGMOD 2022 上,來自清華大學的李國良和張超兩位老師發表了一篇論文:《HTAP Database: What is New and What is Next》,並做了 《HTAP Database:A Tutorial 的專項報告。這幾期學術分享會的文 ...
  • 一、AFNetworking整體框架是怎樣的 1、UIKit集成模塊 UIKit 2、請求序列化 Serialization 3、響應序列化 Serialization 4、會話 NSURLSession AFURLSessionManager最核心 子類:AFHTTPSessionManager ...
  • redux 實現彈出框案例 實現效果,點擊顯示按鈕出現彈出框,點擊關閉按鈕隱藏彈出框 新建彈出框組件 src/components/Modal.js, 在index.js中引入app組件,在app中去顯示計數器和彈出框組件 function Modal ({ showState, show, hid ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...