electron+vue3 进行点击关闭按钮时,弹出自定义弹出框

视频效果展示

准备前提

开始前,首先你要先弄明白什么是主进程,什么是渲染进程。

主进程

  • 启动后一直存在的,相当于一个树的主干
  • 并不会展示出来,是看不到的
  • 所有跟系统资源交互的操作都在这里进行
  • 操控渲染进程,新建或销毁一个渲染进程

渲染进程

  • 渲染进程是一个个的浏览器窗口,就是用于展示
  • 渲染进程就是能看到的界面
  • 如果需要跟系统交互,就需要用 ipcRenderer 跟主进程进行数据交互。

注意:electron框架运行起来以后,主动创建的一个窗口,我们可以称其为:主窗口

思路流程

  1. 在主窗口创建的过程中,我们可以为其绑定通用的窗口事件,在其绑定的窗口事件中,为其加上窗口关闭监听的操作,然后在窗口关闭监听的过程中,先判断一下当前关闭的窗口是否为主窗口,如果不是则需要在group 中删除其对应的值(会默认直接关闭),如果是主窗口 则先 由主进程向渲染进程发送消息(渲染进程收到后为 触发了关闭事件),然后 阻止窗口默认关闭事件 e.preventDefault();
  2. 在默认的窗口参数中去除 默认的顶部导航 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 是指 每个窗口被创建时,自己的窗口对象。

到这里就结束啦~ 感谢大家的观看  如需有什么不懂的 欢迎评论区留言,写的哪不好的,也可以留言哦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小牛学前端

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值