效果预览
技术要点
透明背景
src/main/index.ts 的 new BrowserWindow 中添加
ts
transparent: true, // 设置窗口背景透明
frame: false, // 隐藏窗口边框
仅图标和标题部分可拖拽
仅图标和标题部分添加样式 drag
css
.drag {
-webkit-app-region: drag;
}
图标与标题栏的融合
- 标题栏的背景色采用图标的背景色
css
bg-[#0baaf5]
- 标题栏添加顶部的外边距,值为图标的半径
css
mt-30px
图标的样式如下:
html
<img :src="icon" alt="logo" class="fixed h-60px top-[0px] drag" />
窗口置顶
html
<Icon
v-if="ifTop"
icon="stash:pin-thumbtack-solid"
:class="iconClassString"
@click="cancle_top"
/>
<Icon v-else icon="stash:pin-thumbtack" :class="iconClassString" @click="top" />
ts
const ifTop = ref(false)
// 窗口置顶
function top() {
window.electron.ipcRenderer.send('top')
ifTop.value = true
}
function cancle_top() {
window.electron.ipcRenderer.send('cancle_top')
ifTop.value = false
}
src/main/index.ts 中
ts
ipcMain.on('top', () => {
mainWindow.setAlwaysOnTop(true)
})
ipcMain.on('cancle_top', () => {
mainWindow.setAlwaysOnTop(false)
})
窗口最小化
html
<Icon icon="qlementine-icons:windows-minimize-16" :class="iconClassString" @click="min" />
ts
function min() {
window.electron.ipcRenderer.send('min')
}
src/main/index.ts 中
ts
ipcMain.on('min', () => {
mainWindow.minimize()
})
窗口最大化 / 取消最大化
html
<Icon
v-if="ifMax"
icon="qlementine-icons:windows-unmaximize-16"
:class="iconClassString"
@click="cancel_max"
/>
<Icon
v-else
icon="qlementine-icons:windows-maximize-16"
:class="iconClassString"
@click="max"
/>
ts
const ifMax = ref(false)
function max() {
window.electron.ipcRenderer.send('max')
}
function cancel_max() {
window.electron.ipcRenderer.send('cancel_max')
}
window.electron.ipcRenderer.on('unmaximize', () => {
ifMax.value = false
})
window.electron.ipcRenderer.on('maximize', () => {
ifMax.value = true
})
src/main/index.ts 中
ts
ipcMain.on('max', () => {
mainWindow.maximize()
})
ipcMain.on('cancel_max', () => {
mainWindow.unmaximize()
})
因将窗口拖拽到屏幕边缘时,会自动触发最大化,调整窗口大小会退出最大化,则需监听窗口最大化和退出最大化事件,由主进程通知渲染进程状态,以便切换图标。[ 不适应于本范例,因本范例隐藏了 frame ]
ts
// 窗口变为最大化状态
mainWindow.on('maximize', () => {
mainWindow.webContents.send('maximize')
})
// 窗口从最大化状态退出
mainWindow.on('unmaximize', () => {
mainWindow.webContents.send('unmaximize')
})
隐藏窗口到托盘
html
<Icon icon="si:close-fill" :class="iconClassString" @click="hide" />
ts
function hide() {
window.electron.ipcRenderer.send('hide')
}
src/main/index.ts 中
ts
ipcMain.on('hide', () => {
// 使窗口不显示在任务栏中
mainWindow.setSkipTaskbar(true)
mainWindow.hide()
})
代码实现
src/renderer/src/components/TitleBar.vue
html
<script setup lang="ts">
import icon from '../../../../resources/icon.png'
const props = defineProps({
title: {
type: String
}
})
function hide() {
window.electron.ipcRenderer.send('hide')
}
const ifTop = ref(false)
// 窗口置顶
function top() {
window.electron.ipcRenderer.send('top')
ifTop.value = true
}
function cancle_top() {
window.electron.ipcRenderer.send('cancle_top')
ifTop.value = false
}
function min() {
window.electron.ipcRenderer.send('min')
}
const ifMax = ref(false)
function max() {
window.electron.ipcRenderer.send('max')
}
function cancel_max() {
window.electron.ipcRenderer.send('cancel_max')
}
window.electron.ipcRenderer.on('unmaximize', () => {
ifMax.value = false
})
window.electron.ipcRenderer.on('maximize', () => {
ifMax.value = true
})
const iconClassString = 'cursor-pointer hover:bg-blue-500 block h-full px-2'
</script>
<template>
<img :src="icon" alt="logo" class="fixed h-60px top-[0px] drag" />
<div class="flex items-center bg-[#0baaf5] text-white h-30px mt-30px">
<div class="drag flex-1 pl-70px font-bold text-white">{{ props.title }}</div>
<div class="flex h-full">
<Icon
v-if="ifTop"
icon="stash:pin-thumbtack-solid"
:class="iconClassString"
@click="cancle_top"
/>
<Icon v-else icon="stash:pin-thumbtack" :class="iconClassString" @click="top" />
<Icon icon="qlementine-icons:windows-minimize-16" :class="iconClassString" @click="min" />
<Icon
v-if="ifMax"
icon="qlementine-icons:windows-unmaximize-16"
:class="iconClassString"
@click="cancel_max"
/>
<Icon
v-else
icon="qlementine-icons:windows-maximize-16"
:class="iconClassString"
@click="max"
/>
<Icon icon="si:close-fill" :class="iconClassString" @click="hide" />
</div>
</div>
</template>
页面使用
html
<TitleBar title="邀请函" />
src/main/index.ts
ts
import { app, shell, BrowserWindow, ipcMain, Tray, Menu } from 'electron'
import { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
function createWindow(): void {
const mainWindow = new BrowserWindow({
// 自定义图标
icon: icon,
// 自定义窗口宽度
width: 360,
// 自定义窗口高度
height: 430,
//默认隐藏窗口
show: false,
// 隐藏窗口标题栏
titleBarStyle: 'hidden',
// 隐藏默认菜单
autoHideMenuBar: true,
// 不可改变窗口大小
resizable: false,
// 不可改变窗口大小
maximizable: false,
transparent: true, // 设置窗口背景透明
frame: false, // 隐藏窗口边框
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
// 托盘
const tray = new Tray(icon)
const contextMenu = [
{
label: '退出',
click: function () {
app.exit()
}
}
]
const menu = Menu.buildFromTemplate(contextMenu)
tray.setToolTip('EC编程俱乐部')
tray.setContextMenu(menu)
tray.on('click', () => {
// 使窗口显示在任务栏中
mainWindow.setSkipTaskbar(false)
mainWindow.show()
})
// IPC通信
ipcMain.on('showPage_home', () => {
// 窗口可调整大小
mainWindow.setResizable(true)
mainWindow.setSize(800, 720)
// 窗口居中
mainWindow.center()
// 窗口可最大化
mainWindow.setMaximizable(true)
})
ipcMain.on('top', () => {
mainWindow.setAlwaysOnTop(true)
})
ipcMain.on('cancle_top', () => {
mainWindow.setAlwaysOnTop(false)
})
ipcMain.on('hide', () => {
// 使窗口不显示在任务栏中
mainWindow.setSkipTaskbar(true)
mainWindow.hide()
})
ipcMain.on('min', () => {
mainWindow.minimize()
})
ipcMain.on('max', () => {
mainWindow.maximize()
})
ipcMain.on('cancel_max', () => {
mainWindow.unmaximize()
})
mainWindow.on('ready-to-show', () => {
// 自定义标题
mainWindow.setTitle('EC编程俱乐部')
mainWindow.show()
})
// 窗口变为最大化状态
mainWindow.on('maximize', () => {
mainWindow.webContents.send('maximize')
})
// 窗口从最大化状态退出
mainWindow.on('unmaximize', () => {
mainWindow.webContents.send('unmaximize')
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
ipcMain.on('quit', () => {
app.quit()
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})