electron + vue3 自定义窗口:移动,缩放,置顶

electron

main.js

const { BrowserWindow , ipcMain } = require('electron');
const path = require("path")
const CustomWindow = require('./CustomWindow')

const win = new BrowserWindow({
    frame: false,
    transparent: true,
    center: true,
    webPreferences: {
        preload: path.join(__dirname, `./preload.js`)
    }
})

const customWin = new CustomWindow(win)
ipcMain.on('on-custom-win', (_, funName, ...arg) => customWin[funName](...arg));

preload.js

const { contextBridge, ipcRenderer } = require('electron');
const handleWin = {
    changeSize: (...args) => ipcRenderer.send('on-custom-win', 'autoMax', ...args),
    moveStart: () => ipcRenderer.send('on-custom-win', 'moveStart'),
    moveEnd: () => ipcRenderer.send('on-custom-win', 'moveEnd'),
    top: () => ipcRenderer.send('on-custom-win', 'top'),
    maximize: () => ipcRenderer.send('on-custom-win', 'maximize'),
    onStatus: (fun) => ipcRenderer.on('window-status', fun)
};
contextBridge.exposeInMainWorld('ipcApi', {
    handleWin 
});

CustomWindow.js

const { screen } = require('electron');
/**
 * 窗口移动|缩放|置顶
 */
module.exports = class {
    constructor(win) {
        this.isMove = false;
        this.startPosition = [0, 0]
        this.oldPosition = [0, 0];
        this.maxStatus = false;
        this.topStatus = false;
        this.nowSize = null;
        this.nowPosition = null;
        this._initialization(win);
    };
    _initialization(win) {
        this.win = win;
        this.defaultSize = win.getSize();
        this.defaultPosition = win.getPosition();
        if (win.isAlwaysOnTop()) this.top();
        this.autoMax(...this.defaultSize);
    };
    _isWin = () => this.win && !this.win.isDestroyed();
    moveStart() {
        this.isMove = true;
        if (!this._isWin()) return this.moveEnd();
        // 获取鼠标按下位置
        let { x, y } = screen.getCursorScreenPoint();
        const winPosition = this.win.getPosition();
        x -= winPosition[0];
        y -= winPosition[1];
        this.startPosition = [x, y];
        this._moveing();
    };
    _moveing() {
        if (!this.isMove) return;
        let { x, y } = screen.getCursorScreenPoint();
        x -= this.startPosition[0];
        y -= this.startPosition[1];
        if (x !== this.oldPosition[0] || y !== this.oldPosition[1]) {
            this._restoreSize();
            this.win.setPosition(x, y);
            this.oldPosition = [x, y];
        };
        setTimeout(() => this._moveing(), 16);
    };
    moveEnd = () => this.isMove = false;
    _restoreSize() {
        if (!this.maxStatus || !this.oldPosition[0] || !this.oldPosition[1]) return;
        let startPositionX = parseInt(this.nowSize[0] / 2);
        const { x: leftBoundary, width: screenWidth } = this._getNowScreenSize();
        const rightBoundary = leftBoundary + screenWidth - this.nowSize[0];
        let { x: movePosition } = screen.getCursorScreenPoint();
        movePosition -= startPositionX;
        if (movePosition < leftBoundary) {
            startPositionX += movePosition - leftBoundary;
        };
        if (movePosition > rightBoundary) {
            startPositionX += movePosition - rightBoundary;
        };
        this.startPosition[0] = startPositionX;
        this.win.setSize(...this.nowSize);
        this.maxStatus = false;
        this._sendStatus('max', false);
    };
    _getNowScreenSize() {
        let { x, y } = this.win.getBounds();
        if (x < 0) x = 0;
        for (const display of screen.getAllDisplays()) {
            if (x >= display.bounds.x && x < display.bounds.x + display.bounds.width &&
                y >= display.bounds.y && y < display.bounds.y + display.bounds.height) {
                return display.bounds;
            };
        };
    };
    top() {
        if (!this._isWin()) return;
        this.topStatus = !this.topStatus;
        this.win.setAlwaysOnTop(this.topStatus);
        this._sendStatus('top', this.topStatus);
    };
    autoMax(width, height) {
        if (!this._isWin() || !this.win.isResizable() || this.maxStatus) return;
        const { width: screenWidth, height: screenHeight } = this._getNowScreenSize();
        if (screenWidth - width < 100 && screenHeight - height < 100) {
            this.nowSize = this.defaultSize;
            this.nowPosition = this.defaultPosition;
            this.maxStatus = true;
            this._sendStatus('max', true);
            this.win.maximize();
            this.oldPosition = [0, 0];
        };
    };
    maximize() {
        if (!this._isWin() || !this.win.isResizable()) return;
        this.maxStatus = !this.maxStatus;
        if (this.maxStatus) {
            this.nowSize = this.win.getSize();
            this.nowPosition = this.win.getPosition();
            this.win.maximize();
        } else {
            this.win.setSize(...this.nowSize);
            this.win.setPosition(...this.nowPosition);
        };
        this.oldPosition = [0, 0];
        this._sendStatus('max', this.maxStatus);
    };
    _sendStatus = (type, value) => this.win.webContents.send('window-status', { type, value });
}

