继续上一篇 简单搭建Vue3+electron 进行项目配置,本篇文章来说一说搭建好环境后,开始编码了。
安装插件
想了一下,写一个桌面壁纸功能吧,桌面壁纸功能,查了一些资料,大部分都是用win32的接口进行操纵,也就是说,通过node跟win32进行通信,然后改变桌面的壁纸(不过仅限于图片壁纸),但是在市面上不是出了好几款壁纸了吗?比如某气壁纸,某火壁纸等,还有很多类似的软件,当然这些软件我也没安装细看,我估测它们不是用的electron来进行的渲染的(当然我没下载也不清楚,有这方面想法的同学可以去下载看看),总之,这几个软件都是支持图片壁纸,视频壁纸,动态壁纸,网页壁纸的,那我们至少也应该支持这几种吧。因为我也是electron的初学者,并不是特别熟悉,所以看了一下 ElectronApi 的官网查看了一下,我表示没有找到需要的APi接口,只看到了一个悬浮在桌面的api=>win.setKiosk(flag) 设置锁定模式,就算搞个透明窗锁定后发现窗口还是在桌面图标的上方,无法正常使用图标,所以此模式也被我抛弃掉,它不是友好的模式(这种锁定模式类似于某些音乐软件,桌面悬浮歌词的功能,我们应该是需要的是酷狗音乐的桌面背景歌词模式)。
无奈之下只有去github上去找找了,找了一堆,在mac桌面的此类项目倒是很多,然而在win上的很少。功夫不负有心人,还真被我找到了。
- electron-edge.js只能满足图片壁纸设置,此仓库就是前面说的用win32接口执行更换桌面。
- 基于MAc桌面壁纸更换貌似是不需要这些仓库,只需要直接调用mac的接口即可,没有测试,不知道说得对不对。
- 基于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
大体上完成,我们就可以开始我们的第一步了。
编码
安装完后,大概目录如下图
创建一个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插件直接读取引入即可。
评论 (0)