视频效果展示
准备前提
开始前,首先你要先弄明白什么是主进程,什么是渲染进程。
主进程:
- 启动后一直存在的,相当于一个树的主干
- 并不会展示出来,是看不到的
- 所有跟系统资源交互的操作都在这里进行
- 操控渲染进程,新建或销毁一个渲染进程
渲染进程:
- 渲染进程是一个个的浏览器窗口,就是用于展示
- 渲染进程就是能看到的界面
- 如果需要跟系统交互,就需要用
ipcRenderer
跟主进程进行数据交互。
注意:electron框架运行起来以后,主动创建的一个窗口,我们可以称其为:主窗口
思路流程
- 在主窗口创建的过程中,我们可以为其绑定通用的窗口事件,在其绑定的窗口事件中,为其加上窗口关闭监听的操作,然后在窗口关闭监听的过程中,先判断一下当前关闭的窗口是否为主窗口,如果不是则需要在group 中删除其对应的值(会默认直接关闭),如果是主窗口 则先 由主进程向渲染进程发送消息(渲染进程收到后为 触发了关闭事件),然后 阻止窗口默认关闭事件 e.preventDefault();
- 在默认的窗口参数中去除 默认的顶部导航 frame: false,然后自己封装一个顶部的导航,在自己封装的导航中 监听接收 主进程发送过来的消息,监听到以后实现自己相对应的逻辑
下面为我的部分代码展示
//主进程
/**
* 创建窗口
* @param windowConfig 窗口创建参数
*/
createWindows(windowConfig: IWindowConfig & IWindowsZi): BrowserWindow {
// 先通过key判断是否已窗口,有则聚焦
let windowKey = windowConfig.key;
let isMainWin = windowConfig.isMainWin; // 是否主窗口(当为true时会替代当前主窗口)
if (windowKey && windowKey.length > 0) {
/// 先从窗口组中取出记录
const wg: WindowGroup = this.group.get(windowKey);
if (wg) {
/// 根据记录中的窗口id获取窗口,假如存在该窗口,则聚焦该窗口
const oldWin = BrowserWindow.fromId(wg.windowId);
if (oldWin) {
oldWin.focus();
return oldWin;
}
}
}
// 创建窗口对象
const win: BrowserWindow = new BrowserWindow(
Object.assign({}, defaultWindowConfig, windowConfig)
);
// 将窗口的关键信息与key关联,存入窗口组中
windowKey = windowKey || win.id.toString();
this.group.set(windowKey, {
windowId: win.id,
webContentsId: win.webContents.id,
});
// 是否主窗口
if (isMainWin) {
if (this.main) {
// this.main.close();
this.main.destroy();
}
this.main = win;
}
// 根据当前环境加载页面,并传递参数
const param = windowConfig.param
? "?urlParamData=" + windowConfig.param
: "";
if (process.env.VITE_DEV_SERVER_URL) {
// 如果是开发环境,则直接访问本地跑起的服务,拼接对应的路由
win.loadURL(`${url}#${windowConfig.route}${param}`);
} else {
// 如果是线上环境,则加载html文件的路径,然后拼接路由
win.loadFile(indexHtmlPath, { hash: windowConfig.route + param });
}
// win.webContents.openDevTools(); // 打开 开发者工具
// 绑定通用窗口事件
this.bindWindowEvent(win, windowConfig);
// console.log(this.group);
return win;
}
/**
* 绑定窗口事件
* @param win 窗口对象
* @param windowConfig 窗口创建参数
*/
bindWindowEvent(win: BrowserWindow | any, windowConfig: IWindowConfig) {
win.once("ready-to-show", () => {
win.show();
});
// 禁止 ctrl+shift+i 快捷键 打开 开发者工具
win.webContents.on("before-input-event", (event, input) => {
// console.log(input, 123);
if (input.control && input.shift && input.key.toLowerCase() === "i") {
// console.log("Pressed Control+I");
event.preventDefault(); // 阻止默认行为
}
});
// 监听窗口最大化
win.on('maximize', () => {
win.webContents.send("maximizeRender", { data: true });
});
// 监听窗口 取消最大化
win.on('unmaximize', () => {
win.webContents.send("maximizeRender", { data: false });
});
// 窗口关闭监听,此事件触发时,窗口即将关闭,可以拒绝关闭,此时窗口对象还未销毁
win.on("close", (e: any) => {
// let windowId = this.group.get(windowConfig.key).windowId;
if (this.main == win) {
win.webContents.send("mainSendToRender", { data: true });
e.preventDefault();
// win.destroy(); // 强制关闭窗口
} else {
// 设置窗口透明
if (win != null) {
win.setOpacity(0);
const key = windowConfig.key || win.id.toString();
this.group.delete(key);
}
}
}); // 尝试关闭窗口
// 此事件触发时,窗口已关闭,窗口对象已销毁
win.on("closed", () => {
// 在窗口对象被关闭时,取消订阅所有与该窗口相关的事件
if (win != null) {
win.removeAllListeners();
// 引用置空
win = null;
}
});
}
// 我自己封装的 顶部导航
<template>
<!-- dblclick 双击事件 -->
<div class="w-full">
<drag-tool class="w-full header-tool">
<div
class="header"
@dblclick="dblZuiMax"
:style="{ borderBottom: !titleXian ? '1.5px solid #e3e3e3' : '' }"
>
<div class="m-l-15px noDrag">
<text v-if="!titleXian">{{ userInfo.name }}</text>
</div>
<div class="flex flex-col">
<div class="header-DIv m-r-15px">
<a-button
v-if="!minBidden"
@click="zuiMin"
class="buttonAn"
type="text"
title="最小化"
>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
width="20"
height="20"
>
<path d="M5 11V13H19V11H5Z"></path>
</svg>
</template>
</a-button>
<a-button
v-if="!maxBidden"
class="margin-ZY buttonAn"
@click="zuiMax"
type="text"
:title="isMax ? '还原' : '最大化'"
>
<template #icon>
<svg
v-show="!isMax"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="15"
height="15"
fill="currentColor"
>
<path
d="M4 3H20C20.5523 3 21 3.44772 21 4V20C21 20.5523 20.5523 21 20 21H4C3.44772 21 3 20.5523 3 20V4C3 3.44772 3.44772 3 4 3ZM5 5V19H19V5H5Z"
></path>
</svg>
<svg
v-show="isMax"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
width="15"
height="15"
>
<path
d="M6.99979 7V3C6.99979 2.44772 7.4475 2 7.99979 2H20.9998C21.5521 2 21.9998 2.44772 21.9998 3V16C21.9998 16.5523 21.5521 17 20.9998 17H17V20.9925C17 21.5489 16.551 22 15.9925 22H3.00728C2.45086 22 2 21.5511 2 20.9925L2.00276 8.00748C2.00288 7.45107 2.4518 7 3.01025 7H6.99979ZM8.99979 7H15.9927C16.549 7 17 7.44892 17 8.00748V15H19.9998V4H8.99979V7ZM4.00255 9L4.00021 20H15V9H4.00255Z"
></path>
</svg>
</template>
</a-button>
<a-button
v-if="!closeBidden"
@click="Guan"
class="buttonAn"
type="text"
danger
title="关闭"
>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="#ff7572"
width="20"
height="20"
>
<path
d="M11.9997 10.5865L16.9495 5.63672L18.3637 7.05093L13.4139 12.0007L18.3637 16.9504L16.9495 18.3646L11.9997 13.4149L7.04996 18.3646L5.63574 16.9504L10.5855 12.0007L5.63574 7.05093L7.04996 5.63672L11.9997 10.5865Z"
></path>
</svg>
</template>
</a-button>
</div>
<div
v-if="!titleXian"
class="flex flex-row-reverse items-center m-r-15px m-t-2px noDrag"
>
<a-dropdown>
<div class="m-r-5px cursor-pointer">
<EllipsisOutlined style="font-size: 18px" />
</div>
<template #overlay>
<a-menu>
<a-menu-item>
<a href="javascript:;">1st menu item</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;">2nd menu item</a>
</a-menu-item>
<a-menu-item>
<a href="javascript:;">3rd menu item</a>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</div>
</div>
</drag-tool>
<a-modal
v-model:open="visible"
:maskClosable="true"
centered
:destroyOnClose="true"
title="关闭提示"
ok-text="确认"
cancel-text="取消"
width="400px"
:footer="null"
>
<div style="display: flex; flex-direction: column">
<div class="marginTop15">
<a-radio-group
v-model:value="closeValue"
name="radioGroup"
>
<a-radio :value="1">最小化到托盘</a-radio>
<a-radio :value="2">直接退出</a-radio>
</a-radio-group>
</div>
<div class="tanChu">
<a-checkbox v-model:checked="closeChecked">不再提醒</a-checkbox>
<div>
<a-button
type="primary"
@click="hideModal"
>确认</a-button
>
</div>
</div>
</div>
</a-modal>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ipcRenderer } from "electron";
import electronUtils from "@/utils/electronUtils";
import cacheUtils from "@/utils/cacheUtils";
import { EllipsisOutlined } from "@ant-design/icons-vue";
const closeChecked = ref(false);
const closeValue = ref(1);
const visible = ref(false);
const isMax = ref(false);
const props = defineProps({
minBidden: {
type: Boolean,
default: () => {
return false;
},
},
maxBidden: {
type: Boolean,
default: () => {
return false;
},
},
closeBidden: {
type: Boolean,
default: () => {
return false;
},
},
titleXian: {
type: Boolean,
default: () => {
return false;
},
},
userInfo: {
type: Object,
default: () => {
return {};
},
},
});
onMounted(() => {
// 监听当前窗口是否触发 关闭按钮
ipcRenderer.on("mainSendToRender", function (event, arg) {
const a = cacheUtils.get("closeChecked");
const b = cacheUtils.get("closeValue");
if (a) {
// 如果已选择 不再提醒
switch (b) {
case 1:
electronUtils.miniMizeTray();
break;
case 2:
electronUtils.allColTray();
break;
}
} else {
if (arg.data) {
electronUtils.allColEcho();
visible.value = true;
}
}
});
// 监听当前窗口是否最大化
ipcRenderer.on("maximizeRender", function (event, arg) {
isMax.value = arg.data;
});
});
// 点击最小化
const zuiMin = () => {
electronUtils.zuiMin();
};
// 点击最大化
const zuiMax = () => {
electronUtils.zuiMax();
};
// 双击最大化
const dblZuiMax = () => {
// switch (!props.maxBidden) {
// case true:
// zuiMax();
// break;
// case false:
// break;
// }
};
// 点击关闭窗口
const Guan = () => {
electronUtils.closeGuan();
};
// 点击确认
async function hideModal() {
if (closeChecked.value) {
cacheUtils.set("closeChecked", true); // 判断是否 不再提醒
cacheUtils.set("closeValue", closeValue.value); // 选项
}
switch (closeValue.value) {
case 1:
visible.value = false;
setTimeout(() => {
electronUtils.miniMizeTray();
}, 150);
break;
case 2:
visible.value = false;
electronUtils.allColTray();
break;
}
}
</script>
<style lang="scss" scoped>
.header-tool {
-webkit-app-region: drag;
.header {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
height: 63px;
// -webkit-user-select: none;
// -webkit-app-region: drag;
// flex-direction: row-reverse;
// background-color: #fff;
.header-DIv {
// margin: 8px 15px;
display: flex;
flex-direction: row;
-webkit-app-region: no-drag;
}
.noDrag {
-webkit-app-region: no-drag;
}
}
.buttonAn {
display: flex;
align-items: center;
justify-content: center;
}
}
</style>
最终的实现效果
这里的话,我的代码里面 是已经实现了 这个关闭弹窗里面的 全部的功能。
我的窗口关闭的逻辑是这样的:我们可以先将窗口分为 主窗口和其他窗口,当我们打开了多个窗口的时候,点击其他窗口会直接关闭,只有点击主窗口的关闭按钮时,才会出来这个弹窗。当在主窗口中 直接退出的话,我们就需要退出所有的窗口,而点击其他窗口关闭时则不用,所以需要使用 app.quit(); 来退出。但是如果只写一个这个用来退出的话,你就会发现 这个弹窗会一直弹。这是因为 app.quit() 这个方法触发以后,会自动的去触发窗口事件中的 关闭窗口事件,所以,就需要在执行 app.quit 前,让this.main 不等于 主窗口的win,这样就可以很好的解决这个问题。
这里的this.main 是 我在创建 主窗口 或者 更改主窗口的过程中,存进去的当前的主窗口的 win。win 是指 每个窗口被创建时,自己的窗口对象。
到这里就结束啦~ 感谢大家的观看 如需有什么不懂的 欢迎评论区留言,写的哪不好的,也可以留言哦~