VUE

main.js 注册指令

import { createApp } from 'vue'
import App from '@/App.vue'
import registerDirectives from '@/directives'
const app = createApp(App)
registerDirectives(app)
app.mount('#app')

directives 自定义指令

index.js 

import interactWin from './interactWin';
export default function registerDirectives(app) {
    app.directive("handle-win", { mounted: (...arg) => interactWin.handle(...arg) })
}

interactWin.js


const { handleWin } = window.ipcApi;
class InteractWin {
    constructor() {
        this.el = null;
    };
    handle(el, binding = null) {
        if (this.el) return;
        this.el = el;
        this.el.addEventListener('mousedown', this._mouseDown);
        this.el.addEventListener('dblclick', this._mouseDblclick);
        window.addEventListener('resize', this._handleResize);
        if (binding?.value && typeof binding.value === 'function') {
            handleWin.onStatus((_, ...arg) => binding.value(...arg));
        };
    };
    _mouseDblclick = () => handleWin.maximize();
    _mouseDown = (e) => {
        if (e.button !== 0) return;
        handleWin.moveStart();
        window.addEventListener('mouseup', this._globalMouseUp);
    };
    _globalMouseUp = (e) => {
        if (e.button !== 0) return;
        handleWin.moveEnd();
        window.removeEventListener('mouseup', this._globalMouseUp);
    };
    //此处加防抖可提升性能
    _handleResize = () => {
        handleWin.changeSize(window.innerWidth, window.innerHeight);
    };
};
export default new InteractWin();

vue使用指令操作窗口

在头部DOM上使用 v-handle-win;

1:左键移动窗口;

2:双击最大化窗口与还原窗口;

3:最大化移动窗口自动还原窗口;

4:手动改变窗口尺寸至屏幕最大范围(范围阈值在CustomWindow.js内修改)时自动最大化;

<template>
    <div v-handle-win="onWinHandle">
       <!-- 自定义窗口头部,使用指令并监听使用反馈 -->
    </div>
    <div @click="winTop">
       是否置顶:{{ topStatus }}
    </div>
    <div @click="winMax">
       是否最大化:{{ maxStatus }}
    </div>
</template>
<script setup>
import { ref } from 'vue';
const { handleWin } = window.ipcApi;
const maxStatus = ref(false)
const topStatus = ref(false)
/**
 * 监听操作窗口头部的反馈
 */
const onWinHandle = ({ type, value }) => {
    switch (type) {
        case 'max':
            maxStatus.value = value
            break;
        case 'top':
            topStatus.value = value
            break;
    }
}
/**
 * 窗口置顶
 */
const winTop = () => handleWin.top();
/**
 * 最大化与还原
 */
const winMax = () => handleWin.maximize();

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要实现自定义菜单栏,可以使用 Electron 提供的 Menu 模块。以下是一个使用 Electron Vite Vue 实现自定义菜单栏的步骤: 1. 在 Vue 组件中引入 Electron 的 remote 模块,用于获取主进程的 Menu 对象。 ```javascript import { remote } from 'electron' const Menu = remote.Menu ``` 2. 在 Vue 组件的生命周期钩子函数中创建菜单项,可以使用 Menu.buildFromTemplate 方法创建菜单项的数组。 ```javascript created() { const template = [ { label: '文件', submenu: [ { label: '新建', accelerator: 'CmdOrCtrl+N', click: this.handleNew }, { label: '打开', accelerator: 'CmdOrCtrl+O', click: this.handleOpen }, { type: 'separator' }, { label: '保存', accelerator: 'CmdOrCtrl+S', click: this.handleSave }, { label: '另存为', accelerator: 'Shift+CmdOrCtrl+S', click: this.handleSaveAs }, { type: 'separator' }, { label: '退出', accelerator: 'CmdOrCtrl+Q', click: this.handleQuit } ] }, { label: '编辑', submenu: [ { label: '撤销', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: '重做', accelerator: 'Shift+CmdOrCtrl+Z', role: 'redo' }, { type: 'separator' }, { label: '剪切', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: '复制', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: '粘贴', accelerator: 'CmdOrCtrl+V', role: 'paste' }, { label: '全选', accelerator: 'CmdOrCtrl+A', role: 'selectAll' } ] } ] const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu) } ``` 3. 在 Vue 组件中实现菜单项的点击事件。 ```javascript methods: { handleNew() { // 新建文件 }, handleOpen() { // 打开文件 }, handleSave() { // 保存文件 }, handleSaveAs() { // 另存为文件 }, handleQuit() { // 退出应用程序 } } ``` 这样就可以在 Electron Vite Vue 应用程序中实现自定义菜单栏了。需要注意的是,菜单项的点击事件可以调用主进程中的方法,例如使用 ipcRenderer 发送消息给主进程,让主进程执行相应的操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值