electron-vue 制作悬浮球

白子默
2023-12-01

1、background.js配置文件

'use strict'
import {updateHandle} from './utils/update.js';
import ElectronStore from 'electron-store'
import { app, protocol, ipcMain,screen, BrowserWindow,globalShortcut } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'

import electronDefaultData from './config/electron-default-data'

const isDevelopment = process.env.NODE_ENV !== 'production'

global.$winodws = {
  main: null
}

const electron = require('electron')
const Menu = electron.Menu

let win,ball
let electronStore
protocol.registerStandardSchemes(['app'], { secure: true })

function createWindow() {
  showBallWindow();
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  app.quit()
})

app.on('quit', () => {
  app.quit()
})

app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // Install Vue Devtools
  }
  // 初始化配置文件
  electronStore = new ElectronStore({
    defaults: electronDefaultData,
    cwd: app.getPath('userData')
  })
  global.electronStore = electronStore
  createWindow()
})

if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

//进入主页
ipcMain.on('mainPage',function(){
  showMainPage();
})
//显示悬浮球
ipcMain.on('show_ball',function(){
  win=null
  if (ball) {
    if (ball.isVisible()) {
      showBallWindow();
    } else {
      ball.showInactive();
    }
  } else {
    showBallWindow();
  }
})


//显示主页
function showMainPage(){
  if(win==null){
    let size = screen.getPrimaryDisplay().workAreaSize
    let width = parseInt(size.width)
    let height = parseInt(size.height)
    let appWidth=parseInt(width/3)
    Menu.setApplicationMenu(null)
    win = new BrowserWindow({
      width: appWidth,
      height: height,
      x:width-appWidth,
      y:0,
      closable:true,
      frame: false,
      alwaysOnTop: true,
      webPreferences: {
        enableRemoteModule: true,
        nodeIntegration: true,
        webSecurity: false
      }
    })
    if (process.env.WEBPACK_DEV_SERVER_URL) {
      win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
    } else {
      win.loadURL('app://./index.html')
    }
    globalShortcut.register('CmdOrCtrl+T', () => {
      win.webContents.openDevTools();
    })
  }else {
    win.focus()
    win.show()
  }
}


function showBallWindow() {
  if(ball==null){
    ball = new BrowserWindow({
      width: 175,
      height: 65,
      type: 'toolbar',    //创建的窗口类型为工具栏窗口
      frame: false,   //要创建无边框窗口
      resizable: false, //禁止窗口大小缩放
      show: true,    //先不让窗口显示
      webPreferences: {
        devTools: true,
        nodeIntegration: true,
        enablemotemodule: true,
        webSecurity: false
      },
      transparent: true,  //设置透明
      alwaysOnTop: true,
    });
    const size = screen.getPrimaryDisplay().workAreaSize;   //获取显示器的宽高
    const winheight = parseInt(size.height)
    const winSize = ball.getSize();  //获取窗口宽高
    //设置窗口的位置 注意x轴要桌面的宽度 - 窗口的宽度
    ball.setPosition(size.width - winSize[0]-5, winheight-100);
    if (process.env.WEBPACK_DEV_SERVER_URL) {
      ball.loadURL(process.env.WEBPACK_DEV_SERVER_URL+'#/ball');
    } else {
      createProtocol('app')
      ball.loadURL('app://./index.html'+ '#/ball')
    }

    ball.once('ready-to-show', () => {
      ball.show()
    });

    ball.on('close', () => {
      ball = null;
    })
    globalShortcut.register('CmdOrCtrl+Y', () => {
      ball.webContents.openDevTools();
    })
    //更新软件
    updateHandle(ball);
  }else {
    ball.focus()
    ball.show()
  }

}

ipcMain.on('createSuspensionMenu', (e) => {
  const rightM = Menu.buildFromTemplate([
    {
      label: '显示主页',
      click: () => {
        showMainPage();
      }
    },{
      label: '关闭主页',
      click: () => {
        if(win!=null){
          win.hide()
        }
      }
    }
  ]);
  rightM.popup({});
});

//登录窗口最小化
ipcMain.on('window-min',function(){
  win.minimize();
})
//登录窗口最大化
ipcMain.on('window-max',function(){
  if(win.isMaximized()){
    win.restore();
  }else{
    win.maximize();
  }
})

