vue开发chrome插件,实现获取界面数据和保存到数据库功能


Posted in Vue.js onDecember 01, 2020

前言

最近在评估项目时,要开启评估平台,查看平台和保存平台,感觉非常繁琐,开发了一款可以获取评估平台数据,查看项目排期和直接保存数据到数据库的chrome插件,由于我需要使用之前vue封装的一个日历插件,这里就用vue来开发这个插件。

开发前准备

要开发一个chrome插件,我们首先需要了解chrome插件的基本结构和对应的功能。
每个扩展的文件类型和目录数量有所不同,但都必须有 manifest。 一些基本但有用的扩展程序可能仅由 manifest 及其工具栏图标组成。

manifest.json

{
  "name": "My Extension", // "扩展名"
  "version": "2.1", // 当前创建扩展版本号
  "description": "Gets information from Google.", //"扩展描述"
  "icons": { // 扩展工具界面使用图标
   "128": "icon_16.png",
   "128": "icon_32.png",
   "128": "icon_48.png",
   "128": "icon_128.png"
  },
  "background": { // 扩展常常用一个单独的长时间运行的脚本来管理一些任务或者状态
   "persistent": false,
   "scripts": ["background_script.js"] // 后台常驻脚本,自动运行,直到关闭浏览器。可根据需求自行设置
  },
  "permissions": ["https://*.google.com/", "activeTab"], //开启拓展权限
  "browser_action": { 
   "default_icon": "icon_16.png",  // 器右上角显示
   "default_popup": "popup.html" /** 鼠标移入,显示简短扩展文本描述 **/
  },
   "content_scripts": [{  // ontent scripts是在Web页面内运行的javascript脚本。通过使用标准的DOM,它们可以获取浏览器所访问页面的详细信息,并可以修改这些信息。
  "js": ["script/contentscript.js"], /** 需要注入的脚本 **/
  "matches": [  /** 匹配网址(支持正则),成功即注入(其余属性自行查询) **/
    "http://*/*",
    "https://*/*"
   ]
  }]
  }

vue开发chrome插件

我们需要使用vue来开发插件,几经搜索,查到一款样板,很方便我们进行vue开发插件,便引入该样板来进行开发。

引入vue-web-extension样板来实现vue开发

npm install -g @vue/cli
 npm install -g @vue/cli-init
 vue init kocal/vue-web-extension new-tab-page

然后切换到项目目录安装依赖项

cd new-tab-page
 npm install

我们可以运行

npm run watch:dev

在项目根目录中会得到一个dist 文件夹,我们直接安装解压的扩展程序,选择这个dist,就可以进行开发并监视更改。

样板文件的基本格式

├── dist
│ └── <the built extension>
├── node_modules
│ └── <one or two files and folders>
├── package.json
├── package-lock.json
├── scripts
│ ├── build-zip.js
│ └── remove-evals.js
├── src
│ ├── background.js
│ ├── icons
│ │ ├── icon_128.png
│ │ ├── icon_48.png
│ │ └── icon.xcf
│ ├── manifest.json
│ └── popup
│ ├── App.vue
│ ├── popup.html
│ └── popup.js
└── webpack.config.js

可以看出,样板文件使用 webpack进行打包,

src文件夹包含我们将用于扩展的所有文件。manifest 文件和 background.js 对于我们来说是熟悉的,但也要注意包含Vue 组件的 popup 文件夹。当样板文件将扩展构建到 dist 文件夹中时,它将通过vue-loader 管理所有 .vue 文件并输出一个浏览器可以理解的 JavaScript 包。

在 src 文件夹中还有一个 icons 文件夹。如果你看一眼 Chrome 的工具栏,会看到我们的扩展程序的新图标(也被称为 browser action)。这就是从此文件夹中拿到的。如果单击它,你应该会看到一个弹出窗口,显示“Hello world!” 这是由 popup/App.vue 创建的。

最后,请注 scripts 文件夹的两个脚本:一个用于删除 eval 用法以符合 Chrome Web Store 的内容安全策略,另一个用于当你要把扩展上传到Chrome Web Store时将其打包到 .zip 文件中。

在 package.json 文件中还声明了各种脚本。我们将用 npm run watch:dev 来开发扩展,然后使用 npm run build-zip 生成一个ZIP文件以上传到 Chrome Web Store。

