春分不仅意味着昼长夜短,更象征着万物复苏。它预示着生机勃勃的季节即将到来,草木欣欣向荣,生长愈发旺盛
在惊蛰-electron里面优雅地发起请求已经定制好了请求方式
接下来我们讲讲如何自定义electron的titlebar(兼容win和mac版本)
看看原始的的titlebar:
可以发现不管是mac还是win上面都会有多处一条特殊的割裂的区域显示了title和操作区域,其实是很丑的
macos:
win:
改造:方案一: 利用api自定义
- 我们先改动主进程文件:src/main/index.ts文件,改动createMainWindow方法
//改动createMainWindow方法,里面增加
// macos是不支持titleBarOverlay的自定义的
titleBarStyle: 'hidden',
...(process.platform !== 'darwin'
? { titleBarOverlay: { color: '#000', height: 35, symbolColor: '#000' } }
: {}),
//完整是这样:
mainWindow = new BrowserWindow({
width: 1100,
height: 700,
minWidth: 1100,
minHeight: 700,
show: false,
autoHideMenuBar: true,
titleBarStyle: 'hidden',
...(process.platform !== 'darwin'
? { titleBarOverlay: { color: '#eaf4ff', height: 35, symbolColor: '#000' } }
: {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
2.当然我们还要改动渲染进程renderer/src/layout/index
注意高度减少35 是需要给titlebar留高度的,titleBarOverlay里面设置了35px
// 样式增加
.layout {
display: flex;
height: calc(100vh - 35px); // 改动这个
margin-top: 32px; // 增加这个
display: flex;
macos效果:
win效果:
问题所在:macos是无法定制背景颜色跟我们左侧导航颜色一致的(如果是白色是没问题的):参考文档
那么这种其实不适合我们左侧有颜色的,不能统一颜色,其实是不太理想的
改造: 方案二: 用ui完全自定义
进行方案二之前先要撤回方案一改动的代码;我们其实可以完全用渲染进程的ui自定义titilebar,只需要主进程提供类关闭、缩小、放大、方法等等的功能就好了
- 改造src/main/index.ts文件,改动createMainWindow
// 改动createMainWindow方法
function createMainWindow(): void {
mainWindow = new BrowserWindow({
width: 1100,
height: 700,
minWidth: 1100,
minHeight: 700,
show: false,
autoHideMenuBar: true,
titleBarStyle: 'hidden', //增加了这个
frame: process.platform === 'darwin', // 增加了这个,macos显示原生的titlebar
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
})
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'))
}
// 方便调式
// is.dev && mainWindow.webContents.openDevTools()
mainWindow.on('closed', () => {
mainWindow = null
})
//增加了这3个ipcMain
ipcMain.on('window-minimize', () => mainWindow?.minimize())
ipcMain.on('window-close', () => mainWindow?.close())
ipcMain.on('window-maximize', () => {
if (mainWindow) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize() // 如果已经最大化,则恢复
} else {
mainWindow.maximize() // 否则最大化窗口
}
}
})
}
- 改动src/preload/index.ts
传递process.platform让渲染进程知道是macos或者window
// 传递process.platform让渲染进程知道是macos或者window
import { contextBridge } from "electron"
import { electronAPI } from "@electron-toolkit/preload"
// Custom APIs for renderer
const api = {}
const extendedElectronAPI = {
...electronAPI,
platform: process.platform // 增加这个
}
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld("electron", extendedElectronAPI)
contextBridge.exposeInMainWorld("api", api)
} catch (error) {
console.error(error)
}
} else {
// @ts-ignore (define in dts)
window.electron = extendedElectronAPI
// @ts-ignore (define in dts)
window.api = api
}
- 改动src/preload/index.d.ts
声明platform
import { ElectronAPI } from "@electron-toolkit/preload"
declare global {
// 扩展 ElectronAPI 接口
interface IElectronAPI extends ElectronAPI {
platform: string // 增加这个
}
interface Window {
electron: IElectronAPI
api: unknown
}
}
- 增加自定义titlebar
在renderer/src/ 增加titlebar文件夹
//titlebar/index.vue
<!-- 定制不同系统的头部样式 -->
<template>
<div class="titlebar">
<!-- macOS 样式 -->
<div v-if="isMac" class="macos">
<el-icon :size="16" class="action"> <Setting /></el-icon>
</div>
<!-- Windows 样式 -->
<div v-else class="win">
<div class="title"></div>
<div class="controls">
<ElSpace size="small">
<el-icon class="action"><Setting /></el-icon>
<el-icon :size="16" class="action" @click="handleMinimize"> <Minus /></el-icon>
<el-icon :size="16" class="action" @click="handleMaximize"><FullScreen /></el-icon>
<el-icon :size="16" class="action" @click="handleClose"> <Close /></el-icon>
</ElSpace>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { ElSpace, ElIcon } from 'element-plus'
import { Minus, Close, Bell, FullScreen, Setting } from '@element-plus/icons-vue'
// 条件判断
const isMac = computed(() => window.electron.process.platform === 'darwin')
// 方法定义
const handleMinimize = () => {
window.electron.ipcRenderer.send('window-minimize')
}
const handleClose = () => {
window.electron.ipcRenderer.send('window-close')
}
const handleMaximize = () => {
window.electron.ipcRenderer.send('window-maximize')
}
</script>
<style lang="scss" scoped>
.titlebar {
.action {
background: transparent;
border: none;
color: #333;
width: 20px;
height: 32px;
cursor: pointer;
}
.win {
height: 32px;
background-color: #eaf4ff;
color: white;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px 0 10px;
-webkit-app-region: drag; /* 允许拖动窗口 */
.controls {
display: flex;
-webkit-app-region: no-drag; /* 不允许拖动窗口 */
}
}
.macos {
height: 32px;
background-color: #eaf4ff;
color: white;
display: flex;
align-items: center;
justify-content: end;
padding: 0 10px 0 10px;
-webkit-app-region: drag; /* 允许拖动窗口 */
}
}
</style>
- 改造渲染进程的renderer/src/layout/index.vue
改变布局,给自定义titlebar留出位置
<template>
<div class="layout">
<TitleBar />
<div class="body">
<div class="left-bar">
<div class="nav-bar">
<div class="menu">
<div class="nav-item">
<el-icon style="padding-right: 6px"><Monitor /></el-icon>主页
</div>
</div>
<div class="system">
<div class="nav-item" @click="handleLogout">
<el-icon style="padding-right: 6px"><Monitor /></el-icon>退登
</div>
</div>
</div>
</div>
<div class="content">
<router-view></router-view>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { useRouter } from 'vue-router'
import { Monitor } from '@element-plus/icons-vue'
import { ElIcon } from 'element-plus'
import TitleBar from '@renderer/titlebar/index.vue'
const router = useRouter()
const handleLogout = async () => {
const res = await window.electron.ipcRenderer.invoke('logout')
if (res) {
router.push('/login')
}
}
</script>
<style scoped lang="scss">
.layout {
display: flex;
flex-direction: column;
.body {
height: calc(100vh - 35px);
display: flex;
.left-bar {
height: 100%;
flex-shrink: 0;
width: 120px;
background-color: #eaf4ff;
display: flex;
align-items: center;
flex-direction: column;
.menu {
display: flex;
justify-content: space-between;
flex-direction: column;
}
.nav-bar {
flex: 1;
width: 100px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
.nav-item {
margin-bottom: 8px;
height: 30px;
width: 100px;
display: flex;
font-size: 14px;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 5px;
color: rgba(0, 0, 0, 0.6);
&:hover {
background-color: #fff;
font-weight: bold;
color: #000;
}
}
.active {
background-color: #fff;
font-weight: bolder;
color: #000;
}
}
}
.content {
flex: 1;
}
}
}
</style>
重启应用,效果如下:
macos:
win:
好了,这样自定义的titlebar是跟左侧完全融入的了,且兼容多个系统了
到此,自定义titlebar就基本完成了!
最后源码地址:源码