在Electron应用开发中,我们有时候会接到这样的需求:单独开发一个子窗口,这个子窗口要独立于主窗口,虽然有点类似于网页端里面的弹窗,但是从功能方面来说,这个独立的子窗口要远胜于弹窗
那接下来,我们就来看怎么去创建子窗口以及管理这个子窗口
在这里我们需要先引入一个外部依赖 vue-router
官网地址:入门 | Vue Router
一、创建子窗口父级vue
首先我们在渲染进程里面创建我们的子窗口页面 ChildWin.vue
ChildWin.vue 的内容如下
<template>
<div ref="childWinRef" class="child-win">
<div class="title">
<span class="title-text">{{ title }}</span>
<div class="electron-title-icon">
<div class="icon-item" @click="handle('hide')">
<svg-icon name="funHide" size="0.8em"></svg-icon>
</div>
<div class="icon-close" @click="handle('close')">
<svg-icon name="funClose" size="0.8em" hover-color="#fff"></svg-icon>
</div>
</div>
</div>
<template v-if="key === 'test'">
<test></test>
</template>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import Test from './components/Test.vue'
const winId = ref<number | null>(null)
const key = ref<string>('')
const title = ref<string>('子窗口标题')
const data = ref<any>(null)
onMounted(() => {
//监听主进程传过来的初始化数据
window.electron.ipcRenderer.on('initData', (event, params) => {
winId.value = params.id
title.value = params.title
data.value = params?.data
key.value = params.key
})
})
const handle = (params: string): void => {
const data = {
id: winId.value,
key: key.value,
type: params
}
window.electron.ipcRenderer.send('childWinSize', data)
}
const childWinRef = ref<HTMLDivElement | null>(null)
</script>
<style scoped lang="scss">
.child-win {
width: calc(100% - 2px);
height: calc(100vh - 2px);
position: relative;
font-size: 14px;
--btnHoverColor: #eee;
.title {
width: 100%;
height: 30px;
background-color: #eee;
-webkit-app-region: drag; /* 允许拖拽 */
display: flex;
justify-content: space-between;
align-items: center;
.title-text {
margin-left: 10px;
}
.electron-title-icon {
display: flex;
align-items: center;
gap: 5px;
-webkit-app-region: no-drag; /* 禁止拖拽 */
.icon-item {
cursor: pointer;
width: 40px;
height: 30px;
cursor: pointer;
&:hover {
background-color: var(--btnHoverColor);
}
}
.icon-close {
cursor: pointer;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
width: 30px;
&:hover {
background-color: rgb(243, 88, 88);
}
}
}
}
}
</style>
ChildWin.vue是所有子窗口的父级页面,可以很好去管理所有的子窗口
如果没有ChildWin.vue,那假如我们现在有5个窗口进行管理,我想要改变他们的标题栏样式,那我们可能就需要一个一个去修改,这样的维护过程对我们来说极其繁琐
二、创建test窗口页面
接下来我们来写一个子窗口的页面 Test.vue
文件结构如下
编辑
内容如下
<template>
<div>
<span>我是子窗口Test页面</span>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>
然后我们将这个页面挂载到 ChildWin.vue 里面
三、创建router路由文件
内容如下
import App from '@renderer/App.vue'
import { createRouter, createWebHistory } from 'vue-router'
import ChildWin from '@renderer/components/childWindows/ChildWin.vue'
const routes = [
{
path: '/',
name: 'Home',
component: App
},
{
path: '/childWindows',
name: 'childWindows',
component: ChildWin
}
]
export const router = createRouter({
history: createWebHistory(),
routes
})
将这个路由挂载到main.ts里面
main.ts修改内容如下
import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'
createApp(App).use(router).mount('#app')
三、创建控制器
我们回到主进程中,创建一个 windowsId.ts 的文件,文件结构如下,外层需要有一个store文件夹
文件内容如下
import { BrowserWindow, shell } from 'electron'
import { join } from 'path'
export const allChildWindowsId = {
test: null as number | null //test窗口id
}
//设置窗口id
export const setChildWindowsId = (id: number | null, key: string): boolean => {
allChildWindowsId[key] = id
return true
}
//查询窗口id
export const getChildWindowsId = (key: string): number => {
return allChildWindowsId[key]
}
//开启一个全新的子窗口
export const openChildWindow = (data: any) => {
const childWindow = new BrowserWindow({
width: data?.width || 800,
height: data?.height || 600,
title: data.title,
show: false,
autoHideMenuBar: true,
titleBarStyle: 'hidden',
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
childWindow.on('ready-to-show', () => {
childWindow?.show()
//发送一些初始数据
childWindow.webContents.send('initData', {
id: childWindow.id,
key: data.key,
title: data.title,
data: data?.data
})
})
childWindow.webContents.on('did-fail-load', (event, code, desc) => {
console.error('Failed to load:', desc)
})
childWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
//监听关闭
childWindow.on('closed', () => {
setChildWindowsId(null, data.key)
})
if (process.env['ELECTRON_RENDERER_URL']) {
childWindow.loadURL(`${process.env['ELECTRON_RENDERER_URL']}/childWindows`)
} else {
childWindow.loadFile(join(__dirname, '../renderer/index.html'), {
hash: '/childWindows'
})
}
setChildWindowsId(childWindow.id, data.key)
}
然后回到 monitorEvent.ts 文件里面,修改内容如下,在这里面新增加一个监听事件
import { BrowserWindow, ipcMain } from 'electron'
import { mainWindow } from '.'
import { getChildWindowsId, openChildWindow } from './store/windowsId'
export const monitorEvent = (): void => {
ipcMain.on('toMain', (e, data) => {
console.log(data)
})
ipcMain.on('winAction', (e, data) => {
const type = data.type
if (type === 'hide') {
//最小化窗口
mainWindow.minimize()
} else if (type === 'min') {
//取消最大化
mainWindow.unmaximize()
} else if (type === 'max') {
//最大化
mainWindow.maximize()
} else if (type === 'close') {
//关闭窗口
mainWindow.close()
}
})
//打开一个子窗口
ipcMain.on('openChild', (e, data) => {
//判断是否已经打开过该窗口
const winId = getChildWindowsId(data.key)
if (winId !== null) {
//窗口已经存在,则发送一些初始数据
const childWindow = BrowserWindow.fromId(winId)
if (childWindow === null) {
return
}
childWindow.webContents.send('initData', {
id: childWindow.id,
key: data.key,
title: data.title,
data: data?.data
})
//将窗口显示出来
childWindow.show()
} else {
openChildWindow(data)
}
})
}
四、开启一个子窗口
我们需要再次创建一个Home.vue的文件,App.vue作为我们的总入口文件
编辑
Home.vue文件内容如下
<template>
<div>
<button @click="openTest">开启test</button>
</div>
</template>
<script setup lang="ts">
const openTest = () => {
const obj = {} as any
obj.key = 'test'
obj.title = '测试窗口'
window.electron.ipcRenderer.send('openChild', obj)
}
</script>
<style scoped lang="scss"></style>
我们回到我们的App.vue文件里面
进行如下的修改
<template>
<div>
<button @click="openTest">开启test</button>
</div>
</template>
<script setup lang="ts">
const openTest = () => {
const obj = {} as any
obj.key = 'test'
obj.title = '测试窗口'
window.electron.ipcRenderer.send('openChild', obj)
}
</script>
<style scoped lang="scss"></style>
然后我们再次返回到我们的router里面的index.ts文件里面
修改内容如下
import { createRouter, createWebHistory } from 'vue-router'
import ChildWin from '@renderer/components/childWindows/ChildWin.vue'
import Home from '@renderer/page/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/childWindows',
name: 'childWindows',
component: ChildWin
}
]
export const router = createRouter({
history: createWebHistory(),
routes
})
五、验证效果
运行我们的项目
点击开启test按钮
可以看到我们的test子窗口是独立于主窗口的
并且后面想要对这个主窗口进行修改,或者关闭显示隐藏,都可以在主进程里面做到