Implement single instance lock for Electron app, enhance auto-start service management, and update package.json with afterPack script. Improve installer script to manage desktop shortcut creation and removal.
This commit is contained in:
parent
f72e4b7a6f
commit
894c792253
65
build/afterPack.js
Normal file
65
build/afterPack.js
Normal file
@ -0,0 +1,65 @@
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
|
||||
exports.default = async function(context) {
|
||||
// 只在 Windows 上执行
|
||||
if (process.platform !== 'win32') {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Running afterPack hook to set icon...');
|
||||
|
||||
const appOutDir = context.appOutDir;
|
||||
const productName = context.packager.appInfo.productName;
|
||||
const exePath = path.join(appOutDir, `${productName}.exe`);
|
||||
const iconPath = path.join(__dirname, 'icon.ico');
|
||||
|
||||
// rcedit 路径
|
||||
const userHome = os.homedir();
|
||||
const cacheDir = path.join(userHome, 'AppData', 'Local', 'electron-builder', 'Cache', 'winCodeSign');
|
||||
|
||||
// 查找 rcedit
|
||||
let rceditPath = null;
|
||||
if (fs.existsSync(cacheDir)) {
|
||||
const dirs = fs.readdirSync(cacheDir);
|
||||
for (const dir of dirs) {
|
||||
const possiblePath = path.join(cacheDir, dir, 'rcedit-x64.exe');
|
||||
if (fs.existsSync(possiblePath)) {
|
||||
rceditPath = possiblePath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!rceditPath) {
|
||||
console.warn('rcedit not found, skipping icon modification');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(exePath)) {
|
||||
console.warn(`Exe not found: ${exePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(iconPath)) {
|
||||
console.warn(`Icon not found: ${iconPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Setting icon for: ${exePath}`);
|
||||
console.log(`Using icon: ${iconPath}`);
|
||||
console.log(`Using rcedit: ${rceditPath}`);
|
||||
|
||||
execSync(`"${rceditPath}" "${exePath}" --set-icon "${iconPath}"`, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
console.log('Icon set successfully!');
|
||||
} catch (error) {
|
||||
console.error('Failed to set icon:', error.message);
|
||||
}
|
||||
};
|
||||
|
||||
BIN
build/icon.ico
BIN
build/icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 353 KiB |
@ -5,9 +5,13 @@
|
||||
|
||||
!macro customInstall
|
||||
; 安装完成后的自定义操作
|
||||
; 删除默认创建的桌面快捷方式,然后重新创建带正确图标的快捷方式
|
||||
Delete "$DESKTOP\PHPer开发环境管理器.lnk"
|
||||
CreateShortCut "$DESKTOP\PHPer开发环境管理器.lnk" "$INSTDIR\PHPer开发环境管理器.exe" "" "$INSTDIR\PHPer开发环境管理器.exe" 0
|
||||
!macroend
|
||||
|
||||
!macro customUnInstall
|
||||
; 卸载时的自定义操作
|
||||
Delete "$DESKTOP\PHPer开发环境管理器.lnk"
|
||||
!macroend
|
||||
|
||||
|
||||
@ -235,26 +235,55 @@ function createTray() {
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
createTray();
|
||||
createWindow();
|
||||
// 单实例锁定
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
if (!gotTheLock) {
|
||||
// 如果获取不到锁,说明已有实例在运行,退出当前实例
|
||||
app.quit();
|
||||
} else {
|
||||
// 当第二个实例启动时,聚焦到第一个实例的窗口
|
||||
app.on("second-instance", () => {
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) {
|
||||
mainWindow.restore();
|
||||
}
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 不要在所有窗口关闭时退出,保持托盘运行
|
||||
app.on("window-all-closed", () => {
|
||||
// 什么都不做,保持后台运行
|
||||
});
|
||||
app.whenReady().then(async () => {
|
||||
createTray();
|
||||
createWindow();
|
||||
|
||||
// 真正退出前清理
|
||||
app.on("before-quit", () => {
|
||||
isQuitting = true;
|
||||
});
|
||||
// 根据配置自动启动服务
|
||||
try {
|
||||
const result = await serviceManager.startAutoStartServices();
|
||||
if (result.details.length > 0) {
|
||||
console.log("自动启动服务:", result.details.join(", "));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("自动启动服务失败:", error);
|
||||
}
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 不要在所有窗口关闭时退出,保持托盘运行
|
||||
app.on("window-all-closed", () => {
|
||||
// 什么都不做,保持后台运行
|
||||
});
|
||||
|
||||
// 真正退出前清理
|
||||
app.on("before-quit", () => {
|
||||
isQuitting = true;
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== IPC 处理程序 ====================
|
||||
|
||||
|
||||
@ -16,6 +16,9 @@ interface ConfigSchema {
|
||||
redis: boolean;
|
||||
};
|
||||
sites: SiteConfig[];
|
||||
// 应用设置
|
||||
autoLaunch: boolean;
|
||||
startMinimized: boolean;
|
||||
}
|
||||
|
||||
export interface SiteConfig {
|
||||
@ -60,6 +63,9 @@ export class ConfigStore {
|
||||
redis: false,
|
||||
},
|
||||
sites: [],
|
||||
// 应用设置默认值
|
||||
autoLaunch: false,
|
||||
startMinimized: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -158,6 +158,71 @@ export class ServiceManager {
|
||||
return this.checkAutoStart(service)
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置启动设置为自启动的服务
|
||||
*/
|
||||
async startAutoStartServices(): Promise<{ success: boolean; message: string; details: string[] }> {
|
||||
const details: string[] = []
|
||||
const autoStart = this.configStore.get('autoStart')
|
||||
|
||||
try {
|
||||
// 检查 Nginx 自启动
|
||||
if (autoStart.nginx) {
|
||||
const nginxPath = this.configStore.getNginxPath()
|
||||
if (existsSync(join(nginxPath, 'nginx.exe'))) {
|
||||
if (!(await this.checkProcess('nginx.exe'))) {
|
||||
await this.startProcess(join(nginxPath, 'nginx.exe'), [], nginxPath)
|
||||
details.push('Nginx 已自动启动')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 MySQL 自启动
|
||||
if (autoStart.mysql) {
|
||||
const mysqlVersions = this.configStore.get('mysqlVersions')
|
||||
if (mysqlVersions.length > 0) {
|
||||
if (!(await this.checkProcess('mysqld.exe'))) {
|
||||
for (const version of mysqlVersions) {
|
||||
const mysqlPath = this.configStore.getMysqlPath(version)
|
||||
const mysqld = join(mysqlPath, 'bin', 'mysqld.exe')
|
||||
if (existsSync(mysqld)) {
|
||||
// 使用 VBScript 隐藏窗口启动
|
||||
const vbsPath = join(mysqlPath, 'start_mysql.vbs')
|
||||
const vbsContent = `Set WshShell = CreateObject("WScript.Shell")\nWshShell.Run """${mysqld}"" --defaults-file=""${join(mysqlPath, 'my.ini')}""", 0, False`
|
||||
writeFileSync(vbsPath, vbsContent)
|
||||
await execAsync(`cscript //nologo "${vbsPath}"`, { cwd: mysqlPath })
|
||||
details.push(`MySQL ${version} 已自动启动`)
|
||||
break // 只启动第一个版本
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 Redis 自启动
|
||||
if (autoStart.redis) {
|
||||
const redisPath = this.configStore.getRedisPath()
|
||||
const redisServer = join(redisPath, 'redis-server.exe')
|
||||
if (existsSync(redisServer)) {
|
||||
if (!(await this.checkProcess('redis-server.exe'))) {
|
||||
const configFile = join(redisPath, 'redis.windows.conf')
|
||||
const args = existsSync(configFile) ? ['redis.windows.conf'] : []
|
||||
await this.startProcess(redisServer, args, redisPath)
|
||||
details.push('Redis 已自动启动')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (details.length === 0) {
|
||||
return { success: true, message: '没有需要自动启动的服务', details }
|
||||
}
|
||||
|
||||
return { success: true, message: '自动启动服务完成', details }
|
||||
} catch (error: any) {
|
||||
return { success: false, message: `自动启动失败: ${error.message}`, details }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动所有已安装的服务
|
||||
*/
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"productName": "PHPer开发环境管理器",
|
||||
"copyright": "Copyright © 2024 PHPer",
|
||||
"icon": "build/icon.ico",
|
||||
"afterPack": "./build/afterPack.js",
|
||||
"directories": {
|
||||
"output": "release",
|
||||
"buildResources": "build"
|
||||
@ -67,8 +68,7 @@
|
||||
}
|
||||
],
|
||||
"icon": "build/icon.ico",
|
||||
"requestedExecutionLevel": "requireAdministrator",
|
||||
"signAndEditExecutable": false
|
||||
"requestedExecutionLevel": "requireAdministrator"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
|
||||
BIN
public/icon.ico
BIN
public/icon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 353 KiB |
Loading…
Reference in New Issue
Block a user