Electron with Vue 框架搭建及打印功能分享
2019.09.27
文档有改进地方,欢迎提出。
安装
-
准备工作:确保本机已经安装了nodejs && npm
-
检查本地是否已经安装了vue:
npm view vue version
;否则先安装vue:npm install vue
-
创建一个electron-vue的demo项目:
vue create electron-vue-demo
-
安装配置:
? Please pick a preset: default (babel, eslint) > Manually select features
? Check the features needed for your project: (*) Babel ( ) TypeScript ( ) Progressive Web App (PWA) Support (*) Router (*) Vuex >(*) CSS Pre-processors (*) Linter / Formatter ( ) Unit Testing ( ) E2E Testing
Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n) y
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass) Sass/SCSS (with node-sass) > Less Stylus
? Pick a linter / formatter config: (Use arrow keys) > ESLint with error prevention only ESLint + Airbnb config ESLint + Standard config ESLint + Prettier
? Pick a linter / formatter config: (Use arrow keys) > ESLint with error prevention only ESLint + Airbnb config ESLint + Standard config ESLint + Prettier
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection) >(*) Lint on save ( ) Lint and fix on commit (requires Git)
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arrow keys) > In dedicated config files In package.json
Save this as a preset for future projects? (y/N) N
此处安装过程需要大约2分钟(视网络决定)
-
进入项目目录:
cd electron-vue-demo
-
启动 Vue.js App 项目:
npm run serve
,打开浏览器登入:http://localhost:8080/ -
安装electron构建包:
vue add electron-builder
使用打印机必须要使用electron 3.0或以下版本,此处先任意选择安装,后续单独更换electron版本。
npm uninstall electron && npm install electron@3.1.13 --save-dev
? Choose Electron Version (Use arrow keys) ^4.0.0 ^5.0.0 > ^6.0.0
electron包文件较大,此处安装过程耗时较长,建议使用淘宝镜像cnpm安装。
-
由于electron将版本的问题,
./src/background.js
中有一处需要更改配置,否则启动不了electron。//删除第15行: // protocol.registerSchemesAsPrivileged([{scheme: 'app', privileges: { secure: true, standard: true } }]) //新增一行: protocol.registerStandardSchemes(['app'], { secure: true })
-
启动electron项目:
npm run electron:serve
-
默认配置了热加载,代码保存后,electron窗口会体现最新效果。
搭建框架
-
安装axios:
npm install axios --save-dev
-
封装数据请求方法:
新建文件夹:
./src/axios/
新建文件并粘贴代码:
api.js
import requestAll from "./request"; //引用fetch,js import apiHeader from './url'; //引用url.js // 数据请求 export function Axios(method, api, params = {}) { let header = ''; header = apiHeader.server; // 浏览器访问 if( !window.require ) { header = '/api'; } // electron访问 return requestAll.Request(header+api, params, method) }
request.js
import axios from 'axios';//引入axios // post请求 function Request(url, data = {}, method = 'post') { return new Promise((resolve, reject) => { axios({ url: url, method: method, headers: { 'Content-Type': 'application/json' }, timeout: 30 * 1000, // 30秒超时 data: data }) .then(res => { // 统一错误状态码 let errorMessage = '操作失败!'; switch (res.data.code) { case 200: errorMessage = '请求成功'; break; case 501: errorMessage = '参数异常'; break; } res.data.errorMessage = errorMessage; //成功 resolve(res.data) }) .catch(res => { //失败 reject(res) }) }) } export default { Request: Request }
url.js
export default { server: 'http://127.0.0.1:12306' }
使用方法:
// 引入封装好的axios方法 import { Axios } from "../axios/api"; // 使用例子 Axios("post", url, params) .then(res => { if (res.code === 200) { resolve(res.data); } else { _this.$alert(res.errorMessage, "提示", { confirmButtonText: "知道了" }); } }) .catch(err => { reject(); _this.$alert(`发生错误!\n${err}`, "提示", { confirmButtonText: "知道了" }); });
浏览器访问项目时,会遇到请求跨域问题,解决方法(
./vue.config.js
文件内容):// vue.config.js相关代码 module.exports = { devServer: { proxy: { '/api': { target: 'http://127.0.0.1:8080', changeOrigin: true, pathRewrite: { '^/api': '/' } } } } }
-
路由设置:
./src/router.js
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ base: process.env.BASE_URL, routes: [ { path: '/', name: 'home', component: () => import('./views/Home.vue'), children: [ { path: '/', redirect: 'test' }, { path: '/test', name: 'test', component: () => import('./components/Test.vue') // 主窗口里面的子组件 } ] }, { path: '/setUp', name: 'setUp', component: () => import('./components/SetUp') // 其他窗口的页面文件 } ] })
窗口设置
-
对这个文件进行编写配置:
./src/background.js
-
引入electron中的ipcMain:
import { ipcMain } from 'electron'
-
主窗口在项目初始化已经设置好,基本配置如下:
function createWindow() { // Create the browser window. win = new BrowserWindow({ width: 1336, // 窗体宽度 height: 890, // 窗体高度 frame: false, // 窗体是否需要标题栏 resizable: false, // 窗口是否可以改变尺寸 webPreferences: { nodeIntegration: true, // 是否集成Node webSecurity: false // 解决跨域问题 } }) }
具体更多窗口配置:https://electronjs.org/docs/api/browser-window
-
electron启动后执行createWindow方法:
// This method will be called when Electron has finished // initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', async () => { if (isDevelopment && !process.env.IS_TEST) { // Install Vue Devtools try { await installVueDevtools() } catch (e) { console.error('Vue Devtools failed to install:', e.toString()) } } createWindow() })
-
设置窗口访问的页面(写在createWindow方法内)
if (process.env.WEBPACK_DEV_SERVER_URL) { // 如果是开发模式 // Load the url of the dev server if in development mode win.loadURL(process.env.WEBPACK_DEV_SERVER_URL) testWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL + "/#/test") // 如果是开发模式,自动打开开发者调试工具 if (!process.env.IS_TEST) win.webContents.openDevTools() if (!process.env.IS_TEST) testWindow.webContents.openDevTools() } else { // 如果是打包模式 createProtocol('app') // Load the index.html when not in development win.loadURL('app://./index.html') testWindow.loadURL('app://./index.html#/test') }
-
设置窗口,开启关闭功能(在createWindow方法内)
win.on('closed', () => { win = null }) testWindow.on('close', (e) => { e.preventDefault() testWindow.hide(); })
打开和关闭窗口
-
文件
./src/background.js
添加窗口的打开和关闭监听:// 打开窗口 ipcMain.on('open-test', (event, arg) => { testWindow.show(); testWindow.webContents.send('param', arg) // 触发这个事件时,传递给将要打开的窗口一个数据 }) // 关闭窗口 ipcMain.on('close-test', function () { testWindow.hide(); })
-
.vue
文件里触发窗口的打开或关闭事件:const { ipcRenderer } = require("electron"); // 文件头部新建electron控制窗口的上下文对象 ipcRenderer.send( "open-testEdit", { data: this.data } ); // 打开窗口命令,"open-testEdit"是"background.js"文件中添加的事件监听,send的第二个参数是传递给窗口的数据对象 ipcRenderer.send("close-testEdit"); // 关闭窗口
窗口之间的通讯
-
可以利用electron中的ipcRenderer.send方法给窗口传递数据;当然,在触发打开或关闭窗口的时候也可以传递数据,因为他们都在使用ipcRenderer.send方法。
-
如果是单纯传递数据(非窗口打开或关闭),可以这样做:
// 首先在文件"./src/background.js"中定义监听方法 ipcMain.on('testChange', (event, message) => { win.webContents.send('getTestData', message); }); // 当任何页面触发'testChange'方法时,win窗口可以接收到message这份数据 // 注意:接收数据的窗口(.vue页面),同时需要做好接收数据的工作 mounted() { // 需要使用electron的ipcRenderer.on方法,参数1:接收数据事件名称,参数2:接收数据或event的方法体 ipcRenderer.on("getTestData", (e, message) => { console.log( message ); }); },
构建软件包
-
./package.json
中给打包操作设置参数{ "name": "WondfoDMS-Pet", // 项目名称(主要应用在软件运行中的名称) "productName": "WondfoDMS-Pet", // 软件包名称(安装包上的名称) "version": "6.6.6", // 软件版本号 "scripts": { "electron:build": "vue-cli-service electron:build --windows --dir --ia32", /* *参数意义: *@ --windows 打包成window软件包 *@ --dir *@ --ia32 软件包为32位 */ }, "win": { "icon": "icon.ico", // 软件包图标,图标放置于项目根目录,尺寸为256*256 "extraResources": "./public" // 页面项目静态文件入口放置的位置 } }
-
执行命令
npm run electron:build
即可打包,软件位置:./dist_electron/win-ia32-unpacked/*.exe
Electron如何实现打印(未完善)
-
新建打印中转页面
printContent
(有关页面路由router配置,此处不讲述),内容:<template> <div class="component"> <webview id="printWebview" ref="printWebview" :src="fullPath" nodeintegration /> </div> </template> <script> const { ipcRenderer } = require("electron"); import path from "path"; export default { name: "printContent", data() { return { printList: [], printDeviceName: "", fullPath: path.join(__static, "print.html"), messageBox: null, htmlData: {}, isAllowPrint: false }; }, mounted() { const webview = this.$refs.printWebview; webview.addEventListener("ipc-message", event => { if (!this.isAllowPrint) return; if (event.channel === "webview-print-do") { webview.print( { silent: true, printBackground: true, deviceName: this.printDeviceName }, data => { if (data) { this.$message({ message: "打印成功!", type: "success" }); } else { this.$alert("打印失败!", "提示", { confirmButtonText: "知道了" }); } } ); } }); }, methods: { print(data, isAllowPrint) { this.htmlData = data; // 设置打印机名称 this.printDeviceName = this.htmlData.printDeviceName; this.isAllowPrint = isAllowPrint; this.printRender(); }, printRender() { // 获取<webview>节点 const webview = this.$refs.printWebview; // 发送信息到<webview>里的页面 webview.send("webview-print-render", { printName: this.htmlData.printDeviceName, data: this.htmlData }); } } }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> webview { width: 100%; height: 100%; } </style>
-
文件
./src/background.js
中需要新建获取打印列表事件的监听:// 获取打印列表 ipcMain.on('getPrinterList', (event) => { //主线程获取打印机列表 const list = printWindow.webContents.getPrinters(); //通过webContents发送事件到渲染线程,同时将打印机列表也传过去 printWindow.webContents.send('printerList', list); });
-
触发打印的页面(即打印按钮的页面):
<template> <div class="component"> <div class="content"> <el-form :inline="true" size="small" label-width="108px"> <el-form-item class="form_item_btn"> <!-- 打印按钮 --> <el-button type="primary" @click="doPrint()">打印</el-button> </el-form-item> </el-form> <el-row> <el-col :span="16" style="height:540px;"> <!-- 打印预览 --> <pinter ref="print" :html-data="HtmlData" class="print_preview"></pinter> </el-col> </el-row> </div> </div> </template> <script> const { ipcRenderer } = require("electron"); import Pinter from "./PrintContent.vue"; // 引用上面新建的组件 export default { name: "test", components: { Pinter }, data() { return { HtmlData: "", selectedPrinter: "", }; }, mounted() { // 获取打印列表 ipcRenderer.send("getPrinterList"); ipcRenderer.on("printerList", (e, message) => { // 设置打印机名字 this.selectedPrinter = message[0].name; // 可在页面给予选择打印机功能,此处不讲述 }); }, methods: { // 触发打印事件 doPrint() { this.HtmlData = '打印数据'; this.$refs.print.print(this.HtmlData); } } }; </script>
-
新建预览文件(同时也是打印模版文件,关键!):
./public/print.html
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <!-- 如果需要使用第三方组件,需要额外放置组件文件在public中,并引用 --> <link rel="stylesheet" href="./element-ui.css" type="text/css" /> <script src="./vue.js"></script> <script src="./element-ui.js"></script> <!-- 引用本页面css --> <link rel="stylesheet" href="./print_preview.css" type="text/css" /> </head> <body id='bd'> <div id="app"> 打印内容 </div> <script> // 具体数据渲染,根据实际业务开发 new Vue({ el: '#app', data: function () { return { data: '' // 本页需要的数据 } }, mounted() { const { ipcRenderer } = require('electron') // 接收打印数据 参数2:第1步中给webview发送的数据{ printName, data } ipcRenderer.on('webview-print-render', (event, data) => { this.data = data.data; ipcRenderer.sendToHost('webview-print-do'); }); } }) </script> </body> </html>
打印功能参考:https://juejin.im/post/5bf8b580e51d4522143b7b03
附录
框架搭建参考视频:https://www.youtube.com/watch?v=Fl7—SEORQ
electron构建的第三方包源码:https://github.com/nklayman/vue-cli-plugin-electron-builder