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-drawer-layout实现手势滑出菜单栏
Nov 19 Vue.js
详解如何在vue+element-ui的项目中封装dialog组件
Dec 11 Vue.js
Vue——解决报错 Computed property &quot;****&quot; was assigned to but it has no setter.
Dec 19 Vue.js
vue实现登录、注册、退出、跳转等功能
Dec 23 Vue.js
vue自定义组件实现双向绑定
Jan 13 Vue.js
Vue中的nextTick作用和几个简单的使用场景
Jan 25 Vue.js
vue 实现click同时传入事件对象和自定义参数
Jan 29 Vue.js
vue中axios封装使用的完整教程
Mar 03 Vue.js
vue中利用mqtt服务端实现即时通讯的步骤记录
Jul 01 Vue.js
详解gantt甘特图可拖拽、编辑(vue、react都可用 highcharts)
Nov 27 Vue.js
Vue.js中v-bind指令的用法介绍
Mar 13 Vue.js
vue3引入highlight.js进行代码高亮的方法实例
Apr 08 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对象Object的概念 介绍
2012/06/14 PHP
php中设置index.php文件为只读的方法
2013/02/06 PHP
实例分析基于PHP微信网页获取用户信息
2017/11/24 PHP
Mootools 1.2教程(3) 数组使用简介
2009/09/14 Javascript
jQuery 获取对象 根据属性、内容匹配, 还有表单元素匹配
2010/05/31 Javascript
getElementByIdx_x js自定义getElementById函数
2012/01/24 Javascript
javascript实例分享---具有立体效果的图片特效
2014/06/08 Javascript
jQuery弹出层插件Lightbox_me使用指南
2015/04/21 Javascript
基于html5和nodejs相结合实现websocket即使通讯
2015/11/19 NodeJs
jQuery操作基本控件方法实例分析
2015/12/31 Javascript
深入理解JavaScript中的对象复制(Object Clone)
2016/05/18 Javascript
微信JS-SDK坐标位置如何转换为百度地图坐标
2016/07/04 Javascript
详解nodejs 文本操作模块-fs模块(二)
2016/12/22 NodeJs
jQuery 实现图片的依次加载图片功能
2017/07/06 jQuery
bootstrap-Treeview实现级联勾选
2017/11/23 Javascript
node.js+express+mySQL+ejs+bootstrop实现网站登录注册功能
2018/01/12 Javascript
node 命令方式启动修改端口的方法
2018/05/12 Javascript
python遍历文件夹并删除特定格式文件的示例
2014/03/05 Python
python获取网页状态码示例
2014/03/30 Python
Python扩展内置类型详解
2018/03/26 Python
python 如何对logging日志封装
2020/12/02 Python
flask项目集成swagger的方法
2020/12/09 Python
html5文本内容_动力节点Java学院整理
2017/07/11 HTML / CSS
全球摩托车装备领导者:RevZilla
2017/09/04 全球购物
美国马匹用品和马钉购物网站:State Line Tack
2018/08/05 全球购物
英国打印机墨盒销售网站:Ink Factory
2019/10/07 全球购物
银行职员思想汇报
2013/12/31 职场文书
社区义诊活动总结
2014/04/30 职场文书
小学节能减排倡议书
2014/05/15 职场文书
公司演讲稿开场白
2014/08/25 职场文书
干部作风整顿自我剖析材料和整改措施
2014/09/18 职场文书
银行先进个人总结
2015/02/15 职场文书
工作能力自我评价2015
2015/03/05 职场文书
出国留学单位推荐信
2015/03/26 职场文书
七年级话题作文之执着
2019/11/19 职场文书
MySQL学习之基础命令实操总结
2022/03/19 MySQL