//TODO 置顶
ipcMain.on('window-top', () => {
  win.setAlwaysOnTop(false);
});

//关闭应用
ipcMain.on('window-close', () => {
  app.quit()
});
//隐藏窗口
ipcMain.on('win-hide', () => {
  if(win!=null){
    win.hide()
  }
});
//显示窗口
ipcMain.on('win-show', () => {
  showMainPage()
});


const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
  app.quit()
} else {
  app.on('second-instance', (event, commandLine, workingDirectory) => {
    if (!win) {
      showMainPage()
    }
    if (win.isMinimized()) win.restore()
    win.focus()
    win.show()
    sendUpdateMessage({
      cmd: 'second-instance-msg',
      message: commandLine,
    })
  })
}

//给渲染进程发送消息
function sendUpdateMessage(text) {
  if(win!=null){
    win.webContents.send('message', text)
  }
}

//给渲染进程发送消息
function sendBallUpdateMessage(text) {
  if(ball!=null){
    ball.webContents.send('message', text)
  }
}

2、路由模式

import Vue from 'vue'
import Router from 'vue-router'
import { asyncRouterMap } from '@/config/router.config'

// hack router push callback
const originalPush = Router.prototype.push
Router.prototype.push = function push (location, onResolve, onReject) {
  if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
  return originalPush.call(this, location).catch(err => err)
}
Vue.use(Router)

const $router = new Router({
  // mode: 'history',
  base: process.env.BASE_URL,
  routes: asyncRouterMap,
  scrollBehavior (to, from, savedPosition) {
    return { x: 0, y: 0}
  }
})
// 解决Loading chunk (\d)+ failed问题
$router.onError((error) => {
  const pattern = /Loading chunk (\d)+ failed/g;
  const isChunkLoadFailed = error.message.match(pattern);
  if(isChunkLoadFailed){
    // 用路由的replace方法,并没有相当于F5刷新页面,失败的js文件并没有从新请求,会导致一直尝试replace页面导致死循环,而用 location.reload 方法,相当于触发F5刷新页面,虽然用户体验上来说会有刷新加载察觉,但不会导致页面卡死及死循环,从而曲线救国解决该问题
    location.reload();
    // const targetPath = $router.history.pending.fullPath;
    // $router.replace(targetPath);
  }

});
export default $router;

3、悬浮球页面

功能:右侧菜单事件,拖拽,双击进入主页

<template>
  <div class="content_body" >
    <div class="ball">
      <div @dblclick="goMainPage"  >
        <span style="color: transparent">1</span>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "ball",
  data () {
    return {
    }
  },
  created() {
    let win;
    let biasX ;
    let biasY;
    if (navigator.userAgent.indexOf('Electron') != -1) {
      const remote = require('electron').remote
      const {ipcRenderer: ipc} = require('electron');
      win= remote.getCurrentWindow();
      biasX = 0;
      biasY = 0;
      let that = this;
      document.addEventListener('mousedown', function (e) {
        switch (e.button) {
          case 0:
            biasX = e.x;
            biasY = e.y;
            document.addEventListener('mousemove', moveEvent);
            break;
          case 2:
            ipc.send('createSuspensionMenu')
            break;
        }
      });

      document.addEventListener('mouseup', function () {
        biasX = 0;
        biasY = 0;
        document.removeEventListener('mousemove', moveEvent)
      });
    }

    function moveEvent(e) {
      if (navigator.userAgent.indexOf('Electron') != -1) {
        if(win!=null){
          win.setPosition(e.screenX - biasX, e.screenY - biasY)
        }
      }
    }
  },
  methods: {
    goMainPage () {
      if (navigator.userAgent.indexOf('Electron') != -1) {
        const {ipcRenderer: ipc} = require('electron');
        ipc.send('mainPage')
      }
    }
  },
}

</script>

<style>
body{
  background-color: transparent;
}
* {
  padding: 0;
  margin: 0;
}
.ball {
  background:  url("../../assets/ballbg.png") no-repeat;
}

.content_body {
  position: relative;
  text-align: center;
  line-height: 65px;
  /* 过渡效果在IE下展示效果不友好 */
  transition: all 0.08s;
  user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  -webkit-user-select: none;
  top: 0%;
  left: 0%;
}
</style>
 类似资料: