• 记录--使用Vue开发Chrome插件


    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

    环境搭建

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

    1. npm install -g @vue/cli
    2. npm install -g @vue/cli-init
    3. vue create --preset kocal/vue-web-extension my-extension
    4. cd my-extension
    5. npm run server

    会提供几个选项,如Eslint,background.js,tab页,axios,如下图

    选择完后,将会自动下载依赖,通过npm run server将会在根目录生成dist文件夹,将该文件拖至Chrome插件管理便可安装,由于使用了webpack,所以更改代码将会热更新,不用反复的编译导入。

    项目结构

    1. ├─src
    2. | ├─App.vue
    3. | ├─background.js
    4. | ├─main.js
    5. | ├─manifest.json
    6. | ├─views
    7. | | ├─About.vue
    8. | | └Home.vue
    9. | ├─store
    10. | | └index.js
    11. | ├─standalone
    12. | | ├─App.vue
    13. | | └main.js
    14. | ├─router
    15. | | └index.js
    16. | ├─popup
    17. | | ├─App.vue
    18. | | └main.js
    19. | ├─override
    20. | | ├─App.vue
    21. | | └main.js
    22. | ├─options
    23. | | ├─App.vue
    24. | | └main.js
    25. | ├─devtools
    26. | | ├─App.vue
    27. | | └main.js
    28. | ├─content-scripts
    29. | | └content-script.js
    30. | ├─components
    31. | | └HelloWorld.vue
    32. | ├─assets
    33. | | └logo.png
    34. ├─public
    35. ├─.browserslistrc
    36. ├─.eslintrc.js
    37. ├─.gitignore
    38. ├─babel.config.js
    39. ├─package.json
    40. ├─vue.config.js
    41. ├─yarn.lock

    根据所选的页面,并在src与vue.config.js中配置页面信息编译后dist目录结构如下

    1. ├─devtools.html
    2. ├─favicon.ico
    3. ├─index.html
    4. ├─manifest.json
    5. ├─options.html
    6. ├─override.html
    7. ├─popup.html
    8. ├─_locales
    9. ├─js
    10. ├─icons
    11. ├─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文件

    1. // postcss.config.js
    2. module.exports = {
    3. plugins: [
    4. // ...
    5. require('tailwindcss'),
    6. require('autoprefixer'), // if you have installed `autoprefixer`
    7. // ...
    8. ]
    9. }

    创建tailwind.config.js文件

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

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

    1. <style>
    2. /* purgecss start ignore */
    3. @tailwind base;
    4. @tailwind components;
    5. /* purgecss end ignore */
    6. @tailwind utilities;
    7. </style>

    从官方例子导入一个登陆表单,效果如下

    项目搭建

    页面搭建

    页面搭建就没什么好说的了,因为使用的是element-ui,所以页面很快就搭建完毕了,效果如图

    悬浮窗

    悬浮窗其实可有可无,不过之前写Chrome插件的时候就写了悬浮窗,所以vue版的也顺带写一份。

    要注意的是悬浮窗是内嵌到网页的(且在document加载前载入,也就是"run_at": "document_start"),所以需要通过content-scripts.js才能操作页面Dom元素,首先在配置清单manifest.json与vue.confing.js中匹配要添加的网站,以及注入的js代码,如下

    1. "content_scripts": [
    2. {
    3. "matches": ["https://www.bilibili.com/video/*"],
    4. "js": ["js/jquery.js", "js/content-script.js"],
    5. "css": ["css/index.css"],
    6. "run_at": "document_start"
    7. },
    8. {
    9. "matches": ["https://www.bilibili.com/video/*"],
    10. "js": ["js/jquery.js", "js/bilibili.js"],
    11. "run_at": "document_end"
    12. }
    13. ]
    1. contentScripts: {
    2. entries: {
    3. 'content-script': ['src/content-scripts/content-script.js'],
    4. bilibili: ['src/content-scripts/bilibili.js'],
    5. },
    6. },

    由于是用Vue,但又要在js中生成组件,就使用document.createElement来进行创建元素,Vue组件如下(可拖拽)

    :::danger

    如果使用babel-plugin-component按需引入,组件的样式将无法载入,同时自定义组件如果编写了style标签,那么也同样无法载入,报错:Cannot read properties of undefined (reading 'appendChild')

    大致就是css-loader无法加载对应的css代码,如果执意要写css的话,直接在manifest.json中注入css即可

    :::

    完整代码
    1. // 注意,这里引入的vue是运行时的模块,因为content是插入到目标页面,对组件的渲染需要运行时的vue, 而不是编译环境的vue (我也不知道我在说啥,反正大概意思就是这样)
    2. import Vue from 'vue/dist/vue.esm.js';
    3. import ElementUI, { Message } from 'element-ui';
    4. Vue.use(ElementUI);
    5. // 注意,必须设置了run_at=document_start此段代码才会生效
    6. document.addEventListener('DOMContentLoaded', function() {
    7. console.log('vue-chrome扩展已载入');
    8. insertFloat();
    9. });
    10. // 在target页面中新建一个带有id的dom元素,将vue对象挂载到这个dom上。
    11. function insertFloat() {
    12. let element = document.createElement('div');
    13. let attr = document.createAttribute('id');
    14. attr.value = 'appPlugin';
    15. element.setAttributeNode(attr);
    16. document.getElementsByTagName('body')[0].appendChild(element);
    17. let link = document.createElement('link');
    18. let linkAttr = document.createAttribute('rel');
    19. linkAttr.value = 'stylesheet';
    20. let linkHref = document.createAttribute('href');
    21. linkHref.value = 'https://unpkg.com/element-ui/lib/theme-chalk/index.css';
    22. link.setAttributeNode(linkAttr);
    23. link.setAttributeNode(linkHref);
    24. document.getElementsByTagName('head')[0].appendChild(link);
    25. let left = 0;
    26. let top = 0;
    27. let mx = 0;
    28. let my = 0;
    29. let onDrag = false;
    30. var drag = {
    31. inserted: function(el) {
    32. (el.onmousedown = function(e) {
    33. left = el.offsetLeft;
    34. top = el.offsetTop;
    35. mx = e.clientX;
    36. my = e.clientY;
    37. if (my - top > 40) return;
    38. onDrag = true;
    39. }),
    40. (window.onmousemove = function(e) {
    41. if (onDrag) {
    42. let nx = e.clientX - mx + left;
    43. let ny = e.clientY - my + top;
    44. let width = el.clientWidth;
    45. let height = el.clientHeight;
    46. let bodyWidth = window.document.body.clientWidth;
    47. let bodyHeight = window.document.body.clientHeight;
    48. if (nx < 0) nx = 0;
    49. if (ny < 0) ny = 0;
    50. if (ny > bodyHeight - height && bodyHeight - height > 0) {
    51. ny = bodyHeight - height;
    52. }
    53. if (nx > bodyWidth - width) {
    54. nx = bodyWidth - width;
    55. }
    56. el.style.left = nx + 'px';
    57. el.style.top = ny + 'px';
    58. }
    59. }),
    60. (el.onmouseup = function(e) {
    61. if (onDrag) {
    62. onDrag = false;
    63. }
    64. });
    65. },
    66. };
    67. window.kz_vm = new Vue({
    68. el: '#appPlugin',
    69. directives: {
    70. drag: drag,
    71. },
    72. template: `
    73. <div class="float-page" ref="float" v-drag>
    74. <el-card class="box-card" :body-style="{ padding: '15px' }">
    75. <div slot="header" class="clearfix" style="cursor: move">
    76. <span>悬浮窗</span>
    77. <el-button style="float: right; padding: 3px 0" type="text" @click="toggle">{{ show ? '收起' : '展开'}}</el-button>
    78. </div>
    79. <transition name="ul">
    80. <div v-if="show" class="ul-box">
    81. <span> {{user}} </span>
    82. </div>
    83. </transition>
    84. </el-card>
    85. </div>
    86. `,
    87. data: function() {
    88. return {
    89. show: true,
    90. list: [],
    91. user: {
    92. username: '',
    93. follow: 0,
    94. title: '',
    95. view: 0,
    96. },
    97. };
    98. },
    99. mounted() {},
    100. methods: {
    101. toggle() {
    102. this.show = !this.show;
    103. },
    104. },
    105. });
    106. }
    因为只能在js中编写vue组件,所以得用template模板,同时使用了directives,给组件添加了拖拽的功能(尤其是 window.onmousemove,如果是元素绑定他自身的鼠标移动事件,那么拖拽鼠标将会十分卡顿),还使用了transition来进行缓慢动画效果其中注入的css代码如下
    1. .float-page {
    2. width: 400px;
    3. border-radius: 8px;
    4. position: fixed;
    5. left: 50%;
    6. top: 25%;
    7. z-index: 1000001;
    8. }
    9. .el-card__header {
    10. padding: 10px 15px !important
    11. }
    12. .ul-box {
    13. height: 200px;
    14. overflow: hidden;
    15. }
    16. .ul-enter-active,
    17. .ul-leave-active {
    18. transition: all 0.5s;
    19. }
    20. .ul-enter,
    21. .ul-leave-to {
    22. height: 0;
    23. }

    相关逻辑可自行观看,这里不在赘述了,并不复杂。

    也顺带是复习一下HTML中鼠标事件和vue自定义命令了

    功能实现

    主要功能

    • 检测视频页面,输出对应up主,关注数以及视频标题播放(参数过多就不一一显示了)

    • 监控关键词根据内容判断是否点赞,例如文本出现了下次一定,那么就点赞。

    输出相关信息

    这个其实只要接触过一丢丢爬虫的肯定都会知道如何实现,通过右键审查元素,像这样

    然后使用dom操作,选择对应的元素,输出便可

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

    当然使用JQuery效果也是一样的。后续我都会使用JQuery来进行操作

    在src/content-script/bilibili.js中写下如下代码

    1. window.onload = function() {
    2. console.log('加载完毕');
    3. function getInfo() {
    4. let username = $('#v_upinfo > div.up-info_right > div.name > a.username').text();
    5. 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();
    6. let title = $(`#viewbox_report > h1 > span`).text();
    7. let view = $('#viewbox_report > div > span.view').attr('title');
    8. console.log(username, follow, title, view);
    9. }
    10. getInfo();
    11. };

    重新加载插件,然后输出查看结果

    1. 加载完毕
    2. bilibili.js:19 老番茄 1606.0万 顶级画质 总播放数2368406

    这些数据肯定单纯的输出肯定是没什么作用的,要能显示到内嵌悬浮窗口,或者是popup页面上(甚至发送ajax请求到远程服务器上保存)

    对上面代码微改一下

    1. window.onload = function() {
    2. console.log('加载完毕');
    3. function getInfo() {
    4. let username = $('#v_upinfo > div.up-info_right > div.name > a.username').text().trim()
    5. 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();
    6. let title = $(`#viewbox_report > h1 > span`).text();
    7. let view = $('#viewbox_report > div > span.view').attr('title');
    8. //console.log(username, follow, title, view);
    9. window.kz_vm.user = {
    10. username,
    11. follow,
    12. title,
    13. view,
    14. };
    15. }
    16. getInfo();
    17. };
    其中 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

    同样的,找到对应元素位置

    1. // 评论文本框
    2. $('#comment > div > div.comment > div > div.comment-send > div.textarea-container > textarea').val("要回复的内容");
    3. // 评论按钮
    4. $('#comment > div > div.comment > div > div.comment-send > div.textarea-container > button').click();

    接着就是写页面通信的了,可以看到是popup向content发送请求

    1. window.onload = function() {
    2. console.log('content加载完毕');
    3. function comment() {
    4. chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    5. let { cmd, message } = request;
    6. if (cmd === 'addComment') {
    7. $('#comment > div > div.comment > div > div.comment-send > div.textarea-container > textarea').val(message);
    8. $('#comment > div > div.comment > div > div.comment-send > div.textarea-container > button').click();
    9. }
    10. sendResponse('我收到了你的消息!');
    11. });
    12. }
    13. comment();
    14. };
    1. <template>
    2. <div>
    3. <el-container>
    4. <el-header height="24">B站小工具</el-header>
    5. <el-main>
    6. <el-row :gutter="5">
    7. <el-input
    8. type="textarea"
    9. :rows="2"
    10. placeholder="请输入内容"
    11. v-model="message"
    12. class="mb-5"
    13. >
    14. </el-input>
    15. <div>
    16. <el-button @click="addComment">评论</el-button>
    17. </div>
    18. </el-row>
    19. </el-main>
    20. </el-container>
    21. </div>
    22. </template>
    23. <script>
    24. export default {
    25. name: 'App',
    26. data() {
    27. return {
    28. message: '',
    29. list: [],
    30. open: false,
    31. }
    32. },
    33. created() {
    34. chrome.storage.sync.get('list', (obj) => {
    35. this.list = obj['list']
    36. })
    37. },
    38. mounted() {
    39. chrome.runtime.onMessage.addListener(function (
    40. request,
    41. sender,
    42. sendResponse
    43. ) {
    44. console.log('收到来自content-script的消息:')
    45. console.log(request, sender, sendResponse)
    46. sendResponse('我是后台,我已收到你的消息:' + JSON.stringify(request))
    47. })
    48. },
    49. methods: {
    50. sendMessageToContentScript(message, callback) {
    51. chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    52. chrome.tabs.sendMessage(tabs[0].id, message, function (response) {
    53. if (callback) callback(response)
    54. })
    55. })
    56. },
    57. addComment() {
    58. this.sendMessageToContentScript(
    59. { cmd: 'addComment', message: this.message },
    60. function () {
    61. console.log('来自content的回复:' + response)
    62. }
    63. )
    64. },
    65. },
    66. }
    67. </script>

    代码就不解读了,调用sendMessageToContentScript方法即可。相关源码可自行下载查看

    实现类似点赞功能也是同理的。

    整体体验

    当时写Chrome插件的效率不能说慢,反正不快就是了,像一些tips,都得自行封装。用过Vue的都知道写网页很方便,写Chrome插件未尝不是编写一个网页,当时的我在接触了Vue后就萌发了使用vue来编写Chrome的想法,当然肯定不止我一个这么想过,所以我在github上就能搜索到相应的源码,于是就有了这篇文章。

    如果有涉及到爬取数据相关的,我肯定是首选使用HTTP协议,如果在搞不定我会选择使用puppeteerjs,不过Chrome插件主要还是增强页面功能的,可以实现原本页面不具备的功能。

    本文仅仅只是初步体验,简单编写了个小项目,后期有可能会实现一个百度网盘一键填写提取码,Js自吐Hooke相关的。(原本是打算做pdd商家自动回复的,客户说要用客户端而不是网页端(客户端可以多号登陆),无奈,这篇博客就拿B站来演示了)

    本文转载于:

    https://juejin.cn/post/7009128182007742495

    如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

     

  • 相关阅读:
    ATJ2157&ATJ2127音乐按文件名拼音排序---标案是按内码进行排序
    MongoDB归并连续号段-(待验证)
    Python文件的操作处理,一看就会
    基于卷尾猴算法优化概率神经网络PNN的分类预测 - 附代码
    JDK1.8更便捷获取时间的方法:LocalDateTime、LocalDate、LocalTime、Period
    再谈VC++动态链接库中的结构成员与VB或C#调用
    VSCode使用记录
    Nature子刊:一个从大脑结构中识别阿尔茨海默病维度表征的深度学习框架
    FlashAttention2原理解析以及面向AIGC的加速实践
    Flask框架初学-03-模板
  • 原文地址:https://blog.csdn.net/qq_40716795/article/details/127465925