创建插件界面

我们直接修改popup.html

popup.html

<!DOCTYPE html>
<html lang="zh">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
 <link href="popup.css" rel="external nofollow" rel="stylesheet">
 <div id="app">
 </div>
 <script src="popup.js"></script>
</body>
</html>

这里我们引入popup.css和popup.js 在popup.css放入我们需要用的样式 在popup.js中,来引入我们的vue文件

popup.js

import Vue from 'vue'
 import { Tabs,TabPane, Dialog, Button,Form,FormItem,Input,DatePicker,Message,Alert,Tooltip,MessageBox } from 'element-ui';
 import 'element-ui/lib/theme-chalk/index.css';
 import App from './App'
 Vue.use(Tabs);
 Vue.use(TabPane);
 Vue.use(Dialog);
 Vue.use(Button);
 Vue.use(Form);
 Vue.use(FormItem);
 Vue.use(Input);
 Vue.use(DatePicker);
 Vue.use(Tooltip);
 Vue.use(Alert);
 Vue.prototype.$message = Message;
 Vue.prototype.$confirm = MessageBox.confirm;
 new Vue({
  el: '#app',
  render: h => h(App)
 })

这里,我们主要按需引入element-ui中的控件,和app.vue组件

app.vue

<template>
 <div id="app" style="height: 580px;overflow-y: hidden;width:680px;">
  <div>
   模板
  </div>
  <customPlan :projectData="projectData" :loginPerson="loginPerson"></customPlan>
 </div>
</template>

<script>
import customPlan from '../components/customPlan'
let { Pinyin } = require('../script/pinyin')
let pinyin = new Pinyin()
export default {
 components: { customPlan },
 data() {
  return {
   loginPerson: '',
   projectData: {
    departmentName: '',
    developer: '',
    endDate: '',
    evaluator: '',
    isDeprecated: false,
    isIncludeSaturday: false,
    isNewComponent: false,
    issureAdress: '',
    msg: '',
    name: '',
    startDate: '',
    workDay: '',
    year: 2020
   }
  }
 },
 created() {
  this.getUrl()
 },
 methods: { 
  getCaption(obj) {
   var index = obj.lastIndexOf(',')
   obj = obj.substring(index + 1, obj.length)
   return obj
  },
  /**  
  * @desc 获取当前页面的url
  */
  getUrl() {
   chrome.tabs.getSelected(null, tab => {
    console.log(tab,"tab")
    this.projectData.issureAdress = tab.url
    chrome.tabs.sendMessage(tab.id, { greet: 'hello' }, response => {
     if (response && response.developer && response.processName) {
      let developer = pinyin
       .getFullChars(this.getCaption(response.developer))
       .toLowerCase()
      this.projectData.evaluator = developer
      this.projectData.name = response.processName
     } else if (response && response.developer && !response.processName) {
      var index = response.developer.lastIndexOf('@')
      response.developer = response.developer.substring(
       index + 1,
       response.developer.length
      )
      this.loginPerson = response.loginPerson
      this.projectData.evaluator = response.developer
      this.projectData.name =response.peocessName
     }
    })
   })
  }
 }
}
</script>

在manifest.json中引入

"browser_action": {
   "default_title": "测试",
   "default_popup": "popup/popup.html"
  },

这里我们主要引入了我们的日历控件customPlan,大家可以按需引入自己需要的组件。到这里,我们的插件界面基本搭建完成了。

获取当前界面数据,并在插件中进行监听

需要获取当前界面数据,就需要在Web页面内运行的javascript脚本。通过使用标准的DOM,它们可以获取浏览器所访问页面的详细信息,并可以修改这些信息。就需要content_scripts里面引入我们需要的contentscript.js文件,在这个js文件中,可以获取浏览器所访问页面的详细信息

"content_scripts": [{
  "js": ["script/contentscript.js"],
  "matches": [
   "http://*/*",
   "https://*/*"
  ]
 }]

contentscript.js文件配置如下

