electron+vue3 桌面端壁纸工具案例(一) - 风之涯技术博客

标签搜索

electron+vue3 桌面端壁纸工具案例(一)

小峰
2022年09月28日 / 0 评论 / 1501 阅读 / 正在检测是否收录...
广告

 继续上一篇 简单搭建Vue3+electron 进行项目配置,本篇文章来说一说搭建好环境后,开始编码了。

安装插件

想了一下,写一个桌面壁纸功能吧,桌面壁纸功能,查了一些资料,大部分都是用win32的接口进行操纵,也就是说,通过node跟win32进行通信,然后改变桌面的壁纸(不过仅限于图片壁纸),但是在市面上不是出了好几款壁纸了吗?比如某气壁纸,某火壁纸等,还有很多类似的软件,当然这些软件我也没安装细看,我估测它们不是用的electron来进行的渲染的(当然我没下载也不清楚,有这方面想法的同学可以去下载看看),总之,这几个软件都是支持图片壁纸,视频壁纸,动态壁纸,网页壁纸的,那我们至少也应该支持这几种吧。因为我也是electron的初学者,并不是特别熟悉,所以看了一下 ElectronApi 的官网查看了一下,我表示没有找到需要的APi接口,只看到了一个悬浮在桌面的api=>win.setKiosk(flag) 设置锁定模式,就算搞个透明窗锁定后发现窗口还是在桌面图标的上方,无法正常使用图标,所以此模式也被我抛弃掉,它不是友好的模式(这种锁定模式类似于某些音乐软件,桌面悬浮歌词的功能,我们应该是需要的是酷狗音乐的桌面背景歌词模式)。
 无奈之下只有去github上去找找了,找了一堆,在mac桌面的此类项目倒是很多,然而在win上的很少。功夫不负有心人,还真被我找到了。

  1. electron-edge.js只能满足图片壁纸设置,此仓库就是前面说的用win32接口执行更换桌面。
  2. 基于MAc桌面壁纸更换貌似是不需要这些仓库,只需要直接调用mac的接口即可,没有测试,不知道说得对不对。
  3. 基于Win桌面壁纸更换需要用到electron-as-wallpaper这么个仓库,此作品是c++编写的node和win32连通插件。
安装vue3的ui库,我这里选择了naive-ui
npm i -D naive-ui

有兴趣的可以自行去Naive-Ui官网查看使用方法

安装electron-as-wallpaper
npm i electron-as-wallpaper

此插件不多做描述了,主要用于让窗口置于桌面图标之下

安装 axios 请求
npm i axios

作用不多说,了解的自然了解,当然如果不会用,可以用vue自带的即可

安装图标库,在naive-ui中有介绍 @vicons/ionicons5
npm i @vicons/ionicons5

大体上完成,我们就可以开始我们的第一步了。

编码

安装完后,大概目录如下图
c4ea31690f137009946e2344fa719c7b.png

创建一个common目录

在里面创建一个window.js文件,此文件作用是创建窗口所用
import { app, BrowserWindow, ipcMain, Menu, Tray,remote } from "electron";//先引入electron的部分相关api
import path from "path";//引入path

// 新建窗口时可以传入的一些options配置项
export const windowsCfg = {
    id: null,
    icon:path.join(__dirname,'../src/assets/logo.png'),
    title: "",
    width: null,
    height: null,
    minWidth: null,
    minHeight: null,
    route: "",
    resizable: true,
    maximize: false,
    backgroundColor: "#eee",
    data: null,
    isMultiWindow: false,
    isMainWin: false,
    parentId: null,
    modal: false, //模态窗口 -- 模态窗口是禁用父窗口的子窗口,创建模态窗口必须设置 parent 和 modal 选项 【父窗口不能操作】
};

/**
 * 窗口配置
 */
