electron5+vue3.0搭建一个桌面应用

之前因为项目需求 需要做一个桌面应用 所以在bilibili上学习了一部分基础知识后 又参考[https://zhuanlan.zhihu.com/p/75764907]这篇博客搭建了一个electron+vue 的桌面价格查询应用,此文章记录一下搭建流程和遇到的一些问题

一.electron基础知识点

electron 我认为就是个浏览器 ,里面显示什么.怎么写还是跟平时的页面怎么写一样,只不过一个是用浏览器直接打开,一个是以桌面应用的方法打开

准备工作
1.cnpm init -y 创建package.json
2. cnpm i electron -D 安装electron
3.新建一个main.js 入口文件 这里写入electron 主进程代码
4.修改package.json 在script里添加
“package”:“electron-packager ./ Helloword --platform=win32 --arch=x64 --icon=favicon.ico --out=./out --asar --app-version=0.0.1 --overwrite --ignore=node_modules” 使用cnpm run package 打包
5.新建一个html 让html显示到electron窗口上
创建electron窗口 开始正式学习
main.js里直接引用的js是主进程 在html中引用的是渲染进程 渲染进程不能直接使用主进程的一些属性 需要用remote

// main.js
var electron = require('electron')

var app = electron.app // 引用app

var globalShortcut = electron.globalShortcut // 全局快捷键

var BrowserWindow = electron.BrowserWindow // 窗口引用

var mainWindow = null // 声明要打开的主窗口

app.on('ready', () => {
    // 把窗口设置好
    mainWindow = new BrowserWindow({
        width: 900, height: 800,
        webPreferences: {
            nodeIntegration: true,// 让所有的node都能在我们渲染进程中使用
            contextIsolation: false,
            enableRemoteModule: true // 配置才能用remote 
        }
    })

    // 注册全局函数
    globalShortcut.register('ctrl+e',()=>{
        mainWindow.loadURL('https://jspang.com')
    })

    // 判断这个快捷键是否添加成功
    let isRegister = globalShortcut.isRegistered('ctrl+e')
    console.log('-----',isRegister,'-------')

    // 打开调试面板 如果修改了Menu 不能在view下面的toggle中打开调试模式的话
    // mainWindow.webContents.openDevTools()

    // 引入自定义菜单
    // require('./main/menu')
    // 把网页加进来
    mainWindow.loadFile('demo7.html') //加载html页面


    // BrowserView
    // 在页面中嵌套子页面
    /* var BrowserView = electron.BrowserView
    var view = new BrowserView()
    mainWindow.setBrowserView(view) //view放在主窗口下
    view.setBounds({//设置样式 位置
        x:0,
        y:120,
        width:900,
        height:600
    })
    // 页面里放入什么
    view.webContents.loadURL('https://www.baidu.com') */


    // window.open 子窗口 BrowserWindow // 打开一个窗口

    // 关闭窗口时 要把mainwindow设置null 不然内存会越存越多
    mainWindow.on('closed', () => {
        mainWindow = null
    })
})

app.on('will-quit',function(){
    // 将要退出的这个应用之前 注销快捷键方法
    globalShortcut.unregister('ctrl+e') //取消单个快捷键

    // globalShortcut.unregisterAll() //取消所有的快捷键
})
// menu.js
const { Menu, BrowserWindow } = require('electron')

var template = [
    {
        label: "凤来仪",
        submenu: [
            {
                label: '精品spa',
                accelerator:'ctrl+n',
                click: () => {
                    var win = new BrowserWindow({
                        width: 500,
                        height: 500,
                        webPreferences: {
                            nodeIntegration: true,
                            contextIsolation: false,
                        }
                    })

                    win.loadFile('yellow.html')
                    win.on('closed', () => {
                        win = null
                    })

                }
            },
            { label: "泰式按摩" }
        ]
    },
    {
        label: "大浪淘沙",
        submenu: [
            { label: '牛奶玫瑰浴' },
            { label: "脚底按摩" }
        ]
    }
]

var m = Menu.buildFromTemplate(template) // 构建一下菜单才能使用
Menu.setApplicationMenu(m) //设置菜单


1.测试文字进来

...
<body>
    <button id="btn">测试文字进来</button>
    <div id="myTest"></div>
</body>
<script>
var fs = require('fs')

window.onload = function(){

    var btn = this.document.querySelector('#btn')
    var myTest = this.document.querySelector('#myTest')

    btn.onclick = function(){
        fs.readFile('test.txt',(err,data) => {
            myTest.innerHTML = data
        })
    }
}
</script>

2.打开新的窗口

...
<body>
    <button id="btn">打开新的窗口</button>
    <div id="myTest"></div>
</body>
<script>
const btn = this.document.querySelector('#btn')
// 这样可以共用main.js中的BrowserWindow
// main.js里直接引用的js是主进程  在html中引用的是渲染进程 渲染进程不能直接使用主进程的一些属性 需要用remote
const BrowserWindow = require('electron').remote.BrowserWindow

window.onload = function(){
    btn.onclick = function(){
        newWin  = new BrowserWindow({
            width:500,
            height:500
        })

        newWin.loadFile('yellow.html')
        newWin.on('closed',()=>{
            newWin = null
        })
    }
}
const {remote} = require('electron')

var rightTemplate = [
    {label:"粘贴",accelerator:'ctrl+c',},
    {label:"复制",accelerator:'ctrl+v',}
]

var m = remote.Menu.buildFromTemplate(rightTemplate)

// 右键菜单
window.addEventListener('contextmenu',(e)=>{
    // alert(1)
    e.preventDefault()
    m.popup({window:remote.getCurrentWindow()})
})
</script>

3.打开子窗口

<body>
    <h1>
        <a id="aHref" href="https://www.baidu.com">百度</a>
    </h1>
    <button id="mybtn">打开子窗口</button>
    <div id="mytext"></div>
</body>
<script>
var {shell} = require('electron')

var aHref = this.document.querySelector('#aHref')
var mybtn = document.querySelector('#mybtn')
var mytext = document.querySelector('#mytext')

aHref.onclick = function(e){
    e.preventDefault() // 默认链接会打开到当前应用里面的
    var href = this.getAttribute('href')
    // 这样可以在浏览器中打开 在谷歌浏览器中打开
    shell.openExternal(href)
}

mybtn.onclick = function(){
    window.open('./popup_page.html')
}

window.addEventListener('message',(msg)=>{
    // console.log(msg)
    mytext.innerHTML = JSON.stringify(msg.data)
})
</script>
...
<body>
    <h2>我是弹出的子窗口</h2>
    <button id="popbtn">向父窗口传递信息</button>
</body>
<script>
    var popbtn = document.querySelector('#popbtn')
    popbtn.onclick = (e)=>{
        // alert(1)
        window.opener.postMessage('我是子窗口传递的信息')
    }
</script>

4.打开图片 保存文件 弹出对话框

<body>
    <button id="openBtn">打开图片</button>
    <button id="saveBtn">保存文件</button>
    <button id="messageBtn">弹出对话框</button>
    <img style="width:100%" id="images">
    <script>
        const {dialog} = require('electron').remote
        const fs = require('fs')

        // 上传文件
        const openBtn = document.querySelector('#openBtn')
        // 也可以用input file js的方式  这个是electron 自己的方式
        openBtn.onclick = function(){
            dialog.showOpenDialog({
                title:"请选择你喜欢的照片",
                defaultPath:'./2.png',//可以选择填写默认照片路径
                filters: [{name:'img',extensions:['png']}],
                buttonLabel:'打开我喜欢的图片',// 设置图片打开按钮文字
            }).then(res => {
                console.log(res)
                let images = document.querySelector('#images')
                images.setAttribute('src',res.filePaths[0])
            }).catch(err=>{
                console.log(err)
            })
        }

        // 保存文件 相当于新建一个文件 然后新建成功后 可以在里面写入内容
        const saveBtn = document.querySelector('#saveBtn')
        saveBtn.onclick = function(){
            dialog.showSaveDialog({
                title:"保存文件"
            }).then(res => {
                fs.writeFileSync(res.filePath,'jspang.com')

            }).catch(err=>{
                console.log(err)
            })
        }

        // 弹出对话框
        const messageBtn = document.querySelector('#messageBtn')
        messageBtn.onclick = function(){
            dialog.showMessageBox({
                type:"warning",
                title:'去不去由你',
                message:"是不是要去",
                buttons:['我要去','不去了' ]
            }).then(res=>{
                console.log(res)
                // 返回的是buttons的下标
            })
        }
    </script>

5.断网提醒功能

<body>
    <h2>JSPANG.com 断网提醒测试</h2>
</body>
<script>
// 可以在F12调试工具上 断网试一下
    // online 在线 offline断网
    window.addEventListener('online',function(){
        // 断网的时候再次连接才会触发
        alert('来网啦')
    })
    window.addEventListener('offline',function(){
        alert('断网啦,先行离开一会儿~~')
    })
</script>

6.底部消息通知

<body>
   <button id="notifyBtn">通知消息</button>
</body>
<script>
    var notifyBtn = document.querySelector('#notifyBtn')
    var option = {
        title:'小二,来订单了,出来接客!',
        body:'有大官人翻你牌子啦'
    }

    notifyBtn.onclick = function(){
        new window.Notification(option.title,option)
    }
</script>

7.剪切板功能

<body>
    <div>
        激活码: <span id="code">jasdfdlfjdsklfjkldsfj</span>
        <button id='btn'>复制激活码</button>
    </div>
    <script>
        // clipboard 这个在渲染进程和主进程中都能使用
        const {clipboard} = require('electron')
        var btn = document.querySelector('#btn')
        var code = document.querySelector('#code')
        btn.onclick = function(){
            clipboard.writeText(code.innerHTML)
            alert('复制成功')
        }
    </script>

8.除了使用remote,我们还可以用另一种方式让渲染进程与主进程进行通信

// 主进程 main.js
import { ipcMain} from 'electron'
ipcMain.on('message',function(event,arg){
  // arg 是从渲染进程返回来的数据
  console.log(arg)

  // 这里是传给渲染进程的数据
  // event.sender.send 给渲染进程传递数据
  fs.rendFile(path.join(__dirname,"/test.txt"),'utf8',(err,data) => {
    if(err){
      event.sender.send('replay','读取失败')
    }else{
      event.sender.send('replay',data)
    }
  })
})
// 渲染进程 html
import { ipcRenderer } from 'electron'
// 这里接收主进程传递过来的参数 这里的on要对应主进程send过来的名字
ipcRenderer.on("replay",function(event,arg){
  // 这里的arg 是从主线程请求的数据
  console.log('render+'+arg)
})
// 这里的会传递回主进程,这里的第一个参数需要对应主进程里on注册事件的名字一致
ipcRenderer.send('message','传递回去arg')

二.搭建vue+electron项目

创建vue项目

vue create electron-vue-demo
自定义安装

自动安装electron

vue add electron-builder
在安装过程中可能会因为网络问题卡着 我们可以强制结束掉然后再重新执行
安装完后会自动在src目录下生成background.js(electron代码)并修改package.json

修改package.json

在package.json里添加
“main”: “background.js”,
在scripts里添加
“electron:build”: “vue-cli-service electron:build”,
“electron:serve”: “vue-cli-service electron:serve”,
“postinstall”: “electron-builder install-app-deps”,
“postuninstall”: “electron-builder install-app-deps”
devDependencies里添加
“electron”: “^5.0.6”,
“vue-cli-plugin-electron-builder”: “^1.3.5”,

在安装一下依赖包

cnpm install

编译并启动APP

npm run electron:serve

编译打包

npm run electron:build

vue 单独打包

npm run build

修改electron配置文件

electron配置文件和vue的配置文件放在一起的 都是在vue.config.js里配置
也可以在package.json下面添加一个build:{}配置
在vue.config.js里添加配置 更多其他配置可以到官网上查询

pluginOptions: {
    electronBuilder: {
      builderOptions: {
        "appId": "com.example.app",
        "productName": "Price-look-up",//项目名,也是生成的安装文件名,即aDemo.exe
        "copyright": "Copyright © 2021",//版权信息
        "directories": {
          "output": "./dist_out"//输出文件路径
        },
        "extraResources":  {
          "from": "./public/config.js", // 把config.js文件复制到resources文件夹下面 让它不被打包进去
          "to": "./config.js"
        },
        "win": {//win相关配置
          "icon": "./favicon.ico",//图标,当前图标在根目录下,注意这里有两个坑
          "target": [
            {
              "target": "nsis",//利用nsis制作安装程序
              "arch": [
                "x64"//64位
              ]
            }
          ]
        },
        "nsis": {
          "oneClick": false, // 是否一键安装
          "allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
          "allowToChangeInstallationDirectory": true, // 允许修改安装目录
          "installerIcon": "./favicon.ico",// 安装图标
          "uninstallerIcon": "./favicon.ico",//卸载图标
          "installerHeaderIcon": "./favicon.ico", // 安装时头部图标
          "createDesktopShortcut": true, // 创建桌面图标
          "createStartMenuShortcut": true,// 创建开始菜单图标
          "shortcutName": "Price-look-up", // 图标名称
        }
      }
    }
  }

在background.js可以修改webPreFerences配置 自定义头部 如

...
async function createWindow() {
  // Create the browser window.
  const win = new BrowserWindow({
    width: 1000,
    height: 800,
    // frame: false,// 取消默认头部 自定义头部
    webPreferences: {
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      // nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      nodeIntegration: true, // 在渲染模板上可以使用node
      contextIsolation: false, // 以前electron版本contextIsolation默认为false 现在已经默认为true了 所以要想用node还得改contextIsolation为false
      enableRemoteModule: true,// 可以使用remote
      
    },
    icon: `${__static}/favicon.ico`
  })

  // 注册全局快捷键 Ctrl+ F12打开开发工具调试
  globalShortcut.register('ctrl+F12', () => {
    win.webContents.openDevTools()
  })

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // Load the url of the dev server if in development mode
    await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    createProtocol('app')
    // Load the index.html when not in development
    win.loadURL('app://./index.html')
  }

  createMenu()
}
...

遇到的问题:

1.在渲染模板上使用require(electron)显示 __dirname is undefined 或者是 fs.existsSync is not a function
解决办法:

const {getCurrentWindow} = require('electron').remote
 改为
const {getCurrentWindow} = window.require('electron').remote

webpack会自动编译js文件 改为window.require时 webpack不会编译 会等到node去编译

这个用浏览器打开 还是会提示window.require is not a function错误, 但是用electron桌面应用的方式打开就会显示正常

window.require is not a function
at eval (Header_menu.vue?a063:19)
at Module…/node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader-v16/dist/index.js?!./src/views/Header_menu.vue?vue&type=script&lang=js

2.之前在运行npm run electron:build打包过程中经常失败,查了一下,大概率是因为网络问题,下载electron打包所需的一些包下载不下来,我们可以手动下载下来再打包,也可以改为淘宝镜像打包,我改为淘宝镜像打包后一下就打包成功了

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值