document.addEventListener('click', function (e) {
  let isCurrect = e.path.length > 3&&e.path[4].innerText&&e.path[4].innerText.indexOf('提交需求') != -1 && e.target.innerText === '确 定' && document.getElementsByClassName('layout-nav') && document.getElementsByClassName('layout-nav')[0].children
  if (isCurrect) {
    if (document.getElementsByClassName('user-table') && document.getElementsByClassName('user-table')[0] && document.getElementsByClassName('user-table')[0].getElementsByClassName('el-table__row').length > 0) {
      var port = chrome.runtime.connect({ name: "custommanage" });//通道名称
      let loginPerson = document.getElementsByClassName('layout-nav') && document.getElementsByClassName('layout-nav')[0].children ? document.getElementsByClassName('layout-nav')[0].children[0].innerText : ''
      let partMentName = document.getElementsByClassName('layout-nav') && document.getElementsByClassName('layout-nav')[0].children ? document.getElementsByClassName('layout-nav')[0].children[3].innerText : ''
      let processName = document.getElementsByClassName('el-input__inner') && document.getElementsByClassName('layout-nav')[0].children ? document.getElementsByClassName('el-input__inner')[0].title : ''
      let tableElement = document.getElementsByClassName('user-table') ? document.getElementsByClassName('user-table')[0].getElementsByClassName('el-table__row') : []
      let choseSelect = []
      for (let value of tableElement) {
        if (value.innerText.indexOf(partMentName) !== -1) {
          choseSelect = value
        }
      }
      let developPerson = ''
      let startTime = ''
      let endTime = ''
      if (choseSelect && choseSelect.getElementsByTagName('td')) {
        developPerson = choseSelect.getElementsByTagName('td')[1].innerText
        startTime = choseSelect.getElementsByTagName('td')[3].getElementsByTagName('input')[0].title
        endTime = choseSelect.getElementsByTagName('td')[4].getElementsByTagName('input')[0].title
      }
      let item = {
        "loginPerson": loginPerson,
        "processName": processName,
        "developPerson": developPerson,
        "startTime": startTime,
        "endTime": endTime
      }
      port.postMessage(item);//发送消息  
    } else {
      alert('未查到该项目预排人员与预排时间,请点开插件或打开定制管理系统手动添加项目!')
    }
  }
});

这里获取元素就是js基本知识了。主要使用chrome插件的api

chrome.runtime.connect

保持长期连接的模式,在content scripts与Chrome扩展程序页面之间建立通道(可以为通道命名),可以处理多个消息。在通道的两端分别拥有一个chrome.runtime.Port对象,用以收发消息。这里主要在我们点击需要的按钮时,就会向chrome插件发送消息。
在content scripts主动建立通道如下:

var port = chrome.runtime.connect({name: "custommanage"});//通道名称
 port.postMessage({joke: "Knock knock"});//发送消息
 port.onMessage.addListener(function(msg) {//监听消息
   port.postMessage({answer: "custommanage"});
 });

获取到界面信息后,在content scripts发生请求消息给Google Chrome扩展程序,我们在插件中就需要获取获取的界面信息了

chrome扩展获取信息

我们在background.js中建立通道,获取web界面传回的信息

chrome.tabs.query(
 { active: true, currentWindow: true },
 function (tabs) {
  var port = chrome.tabs.connect(//建立通道
   tabs[0].id,
   { name: "custommanage" }//通道名称
  );
 });
chrome.runtime.onConnect.addListener((port) => {
 console.assert(port.name == "custommanage");
 port.onMessage.addListener((res) => {  
   addActon(res)
 });
});

addAction函数即是保存我们获取的数据到数据库。

/**
  * @desc 添加获取数据到数据库
  */
function addProject (params) {  
   let paramsObj = Object.assign({}, params)
   let optsUpdata = {
    method: 'POST', //请求方法
    body: JSON.stringify(paramsObj), //请求体
    headers: {
     Accept: 'application/json',
     'Content-Type': 'application/json'
    }
   }
   fetch('http://****/api/EditConfirmWork', optsUpdata)
    .then(response => {
     return response.json()
    })
    .then(data => {
     if (data.code === 0) {
      alert('更新成功!')
     }
    })
    .catch(error => {
     alert(error)
    })
}

这里我们采用fetch函数来连接数据库,和修改数据库,后端接口也需要做一些跨域相关处理,才能正常连接,我这里用的Node开发的后端,大致代码如下