export class Window {
    constructor() {
        this.main = null; //当前页
        this.group = {}; //窗口组
        this.tray = null; //托盘
    }
    // 窗口配置
    winOpts(wh = []) {
        return {
            width: wh[0],
            height: wh[1],
            backgroundColor: "#f7f8fc",
            autoHideMenuBar: true,
            resizable: true,
            minimizable: true,
            maximizable: true,
            frame: false,
            show: true,
            minWidth: 0,
            minHeight: 0,
            modal: true,
            webPreferences: {
                contextIsolation: false,
                nodeIntegration: true,
                webSecurity: false,
                enableRemoteModule:process.env.ELECTRON_NODE_INTEGRATION || true
            },
        };
    }
    // 获取窗口
    getWindow(id) {
        return BrowserWindow.fromId(id);
    }
    // 创建窗口
    createWindows(options) {
        console.log("------------开始创建窗口...");
        let args = Object.assign({}, windowsCfg, options);
        // 判断窗口是否存在
        for (let i in this.group) {
            if (this.getWindow(Number(i)) &&
                this.group[i].route === args.route &&
                !this.group[i].isMultiWindow) {
                console.log("窗口已经存在了");
                this.getWindow(Number(i)).focus();
                if(options.route==='deskwin'){
                    global.shareObject.deskWin = this.getWindow(Number(i));
                }
                return;
            }
        }
        // 创建 electron 窗口的配置参数
        let opt = this.winOpts([args.width || 390, args.height || 590]);
        // 判断是否有父窗口
        if (args.parentId) {
            console.log("parentId:" + args.parentId);
            opt.parent = this.getWindow(args.parentId); // 获取主窗口
        }
        else if (this.main) {
            console.log('当前为主窗口');
        } // 还可以继续做其它判断
        // 根据传入配置项,修改窗口的相关参数
        opt.modal = args.modal;
        opt.resizable = args.resizable; // 窗口是否可缩放
        if (args.backgroundColor)
            opt.backgroundColor = args.backgroundColor; // 窗口背景色
        if (args.minWidth)
            opt.minWidth = args.minWidth;
        if (args.minHeight)
            opt.minHeight = args.minHeight;
        let win = new BrowserWindow(opt);
        console.log("窗口 id:" + win.id);
        this.group[win.id] = {
            route: args.route,
            isMultiWindow: args.isMultiWindow,
        };
        // 是否最大化
        if (args.maximize && args.resizable) {
            win.maximize();
        }
        // 是否主窗口
        if (args.isMainWin) {
            if (this.main) {
                console.log("主窗口存在");
                delete this.group[this.main.id];
                this.main.close();
            }
            this.main = win;
            win.webContents.openDevTools();
        }
        args.id = win.id;
        win.on("close", () => win.setOpacity(0));
        //关闭右键菜单
        win.hookWindowMessage(278, () => {
            win.setEnabled(false)
            setTimeout(() => {
                win.setEnabled(true)
            }, 100)
            return true
        });

        // 打开网址(加载页面)
        let winURL;
        if (app.isPackaged) {
            winURL = args.route
                ? `app://./index.html${args.route}`
                : `app://./index.html`;
        }
        else {
            winURL = args.route
                ? process.env.WEBPACK_DEV_SERVER_URL+`${args.route}?winId=${args.id}`
                : process.env.WEBPACK_DEV_SERVER_URL+`?winId=${args.id}`//`http://${process.env["VITE_DEV_SERVER_HOST"]}:${process.env["VITE_DEV_SERVER_PORT"]}?winId=${args.id}`;
        }
        console.log("新窗口地址:", winURL);
        win.loadURL(winURL);
        // 监听窗口大小变化
        win.on('maximize', () => {
            win.webContents.send('mainWin-max', true)
        });
        win.on('unmaximize', () => {
            win.webContents.send('mainWin-max', false)
        });
        // 监听窗口显隐
        win.once("ready-to-show", () => {
            win.show();
        });
        // win.webContents.openDevTools();

        if(options.route==='deskwin'){
            global.shareObject.deskWin = win;
        }
    }
    // 创建托盘
    createTray() {
        // 创建托盘
        const contextMenu = Menu.buildFromTemplate([
            {
                label: '更换皮肤',
                icon:'',
                click:()=>{
                  // 备用
                }
            },
            {
                label: '设置',
                icon:'',
                click:()=>{
                  // 备用
                }
            },
            {
                label: '退出',
                click: () => {
                    // 此处未编写,后续编写,关闭整个主程序及其所有窗口
                }
            }
        ]);
        this.tray = new Tray(path.join(__dirname,'../src/assets/logo.png')); // 托盘图标
        // 点击托盘显示窗口
        this.tray.on("click", () => {
            for (let i in this.group) {
                if (this.group[i])
                    this.getWindow(Number(i)).show();
            }
        });
        // 处理右键
        this.tray.on("right-click", () => {
            var _a;
            (_a = this.tray) === null || _a === void 0 ? void 0 : _a.popUpContextMenu(contextMenu);
        });
        this.tray.setToolTip("风之涯月兔壁纸 - 强大的自定义壁纸软件");
    }
    // 开启监听
    listen() {
        // 固定
        ipcMain.on('pinUp', (event, winId) => {
            event.preventDefault();
            if (winId && this.main.id == winId) {
                let win = this.getWindow(Number(this.main.id));
                if (win.isAlwaysOnTop()) {
                    win.setAlwaysOnTop(false); // 取消置顶
                }
                else {
                    win.setAlwaysOnTop(true); // 置顶
                }
            }
        });
        // 隐藏
        ipcMain.on("window-hide", (event, winId) => {
            if (winId) {
                this.getWindow(Number(winId)).hide();
            }
            else {
                for (let i in this.group) {
                    if (this.group[i])
                        this.getWindow(Number(i)).hide();
                }
            }
        });
        // 显示
        ipcMain.on("window-show", (event, winId) => {
            if (winId) {
                this.getWindow(Number(winId)).show();
            }
            else {
                for (let i in this.group) {
                    if (this.group[i])
                        this.getWindow(Number(i)).show();
                }
            }
        });
        // 最小化
        ipcMain.on("mini", (event, winId) => {
            console.log("最小化窗口 id", winId);
            if (winId) {
                this.getWindow(Number(winId)).minimize();
            }
            else {
                for (let i in this.group) {
                    if (this.group[i]) {
                        this.getWindow(Number(i)).minimize();
                    }
                }
            }
        });
        // 最大化
        ipcMain.on("window-max", (event, winId) => {
            if (winId) {
                this.getWindow(Number(winId)).maximize();
            }
            else {
                for (let i in this.group)
                    if (this.group[i])
                        this.getWindow(Number(i)).maximize();
            }
        });
        // 创建窗口
        ipcMain.on("window-new", (event, args) => this.createWindows(args));
    }
    // 窗口给webContents发送信息
    sendToWeb(win,obj){
        if(win){
            win.webContents.send(obj.event,obj.data);
        }
    }
}
修改或重写background.js
'use strict' //即时刷新

