import { defineConfig, loadEnv } from 'vite'import react from'@vitejs/plugin-react'import electron from'vite-plugin-electron'import { resolve } from'path'import { parseEnv } from'./src/utils/env'
//https://vitejs.dev/config/
export default defineConfig(({ command, mode }) =>{
const viteEnv=loadEnv(mode, process.cwd())
const env=parseEnv(viteEnv)return{
plugins: [
react(),
electron({
entry:'electron-main.js',
})
],
esbuild: {//打包去除 console.log 和 debugger
drop: env.VITE_DROP_CONSOLE && command === 'build' ? ["console", "debugger"] : []
},/*开发服务器配置*/server: {//端口
port: env.VITE_PORT,//代理配置
proxy: {//...
}
},
resolve: {//设置别名
alias: {'@': resolve(__dirname, 'src'),'@assets': resolve(__dirname, 'src/assets'),'@components': resolve(__dirname, 'src/components'),'@views': resolve(__dirname, 'src/views')
}
}
}
})
import { useState, useContext } from 'react'
import { Modal } from '@arco-design/web-react'
import { setWin } from '@/windows/action'
function WinBtn(props) {
const {
color = '#fff',
minimizable = true,
maximizable = true,
closable = true,
zIndex = 2023,
children
} = props
const [hasMaximized, setHasMaximized] = useState(false)
window.electronAPI.invoke('win__isMaximized').then(res => {
setHasMaximized(res)
})
window.electronAPI.receive('win__hasMaximized', (e, res) => {
setHasMaximized(res)
})
// 最小化
const handleWinMin = () => {
window.electronAPI.send("win__minimize")
}
// 最大化/还原
const handleWinMax2Min = () => {
window.electronAPI.invoke("win__max2min").then(res => {
console.log(res)
setHasMaximized(res)
})
}
// 关闭
const handleWinClose = () => {
if(window.config.isMainWin) {
Modal.confirm({
title: '提示',
content:<divstyle={{textAlign: 'center' }}>是否最小化至托盘,不退出程序?</div>,
okButtonProps: {status: 'warning'},
style: {width: 360},
cancelText: '最小化至托盘',
okText: '残忍退出',
onOk: () => {
setWin('close')
},
onCancel: () => {
setWin('hide', window.config.id)
}
})
}else {
setWin('close', window.config.id)
}
}
return (<>
<divclassName="vui__macbtn flexbox flex-alignc"style={{zIndex:zIndex}}>
<divclassName="vui__macbtn-groups flexbox flex-alignc"style={{color:color}}>{ JSON.parse(minimizable) &&<aclassName="mbtn min"title="最小化"onClick={handleWinMin}><svgx="0"y="0"width="10"height="10"viewBox="0 0 10 10"><pathfill="#995700"d="M8.048,4.001c0.163,0.012 0.318,0.054 0.459,0.137c0.325,0.191 0.518,0.559 0.49,0.934c-0.007,0.097 -0.028,0.192 -0.062,0.283c-0.04,0.105 -0.098,0.204 -0.171,0.29c-0.083,0.098 -0.185,0.181 -0.299,0.24c-0.131,0.069 -0.271,0.103 -0.417,0.114c-2.031,0.049 -4.065,0.049 -6.096,0c-0.163,-0.012 -0.318,-0.054 -0.459,-0.137c-0.325,-0.191 -0.518,-0.559 -0.49,-0.934c0.007,-0.097 0.028,-0.192 0.062,-0.283c0.04,-0.105 0.098,-0.204 0.171,-0.29c0.083,-0.098 0.185,-0.181 0.299,-0.24c0.131,-0.069 0.271,-0.103 0.417,-0.114c2.031,-0.049 4.065,-0.049 6.096,0Z"></path></svg></a>}
{ JSON.parse(maximizable) &&<aclassName="mbtn max"title={hasMaximized? '向下还原' : '最大化'} onClick={handleWinMax2Min}>{
hasMaximized ?<svgx="0"y="0"width="10"height="10"viewBox="0 0 10 10"><pathfill="#4d0000"d="M5,10c0,0 0,-2.744 0,-4.167c0,-0.221 -0.088,-0.433 -0.244,-0.589c-0.156,-0.156 -0.368,-0.244 -0.589,-0.244c-1.423,0 -4.167,0 -4.167,0l5,5Z"></path><pathfill="#006400"d="M5,0c0,0 0,2.744 0,4.167c0,0.221 0.088,0.433 0.244,0.589c0.156,0.156 0.368,0.244 0.589,0.244c1.423,0 4.167,0 4.167,0l-5,-5Z"></path></svg>:<svgx="0"y="0"width="10"height="10"viewBox="0 0 10 10"><pathfill="#4d0000"d="M2,3c0,0 0,2.744 0,4.167c0,0.221 0.088,0.433 0.244,0.589c0.156,0.156 0.368,0.244 0.589,0.244c1.423,0 4.167,0 4.167,0l-5,-5Z"></path><pathfill="#006400"d="M8,7c0,0 0,-2.744 0,-4.167c0,-0.221 -0.088,-0.433 -0.244,-0.589c-0.156,-0.156 -0.368,-0.244 -0.589,-0.244c-1.423,0 -4.167,0 -4.167,0l5,5Z"></path></svg>}</a>}
{ JSON.parse(closable) &&<aclassName="mbtn close"title="关闭"onClick={handleWinClose}><svgx="0"y="0"width="10"height="10"viewBox="0 0 10 10"><pathfill="#4d0000"d="M5,3.552c0.438,-0.432 0.878,-0.861 1.322,-1.287c0.049,-0.044 0.101,-0.085 0.158,-0.119c0.149,-0.091 0.316,-0.137 0.49,-0.146c0.04,0 0.04,0 0.08,0.001c0.16,0.011 0.314,0.054 0.453,0.135c0.08,0.046 0.154,0.104 0.218,0.171c0.252,0.262 0.342,0.65 0.232,0.996c-0.045,0.141 -0.121,0.265 -0.218,0.375c-0.426,0.444 -0.855,0.884 -1.287,1.322c0.432,0.438 0.861,0.878 1.287,1.322c0.097,0.11 0.173,0.234 0.218,0.375c0.04,0.126 0.055,0.26 0.043,0.392c-0.011,0.119 -0.043,0.236 -0.094,0.344c-0.158,0.327 -0.49,0.548 -0.852,0.566c-0.106,0.005 -0.213,-0.007 -0.315,-0.035c-0.156,-0.043 -0.293,-0.123 -0.413,-0.229c-0.444,-0.426 -0.884,-0.855 -1.322,-1.287c-0.438,0.432 -0.878,0.861 -1.322,1.287c-0.11,0.097 -0.234,0.173 -0.375,0.218c-0.126,0.04 -0.26,0.055 -0.392,0.043c-0.119,-0.011 -0.236,-0.043 -0.344,-0.094c-0.327,-0.158 -0.548,-0.49 -0.566,-0.852c-0.005,-0.106 0.007,-0.213 0.035,-0.315c0.043,-0.156 0.123,-0.293 0.229,-0.413c0.426,-0.444 0.855,-0.884 1.287,-1.322c-0.432,-0.438 -0.861,-0.878 -1.287,-1.322c-0.106,-0.12 -0.186,-0.257 -0.229,-0.413c-0.025,-0.089 -0.037,-0.182 -0.036,-0.275c0.004,-0.363 0.211,-0.704 0.532,-0.874c0.13,-0.069 0.272,-0.105 0.418,-0.115c0.04,-0.001 0.04,-0.001 0.08,-0.001c0.174,0.009 0.341,0.055 0.49,0.146c0.057,0.034 0.109,0.075 0.158,0.119c0.444,0.426 0.884,0.855 1.322,1.287Z"></path></svg></a>}<iclassName="mr-10"></i>{ children }</div>
<divclassName="vui__mactitle">{window.config.title || '首页'}</div>
</div>
</>)
}
export default WinBtn
/**
* Electron多窗口管理器
* @author Andy Q:282310962*/const { app, BrowserWindow, ipcMain, Menu, Tray, dialog, globalShortcut }= require('electron')//const { loadEnv } = require('vite')
const { join } = require('path')//根目录路径
process.env.ROOT = join(__dirname, '../../')
const isDev= process.env.NODE_ENV === 'development'
//const winURL = isDev ? 'http://localhost:3000/' : join(__dirname, 'dist/index.html')
const winURL = isDev ? process.env.VITE_DEV_SERVER_URL : join(process.env.ROOT, 'dist/index.html')
class MultiWindow {
constructor() {//主窗口对象
this.main = null
//窗口组
this.group ={}//托盘图标
this.tray = null
this.flashTimer = null
this.trayIco1 = join(process.env.ROOT, 'resource/tray.ico')this.trayIco2 = join(process.env.ROOT, 'resource/tray-empty.ico')//监听ipcMain事件
this.listenIpc()//创建系统托盘
this.createTray()
}//系统配置参数
winOptions() {return{//窗口图标
icon: join(process.env.ROOT, 'resource/shortcut.ico'),
backgroundColor:'#fff',
autoHideMenuBar:true,
titleBarStyle:'hidden',
width:900,
height:600,
resizable:true,
minimizable:true,
maximizable:true,
frame:false, //设置为 false 时可以创建一个无边框窗口 默认值为 true
show: false, //窗口是否在创建时显示
webPreferences: {
contextIsolation:true, //启用上下文隔离(为了安全性)(默认true)
nodeIntegration: false, //启用Node集成(默认false)
preload: join(process.env.ROOT, 'electron-preload.js')
}
}
}//创建新窗口
createWin(options) {//...
}//...
//主进程监听事件
listenIpc() {//创建新窗体
ipcMain.on('win-create', (event, args) => this.createWin(args))//...
//托盘图标闪烁
ipcMain.on('win__flashTray', (event, bool) => this.flashTray(bool))//屏幕截图
ipcMain.on('win__setCapture', () =>{//...
})
}//创建系统托盘图标
createTray() {
console.log(__dirname)
console.log(join(process.env.ROOT,'resource/tray.ico'))
const trayMenu=Menu.buildFromTemplate([
{
label:'打开主界面',
icon: join(process.env.ROOT,'resource/home.png'),
click: ()=>{try{for(let i in this.group) {
let win= this.getWin(i)if(!win) return
//是否主窗口
if(this.group[i].isMainWin) {if(win.isMinimized()) win.restore()
win.show()
}
}
}catch(error) {
console.log(error)
}
}
},
{
label:'设置中心',
icon: join(process.env.ROOT,'resource/setting.png'),
click: ()=>{for(let i in this.group) {
let win= this.getWin(i)if(win) win.webContents.send('win__ipcData', { type: 'CREATE_WIN_SETTING', value: null})
}
},
},
{
label:'锁屏',
click: ()=> null,
},
{
label:'关闭托盘闪烁',
click: ()=>{this.flashTray(false)
}
},
{type:'separator'},/*{
label: '重启',
click: () => {
// app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) })
// app.exit(0)
app.relaunch()
app.quit()
}
},*/{
label:'关于',
click: ()=>{for(let i in this.group) {
let win= this.getWin(i)if(win) win.webContents.send('win__ipcData', { type: 'CREATE_WIN_ABOUT', value: null})
}
}
},
{
label:'关闭应用并退出',
icon: join(process.env.ROOT,'resource/logout.png'),
click: ()=>{
dialog.showMessageBox(this.main, {
title:'询问',
message:'确定要退出应用程序吗?',
buttons: ['取消', '最小化托盘', '退出应用'],
type:'error',
noLink:false, //true传统按钮样式 false链接样式
cancelId: 0}).then(res=>{
console.log(res)
const index=res.responseif(index == 0) {
console.log('取消')
}if(index == 1) {
console.log('最小化托盘')for(let i in this.group) {
let win= this.getWin(i)if(win) win.hide()
}
}else if(index == 2) {
console.log('退出应用')try{for(let i in this.group) {
let win= this.getWin(i)if(win) win.webContents.send('win__ipcData', { type: 'WIN_LOGOUT', value: null})
}//app.quit 和 app.exit(0) 都可退出应用。
//前者可以被打断并触发一些事件,而后者将强制应用程序退出而不触发任何事件或允许应用程序取消操作。
app.quit()
}catch(error) {
console.log(error)
}
}
})
}
}
])this.tray = new Tray(this.trayIco1)this.tray.setContextMenu(trayMenu)this.tray.setToolTip(app.name)this.tray.on('double-click', () =>{
console.log('double clicked')
})//开启托盘闪烁
//this.flashTray(true)
}//托盘图标闪烁
flashTray(flash) {
let hasIco= false
if(flash) {if(this.flashTimer) return
this.flashTimer = setInterval(() =>{this.tray.setImage(hasIco ? this.trayIco1 : this.trayIco2)
hasIco= !hasIco
},500)
}else{if(this.flashTimer) {
clearInterval(this.flashTimer)this.flashTimer = null}this.tray.setImage(this.trayIco1)
}
}//销毁托盘图标
destoryTray() {this.flashTray(false)this.tray.destroy()this.tray = null}
}
module.exports= MultiWindow