//跨域
app.all('*', function (req, res, next) {
 res.header("Access-Control-Allow-Origin", "*"); 
 res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
 res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
 res.header('Access-Control-Allow-Credentials', true)
 next();
});

到此,获取界面数据,并自动保存到数据库功能已完成,background.js我们在manifest.json引用下。

"background": {
  "scripts": ["script/background.js"]
 },

我们需要将编辑好的插件通过webpack打包,还需要在webpack.config.js配置一下,然后运行npm run watch:dev 就可以得到我们需要的dist,安装到扩展程序就可使用了。

webpack.config.js配置如下

const webpack = require('webpack');
const ejs = require('ejs');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WebpackShellPlugin = require('webpack-shell-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ChromeExtensionReloader = require('webpack-chrome-extension-reloader');
const { VueLoaderPlugin } = require('vue-loader');
const { version } = require('./package.json');

const config = {
 mode: process.env.NODE_ENV,
 context: __dirname + '/src',
 entry: {
  'popup/popup': './popup/popup.js',
  'script/contentscript': './script/contentscript.js',
  'script/background': './script/background.js'
 },
 output: {
  path: __dirname + '/dist',
  filename: '[name].js',
 },
 resolve: {
  extensions: ['.js', '.vue'],
 }, 
 module: {
  rules: [
   {
    test: /\.vue$/,
    loaders: 'vue-loader',
   },
   {
    test: /\.js$/,
    loader: 'babel-loader',
    exclude: /node_modules/,
   },
   {
    test: /\.css$/,
    use: [MiniCssExtractPlugin.loader, 'css-loader'],
   },
   {
    test: /\.scss$/,
    use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
   },
   {
    test: /\.sass$/,
    use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader?indentedSyntax'],
   },
   {
    test: /\.(png|jpg|gif|svg|ico)$/,
    loader: 'file-loader',
    options: {
     name: '[name].[ext]?emitFile=false',
    },
   },
   {
    test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/,
    loader: 'url-loader',
    options: {
     esModule: false,
     limin: 10000,
     name: "font/[name].[hash:8].[ext]"
    }
   }
  ],
 },
 plugins: [  
  new VueLoaderPlugin(),
  new MiniCssExtractPlugin({
   filename: '[name].css',
  }),
  new CopyWebpackPlugin([
   { from: 'icons', to: 'icons', ignore: ['icon.xcf'] },
   { from: 'popup/popup.html', to: 'popup/popup.html', transform: transformHtml },
   {
    from: 'manifest.json',
    to: 'manifest.json',
    transform: (content) => {
     const jsonContent = JSON.parse(content);
     jsonContent.version = version;

     if (config.mode === 'development') {
      jsonContent['content_security_policy'] = "script-src 'self' 'unsafe-eval'; object-src 'self'";
     }

     return JSON.stringify(jsonContent, null, 2);
    },
   },
  ])
 ],
};

if (config.mode === 'production') {
 config.plugins = (config.plugins || []).concat([
  new webpack.DefinePlugin({
   'process.env': {
    NODE_ENV: '"production"',
   },
  }),
 ]);
}

if (process.env.HMR === 'true') {
 config.plugins = (config.plugins || []).concat([
  new ChromeExtensionReloader(),
 ]);
}

function transformHtml(content) {
 return ejs.render(content.toString(), {
  ...process.env,
 });
}

module.exports = config;

我们数据改变后,如果想点开插件就查看对应界面,这里就按需引入我们需要的组件,来实现不同的界面展示。

最后附上manifest.json完整的配置

{
 "name": "插件",
 "description": "描述",
 "version": 2.0,
 "manifest_version": 2,
 "icons": {
  "48": "icons/icon_426.png",
  "128": "icons/icon_426.png"
 },
 "browser_action": {
  "default_title": "插件",
  "default_popup": "popup/popup.html"
 },
 "permissions": [
  "tabs",
  "<all_urls>"
 ],
 "background": {
  "scripts": ["script/background.js"]
 },
 "content_scripts": [{
  "js": ["script/contentscript.js"],
  "matches": [
   "http://*/*",
   "https://*/*"
  ]
 }]
}

以上就是vue开发chrome插件,实现获取界面数据和保存到数据库功能的详细内容,更多关于vue开发chrome插件的资料请关注三水点靠木其它相关文章!

Vue.js 相关文章推荐
vue+iview分页组件的封装
Nov 17 Vue.js
在Vue中使用CSS3实现内容无缝滚动的示例代码
Nov 27 Vue.js
Vue 组件注册全解析
Dec 17 Vue.js
Vue+scss白天和夜间模式切换功能的实现方法
Jan 05 Vue.js
如何在 Vue 表单中处理图片
Jan 26 Vue.js
如何在vue中使用video.js播放m3u8格式的视频
Feb 01 Vue.js
手动实现vue2.0的双向数据绑定原理详解
Feb 06 Vue.js
详解vue身份认证管理和租户管理
May 25 Vue.js
vue响应式原理与双向数据的深入解析
Jun 04 Vue.js
Vue3.0写自定义指令的简单步骤记录
Jun 27 Vue.js
vue elementUI表格控制对应列
Apr 13 Vue.js
vue动态绑定style样式
Apr 20 Vue.js
vue实现表格合并功能
Dec 01 #Vue.js
vue element实现表格合并行数据
Nov 30 #Vue.js
Vue Elenent实现表格相同数据列合并
Nov 30 #Vue.js
vue中defineProperty和Proxy的区别详解
Nov 30 #Vue.js
详解Vue 的异常处理机制
Nov 30 #Vue.js
ESLint 是如何检查 .vue 文件的
Nov 30 #Vue.js
Vue用mixin合并重复代码的实现
Nov 27 #Vue.js
You might like
PHP的基本常识小结
2013/07/05 PHP
php引用传值实例详解学习
2013/11/06 PHP
PHP模板引擎Smarty的缓存使用总结
2014/04/24 PHP
php实现点击可刷新验证码
2015/11/07 PHP
PHP实现适用于自定义的验证码类
2016/06/15 PHP
Laravel中间件实现原理详解
2016/10/09 PHP
laravel框架的安装与路由实例分析
2019/10/11 PHP
PhpStorm连接服务器并实现自动上传功能
2020/12/09 PHP
关于Javascript模块化和命名空间管理的问题说明
2010/12/06 Javascript
js获取height和width的方法说明
2013/01/06 Javascript
javascript解析ajax返回的xml和json格式数据实例详解
2017/01/05 Javascript
基于JavaScript实现的插入排序算法分析
2017/04/14 Javascript
解决angularjs中同步执行http请求的方法
2018/08/13 Javascript
vue-router判断页面未登录自动跳转到登录页的方法示例
2018/11/04 Javascript
js getBoundingClientRect使用方法详解
2019/07/17 Javascript
vue-mugen-scroll组件实现pc端滚动刷新
2019/08/16 Javascript
如何实现js拖拽效果及原理解析
2020/05/08 Javascript
在JavaScript中查找字符串中最长单词的三种方法(推荐)
2021/01/18 Javascript
[02:41]DOTA2英雄基础教程 亚巴顿
2014/01/02 DOTA
[42:52]Optic vs Serenity 2018国际邀请赛淘汰赛BO3 第二场 8.22
2018/08/23 DOTA
Python使用当前时间、随机数产生一个唯一数字的方法
2017/09/18 Python
python输入错误密码用户锁定实现方法
2017/11/27 Python
深入理解Python爬虫代理池服务
2018/02/28 Python
基于python的docx模块处理word和WPS的docx格式文件方式
2020/02/13 Python
利用python控制Autocad:pyautocad方式
2020/06/01 Python
Python3开发环境搭建详细教程
2020/06/18 Python
Python实现哲学家就餐问题实例代码
2020/11/09 Python
Perfume’s Club美国官网:西班牙第一家在线美容店
2020/06/10 全球购物
Unix如何添加新的用户
2014/08/20 面试题
计算机专业个人简短的自我评价
2013/10/23 职场文书
旷课检讨书2000字
2014/01/14 职场文书
聚美优品励志广告词
2014/03/14 职场文书
担保书格式及范文
2014/04/01 职场文书
2014领导干部四风问题查摆思想汇报
2014/09/13 职场文书
2014年销售工作总结
2014/12/01 职场文书
2015年教师个人业务工作总结
2015/10/23 职场文书