import { app, protocol, BrowserWindow,Tray,Menu,screen,ipcMain } from 'electron' //引入electron 的相关API
import path from 'path' 
import { Window } from "../src/common/window" //引用刚刚创建的window.js

import { createProtocol } from 'vue-cli-plugin-electron-builder/lib' //引入vuecli和electron 创建程序
// import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer' //不需要注释掉的
const isDevelopment = process.env.NODE_ENV !== 'production' //判断是否是生产环境
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } }
])
// 设置部分全局变量
global.shareObject = {
  downpath:app.getPath('downloads'),//下载路径
  apppath:app.getAppPath(),//app的路径
  temppath:path.join(__dirname,'../temp')
}

async function createWindow() {
  // 创建窗口
  let window = new Window();
  window.listen();
  const win = window.createWindows({
    icon:path.join(__dirname,'../src/assets/logo.png'),
    isMainWin: true,
    width: 1276,
    height: 870,
    minWidth:1276,
    minHeight:870,
    frame:false,
    backgroundColor:'#0000000',
    webPreferences: {
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      enableRemoteModule:process.env.ELECTRON_NODE_INTEGRATION,
      contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION
    }

  });
  window.createTray();
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) 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 () => {
  createWindow();
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', (data) => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

到此为止,我们的主窗口创建完毕

运行 npm run electron:serve 仍然能够看到vue的主页面被加载

修改main.js文件
// 引入vue
import { createApp } from 'vue'
// 引入electron
import electron from 'electron'
// 引入naiveUI
import naive from 'naive-ui'
// 通用字体
// import 'vfonts/Lato.css'
// 等宽字体,引入字体
import 'vfonts/FiraCode.css'
// 图片懒加载
import lazyload from "@/common/lazyload";
// 引入APP主文件1和2
import App from './App.vue'
// 引入路由
import router from './router'
// 引入store 执行目录
import store from './store'
// 判断是否为mobile
// import isMobile from "@/common/isMobile";


const app = createApp(App)
lazyload(app);
app.use(naive)
app.use(electron)
app.use(store)
app.use(router)
app.mount('#app')
//判断是否是移动端还是PC端
//app.config.globalProperties.$isMobile = isMobile
//设置全局属性Electron
app.config.globalProperties.$electron = electron
修改App.vue
<template>
  <n-config-provider :theme-overrides="theme" :locale="zhCN">
    <router-view />
  </n-config-provider>
</template>
<script>
  import { darkTheme,zhCN } from 'naive-ui'
  import {defineComponent, ref} from "vue";
  import { useIpcRenderer } from "@vueuse/electron";
  import {Window} from "@/common/window"
  import { attach, detach, refresh } from "electron-as-wallpaper"
  const window = new Window();
  const ipcRenderer = useIpcRenderer();

  export default defineComponent({
    setup() {
      return {
        darkTheme,
        theme: ref(null),
        zhCN,
      }
    },
    created() {
      // console.log(this.$router)
    },
    mounted() {
      setTimeout(()=>{
        this.createDeskWin();
      },500)
    },
    methods:{
      //创建一个桌面壁纸层窗口
      createDeskWin(){
        const screenSize = this.$electron.remote.screen.getPrimaryDisplay().workAreaSize;
        ipcRenderer.send('window-new',{
          route:"deskwin",//路由名称,在views里面创建此deskwin再加入路由index.js文件即可
          width:screenSize.width,
          height:screenSize.height
        });
        if(this.$electron.remote.getGlobal('shareObject') && this.$electron.remote.getGlobal('shareObject').deskWin){
          attach(this.$electron.remote.getGlobal('shareObject').deskWin); //electron-as-wallpaper提供的attach方法直接将窗口置于桌面底部
        }
      }
    }
  });
</script>
在views里面创建一个DeskwinView.vue文件

在里面写入

<template>
    <div class="deskBox">
        <img :src="obj.url" class="imgBox" v-if="obj.type==`img`?true:false" />
        <video :src="obj.url" class="imgBox" autoplay loop muted id="deskPlayer" v-if="obj.type==`video`?true:false" />
    </div>
</template>
<script>
    import { useIpcRenderer } from "@vueuse/electron"; //自行安装此插件
    const ipcRenderer = useIpcRenderer();
    export default {
        name: "DeskView",
        data(){
            return {
                obj:{
                    type:'video',//video,html
                    url:'https://n0va.mihoyo.com/medias/bgvideo.13edb8ad.mp4'
                },
            }
        },
        mounted() {
            //监听改变背景
            ipcRenderer.on('changeBg',(event,data)=>{
                this.obj = data?data:this.obj;
            })
        }
    }
</script>
<!--有点样式-->
<style scoped>
    .deskBox{
        width: 100%;
        height: 100%;
        overflow: hidden;
    }
    .imgBox{
        width: 100%;
        height: 100%;
        object-fit: cover;
        overflow: hidden;
        border: none;
        background: none;
    }
</style>

此时再运行npm run electron:serve 桌面的壁纸就发生变化了。(最开始的样子就是如此了)

后面的大家可以自行扩展,下期聊聊采集网络壁纸,因为我们自己可能没有壁纸库,当然如果自己有,可通过fs插件直接读取引入即可。

7

评论 (0)

QQ
昵称
邮箱
取消