Update application icon, enhance settings for auto-launch and service management, and improve tray functionality in the Electron app
This commit is contained in:
parent
9103faec32
commit
7737a06290
@ -1,7 +1,7 @@
|
||||
# PHPer 开发环境管理器
|
||||
|
||||
<p align="center">
|
||||
<img src="public/favicon.svg" alt="PHPer Logo" width="120" height="120">
|
||||
<img src="public/icon.ico" alt="PHPer Logo" width="120" height="120">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@ -168,7 +168,7 @@ phper/
|
||||
│ └── Settings.vue # 设置
|
||||
│
|
||||
├── public/ # 静态资源
|
||||
│ └── favicon.svg # 应用图标
|
||||
│ └── icon.ico # 应用图标
|
||||
│
|
||||
├── index.html # HTML 模板
|
||||
├── package.json # 项目配置
|
||||
|
||||
162
electron/main.ts
162
electron/main.ts
@ -1,4 +1,4 @@
|
||||
import { app, BrowserWindow, ipcMain, shell } from 'electron'
|
||||
import { app, BrowserWindow, ipcMain, shell, Tray, Menu, nativeImage } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { PhpManager } from './services/PhpManager'
|
||||
import { MysqlManager } from './services/MysqlManager'
|
||||
@ -10,6 +10,8 @@ import { HostsManager } from './services/HostsManager'
|
||||
import { ConfigStore } from './services/ConfigStore'
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
let tray: Tray | null = null
|
||||
let isQuitting = false
|
||||
|
||||
// 发送下载进度到渲染进程
|
||||
export function sendDownloadProgress(type: string, progress: number, downloaded: number, total: number) {
|
||||
@ -46,7 +48,8 @@ function createWindow() {
|
||||
height: 40
|
||||
},
|
||||
frame: false,
|
||||
icon: join(__dirname, '../public/icon.ico')
|
||||
icon: join(__dirname, '../public/icon.ico'),
|
||||
show: false // 先不显示,等 ready-to-show
|
||||
})
|
||||
|
||||
// 开发环境加载 Vite 开发服务器
|
||||
@ -58,13 +61,111 @@ function createWindow() {
|
||||
mainWindow.loadFile(join(__dirname, '../dist/index.html'))
|
||||
}
|
||||
|
||||
// 窗口准备好后显示
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
// 检查是否开机自启且静默启动
|
||||
const startMinimized = configStore.get('startMinimized')
|
||||
if (!startMinimized) {
|
||||
mainWindow?.show()
|
||||
}
|
||||
})
|
||||
|
||||
// 关闭按钮改为最小化到托盘
|
||||
mainWindow.on('close', (event) => {
|
||||
if (!isQuitting) {
|
||||
event.preventDefault()
|
||||
mainWindow?.hide()
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
// 创建系统托盘
|
||||
function createTray() {
|
||||
// 创建托盘图标
|
||||
const iconPath = join(__dirname, '../public/favicon.svg')
|
||||
let trayIcon
|
||||
|
||||
try {
|
||||
trayIcon = nativeImage.createFromPath(iconPath)
|
||||
if (trayIcon.isEmpty()) {
|
||||
// 如果 SVG 无法加载,创建一个简单的图标
|
||||
trayIcon = nativeImage.createEmpty()
|
||||
}
|
||||
} catch (e) {
|
||||
trayIcon = nativeImage.createEmpty()
|
||||
}
|
||||
|
||||
tray = new Tray(trayIcon)
|
||||
tray.setToolTip('PHPer 开发环境管理器')
|
||||
|
||||
// 创建托盘菜单
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: '显示主窗口',
|
||||
click: () => {
|
||||
if (mainWindow) {
|
||||
mainWindow.show()
|
||||
mainWindow.focus()
|
||||
} else {
|
||||
createWindow()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '启动全部服务',
|
||||
click: async () => {
|
||||
const result = await serviceManager.startAll()
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('service-status-changed')
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '停止全部服务',
|
||||
click: async () => {
|
||||
const result = await serviceManager.stopAll()
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('service-status-changed')
|
||||
}
|
||||
}
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: '退出',
|
||||
click: () => {
|
||||
isQuitting = true
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
tray.setContextMenu(contextMenu)
|
||||
|
||||
// 双击托盘图标显示窗口
|
||||
tray.on('double-click', () => {
|
||||
if (mainWindow) {
|
||||
mainWindow.show()
|
||||
mainWindow.focus()
|
||||
} else {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
createTray()
|
||||
createWindow()
|
||||
|
||||
// 检查是否启用开机自启且自动启动服务
|
||||
const autoStartServices = configStore.get('autoStartServicesOnBoot')
|
||||
if (autoStartServices) {
|
||||
await serviceManager.startAll()
|
||||
}
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
@ -73,10 +174,14 @@ app.whenReady().then(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// 不要在所有窗口关闭时退出,保持托盘运行
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
// 什么都不做,保持后台运行
|
||||
})
|
||||
|
||||
// 真正退出前清理
|
||||
app.on('before-quit', () => {
|
||||
isQuitting = true
|
||||
})
|
||||
|
||||
// ==================== IPC 处理程序 ====================
|
||||
@ -194,3 +299,48 @@ ipcMain.handle('config:set', (_, key: string, value: any) => configStore.set(key
|
||||
ipcMain.handle('config:getBasePath', () => configStore.getBasePath())
|
||||
ipcMain.handle('config:setBasePath', (_, path: string) => configStore.setBasePath(path))
|
||||
|
||||
// ==================== 应用设置 ====================
|
||||
// 设置开机自启
|
||||
ipcMain.handle('app:setAutoLaunch', async (_, enabled: boolean) => {
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: enabled,
|
||||
openAsHidden: true, // 静默启动
|
||||
args: ['--hidden']
|
||||
})
|
||||
configStore.set('autoLaunch', enabled)
|
||||
return { success: true, message: enabled ? '已启用开机自启' : '已禁用开机自启' }
|
||||
})
|
||||
|
||||
// 获取开机自启状态
|
||||
ipcMain.handle('app:getAutoLaunch', () => {
|
||||
return app.getLoginItemSettings().openAtLogin
|
||||
})
|
||||
|
||||
// 设置启动时最小化到托盘
|
||||
ipcMain.handle('app:setStartMinimized', (_, enabled: boolean) => {
|
||||
configStore.set('startMinimized', enabled)
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
// 获取启动时最小化状态
|
||||
ipcMain.handle('app:getStartMinimized', () => {
|
||||
return configStore.get('startMinimized') || false
|
||||
})
|
||||
|
||||
// 设置开机自启时自动启动服务
|
||||
ipcMain.handle('app:setAutoStartServices', (_, enabled: boolean) => {
|
||||
configStore.set('autoStartServicesOnBoot', enabled)
|
||||
return { success: true }
|
||||
})
|
||||
|
||||
// 获取自动启动服务状态
|
||||
ipcMain.handle('app:getAutoStartServices', () => {
|
||||
return configStore.get('autoStartServicesOnBoot') || false
|
||||
})
|
||||
|
||||
// 真正退出应用
|
||||
ipcMain.handle('app:quit', () => {
|
||||
isQuitting = true
|
||||
app.quit()
|
||||
})
|
||||
|
||||
|
||||
@ -123,6 +123,25 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
set: (key: string, value: any) => ipcRenderer.invoke('config:set', key, value),
|
||||
getBasePath: () => ipcRenderer.invoke('config:getBasePath'),
|
||||
setBasePath: (path: string) => ipcRenderer.invoke('config:setBasePath', path)
|
||||
},
|
||||
|
||||
// 应用设置
|
||||
app: {
|
||||
setAutoLaunch: (enabled: boolean) => ipcRenderer.invoke('app:setAutoLaunch', enabled),
|
||||
getAutoLaunch: () => ipcRenderer.invoke('app:getAutoLaunch'),
|
||||
setStartMinimized: (enabled: boolean) => ipcRenderer.invoke('app:setStartMinimized', enabled),
|
||||
getStartMinimized: () => ipcRenderer.invoke('app:getStartMinimized'),
|
||||
setAutoStartServices: (enabled: boolean) => ipcRenderer.invoke('app:setAutoStartServices', enabled),
|
||||
getAutoStartServices: () => ipcRenderer.invoke('app:getAutoStartServices'),
|
||||
quit: () => ipcRenderer.invoke('app:quit')
|
||||
},
|
||||
|
||||
// 监听服务状态变化
|
||||
onServiceStatusChanged: (callback: () => void) => {
|
||||
ipcRenderer.on('service-status-changed', callback)
|
||||
},
|
||||
removeServiceStatusChangedListener: (callback: () => void) => {
|
||||
ipcRenderer.removeListener('service-status-changed', callback)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" type="image/x-icon" href="/icon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>PHPer 开发环境管理器</title>
|
||||
</head>
|
||||
@ -11,4 +11,3 @@
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@ -56,9 +56,16 @@
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"requestedExecutionLevel": "requireAdministrator"
|
||||
"requestedExecutionLevel": "requireAdministrator",
|
||||
"signAndEditExecutable": false
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
|
||||
BIN
public/icon.ico
Normal file
BIN
public/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@ -8,6 +8,56 @@
|
||||
<p class="page-description">配置应用程序和服务设置</p>
|
||||
</div>
|
||||
|
||||
<!-- 应用设置 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon><Monitor /></el-icon>
|
||||
应用设置
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<h4 class="setting-title">开机自动启动</h4>
|
||||
<p class="setting-description">Windows 启动时自动运行 PHPer 开发环境管理器</p>
|
||||
</div>
|
||||
<div class="setting-action">
|
||||
<el-switch
|
||||
v-model="appSettings.autoLaunch"
|
||||
@change="(val) => toggleAutoLaunch(val as boolean)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<h4 class="setting-title">启动时最小化到托盘</h4>
|
||||
<p class="setting-description">开机自启时不弹出主窗口,直接在系统托盘后台运行</p>
|
||||
</div>
|
||||
<div class="setting-action">
|
||||
<el-switch
|
||||
v-model="appSettings.startMinimized"
|
||||
@change="(val) => toggleStartMinimized(val as boolean)"
|
||||
:disabled="!appSettings.autoLaunch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<h4 class="setting-title">启动时自动运行所有服务</h4>
|
||||
<p class="setting-description">开机自启时自动启动 Nginx、MySQL、Redis 等服务</p>
|
||||
</div>
|
||||
<div class="setting-action">
|
||||
<el-switch
|
||||
v-model="appSettings.autoStartServices"
|
||||
@change="(val) => toggleAutoStartServices(val as boolean)"
|
||||
:disabled="!appSettings.autoLaunch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 基础设置 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
@ -33,13 +83,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 开机自启动 -->
|
||||
<!-- 服务开机自启动 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon><Timer /></el-icon>
|
||||
开机自启动
|
||||
服务开机自启动
|
||||
</span>
|
||||
<span class="card-subtitle">独立于应用的 Windows 服务自启</span>
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<div class="setting-item" v-for="service in services" :key="service.name">
|
||||
@ -75,7 +126,7 @@
|
||||
<h2 class="app-title">PHPer 开发环境管理器</h2>
|
||||
<p class="app-version">版本 1.0.0</p>
|
||||
<p class="app-desc">
|
||||
一站式 PHP 开发环境管理工具,支持 PHP、MySQL、Nginx、Redis 的安装和管理。
|
||||
一站式 PHP 开发环境管理工具,支持 PHP、MySQL、Nginx、Redis、Node.js 的安装和管理。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -92,6 +143,9 @@
|
||||
<el-button @click="openLink('https://redis.io/')">
|
||||
Redis 官网
|
||||
</el-button>
|
||||
<el-button @click="openLink('https://nodejs.org/')">
|
||||
Node.js 官网
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -102,6 +156,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Monitor } from '@element-plus/icons-vue'
|
||||
|
||||
interface ServiceAutoStart {
|
||||
name: string
|
||||
@ -110,19 +165,35 @@ interface ServiceAutoStart {
|
||||
autoStart: boolean
|
||||
}
|
||||
|
||||
interface AppSettings {
|
||||
autoLaunch: boolean
|
||||
startMinimized: boolean
|
||||
autoStartServices: boolean
|
||||
}
|
||||
|
||||
const basePath = ref('')
|
||||
const appSettings = reactive<AppSettings>({
|
||||
autoLaunch: false,
|
||||
startMinimized: false,
|
||||
autoStartServices: false
|
||||
})
|
||||
|
||||
const services = reactive<ServiceAutoStart[]>([
|
||||
{ name: 'nginx', displayName: 'Nginx', description: '开机时自动启动 Nginx 服务', autoStart: false },
|
||||
{ name: 'mysql', displayName: 'MySQL', description: '开机时自动启动 MySQL 服务', autoStart: false },
|
||||
{ name: 'redis', displayName: 'Redis', description: '开机时自动启动 Redis 服务', autoStart: false },
|
||||
{ name: 'php-cgi', displayName: 'PHP-CGI', description: '开机时自动启动 PHP FastCGI 进程', autoStart: false }
|
||||
{ name: 'nginx', displayName: 'Nginx', description: '开机时自动启动 Nginx 服务(独立于应用)', autoStart: false },
|
||||
{ name: 'mysql', displayName: 'MySQL', description: '开机时自动启动 MySQL 服务(独立于应用)', autoStart: false },
|
||||
{ name: 'redis', displayName: 'Redis', description: '开机时自动启动 Redis 服务(独立于应用)', autoStart: false }
|
||||
])
|
||||
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
basePath.value = await window.electronAPI?.config.getBasePath() || ''
|
||||
|
||||
// 加载自启动设置
|
||||
// 加载应用设置
|
||||
appSettings.autoLaunch = await window.electronAPI?.app?.getAutoLaunch() || false
|
||||
appSettings.startMinimized = await window.electronAPI?.app?.getStartMinimized() || false
|
||||
appSettings.autoStartServices = await window.electronAPI?.app?.getAutoStartServices() || false
|
||||
|
||||
// 加载服务自启动设置
|
||||
for (const service of services) {
|
||||
service.autoStart = await window.electronAPI?.service.getAutoStart(service.name) || false
|
||||
}
|
||||
@ -131,6 +202,46 @@ const loadSettings = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const toggleAutoLaunch = async (enabled: boolean) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.app?.setAutoLaunch(enabled)
|
||||
if (result?.success) {
|
||||
ElMessage.success(result.message)
|
||||
if (!enabled) {
|
||||
// 禁用开机自启时,同时禁用相关选项
|
||||
appSettings.startMinimized = false
|
||||
appSettings.autoStartServices = false
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(result?.message || '设置失败')
|
||||
appSettings.autoLaunch = !enabled
|
||||
}
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message)
|
||||
appSettings.autoLaunch = !enabled
|
||||
}
|
||||
}
|
||||
|
||||
const toggleStartMinimized = async (enabled: boolean) => {
|
||||
try {
|
||||
await window.electronAPI?.app?.setStartMinimized(enabled)
|
||||
ElMessage.success(enabled ? '已启用启动时最小化' : '已禁用启动时最小化')
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message)
|
||||
appSettings.startMinimized = !enabled
|
||||
}
|
||||
}
|
||||
|
||||
const toggleAutoStartServices = async (enabled: boolean) => {
|
||||
try {
|
||||
await window.electronAPI?.app?.setAutoStartServices(enabled)
|
||||
ElMessage.success(enabled ? '已启用自动启动服务' : '已禁用自动启动服务')
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message)
|
||||
appSettings.autoStartServices = !enabled
|
||||
}
|
||||
}
|
||||
|
||||
const toggleAutoStart = async (name: string, enabled: boolean) => {
|
||||
try {
|
||||
const result = await window.electronAPI?.service.setAutoStart(name, enabled)
|
||||
@ -163,6 +274,12 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-subtitle {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
13
src/vite-env.d.ts
vendored
13
src/vite-env.d.ts
vendored
@ -105,6 +105,19 @@ interface Window {
|
||||
getBasePath: () => Promise<string>
|
||||
setBasePath: (path: string) => Promise<void>
|
||||
}
|
||||
|
||||
app: {
|
||||
setAutoLaunch: (enabled: boolean) => Promise<{ success: boolean; message: string }>
|
||||
getAutoLaunch: () => Promise<boolean>
|
||||
setStartMinimized: (enabled: boolean) => Promise<{ success: boolean }>
|
||||
getStartMinimized: () => Promise<boolean>
|
||||
setAutoStartServices: (enabled: boolean) => Promise<{ success: boolean }>
|
||||
getAutoStartServices: () => Promise<boolean>
|
||||
quit: () => Promise<void>
|
||||
}
|
||||
|
||||
onServiceStatusChanged: (callback: () => void) => void
|
||||
removeServiceStatusChangedListener: (callback: () => void) => void
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user