新增 系统托盘功能及快速切换API配置

This commit is contained in:
2026-04-17 23:25:09 +08:00
parent cdddcccfe0
commit 3329e0ddbf
3 changed files with 144 additions and 7 deletions

129
main.js
View File

@@ -1,4 +1,4 @@
const { app, BrowserWindow, ipcMain, dialog } = require('electron')
const { app, BrowserWindow, ipcMain, dialog, Tray, Menu, nativeImage } = require('electron')
const path = require('path')
const fs = require('fs')
console.log('main.js loaded')
@@ -6,7 +6,123 @@ console.log('app.getPath("home"):', app.getPath('home'))
const SETTINGS_FILE = path.join(app.getPath('home'), '.iflow', 'settings.json')
console.log('SETTINGS_FILE:', SETTINGS_FILE)
let mainWindow
let tray
const isDev = process.argv.includes('--dev')
// 创建系统托盘
function createTray() {
// 使用内置图标或创建空白图标
const iconPath = path.join(__dirname, 'build', 'icon.ico')
let trayIcon
if (fs.existsSync(iconPath)) {
trayIcon = nativeImage.createFromPath(iconPath)
} else {
// 创建一个简单的图标
trayIcon = nativeImage.createEmpty()
}
// 调整图标大小以适应托盘
trayIcon = trayIcon.resize({ width: 16, height: 16 })
tray = new Tray(trayIcon)
tray.setToolTip('iFlow 设置编辑器')
updateTrayMenu()
// 双击托盘显示主窗口
tray.on('double-click', () => {
if (mainWindow) {
mainWindow.show()
mainWindow.focus()
}
})
}
// 更新托盘菜单
function updateTrayMenu() {
const settings = readSettings()
const profiles = settings?.apiProfiles || {}
const currentProfile = settings?.currentApiProfile || 'default'
const profileList = Object.keys(profiles).length > 0
? Object.keys(profiles)
: ['default']
const profileMenuItems = profileList.map(name => ({
label: name + (name === currentProfile ? ' ✓' : ''),
type: 'radio',
checked: name === currentProfile,
click: () => switchApiProfileFromTray(name)
}))
const contextMenu = Menu.buildFromTemplate([
{
label: '显示主窗口',
click: () => {
if (mainWindow) {
mainWindow.show()
mainWindow.focus()
}
}
},
{ type: 'separator' },
{
label: '切换 API 配置',
submenu: profileMenuItems
},
{ type: 'separator' },
{
label: '退出',
click: () => {
app.isQuitting = true
app.quit()
}
}
])
tray.setContextMenu(contextMenu)
}
// 从托盘切换 API 配置
function switchApiProfileFromTray(profileName) {
try {
const settings = readSettings()
if (!settings) return
const profiles = settings.apiProfiles || {}
if (!profiles[profileName]) return
// 保存当前配置到 apiProfiles
const currentProfile = settings.currentApiProfile || 'default'
if (profiles[currentProfile]) {
const currentConfig = {}
for (const field of API_FIELDS) {
if (settings[field] !== undefined) {
currentConfig[field] = settings[field]
}
}
profiles[currentProfile] = currentConfig
}
// 从 apiProfiles 加载新配置到主字段
const newConfig = profiles[profileName]
for (const field of API_FIELDS) {
if (newConfig[field] !== undefined) {
settings[field] = newConfig[field]
}
}
settings.currentApiProfile = profileName
settings.apiProfiles = profiles
writeSettings(settings)
updateTrayMenu()
// 通知渲染进程刷新
if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.send('api-profile-switched', profileName)
}
} catch (error) {
console.error('切换API配置失败:', error)
}
}
function createWindow() {
console.log('Creating window...')
mainWindow = new BrowserWindow({
@@ -42,6 +158,7 @@ function createWindow() {
mainWindow.once('ready-to-show', () => {
console.log('Window ready to show')
mainWindow.show()
createTray()
})
mainWindow.on('closed', () => {
mainWindow = null
@@ -49,7 +166,7 @@ function createWindow() {
}
app.whenReady().then(createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
if (process.platform !== 'darwin' && app.isQuitting) {
app.quit()
}
})
@@ -67,7 +184,13 @@ ipcMain.on('window-maximize', () => {
mainWindow.maximize()
}
})
ipcMain.on('window-close', () => mainWindow.close())
ipcMain.on('window-close', () => {
if (!app.isQuitting) {
mainWindow.hide()
} else {
mainWindow.close()
}
})
ipcMain.handle('is-maximized', () => mainWindow.isMaximized())
// API 配置相关的字段
const API_FIELDS = ['selectedAuthType', 'apiKey', 'baseUrl', 'modelName', 'searchApiKey', 'cna']

View File

@@ -5,18 +5,23 @@ contextBridge.exposeInMainWorld('electronAPI', {
loadSettings: () => ipcRenderer.invoke('load-settings'),
saveSettings: (data) => ipcRenderer.invoke('save-settings', data),
showMessage: (options) => ipcRenderer.invoke('show-message', options),
// 窗口控制
isMaximized: () => ipcRenderer.invoke('is-maximized'),
minimize: () => ipcRenderer.send('window-minimize'),
maximize: () => ipcRenderer.send('window-maximize'),
close: () => ipcRenderer.send('window-close'),
// API 配置管理(单文件内多配置)
listApiProfiles: () => ipcRenderer.invoke('list-api-profiles'),
switchApiProfile: (profileName) => ipcRenderer.invoke('switch-api-profile', profileName),
createApiProfile: (name) => ipcRenderer.invoke('create-api-profile', name),
deleteApiProfile: (name) => ipcRenderer.invoke('delete-api-profile', name),
renameApiProfile: (oldName, newName) => ipcRenderer.invoke('rename-api-profile', oldName, newName),
duplicateApiProfile: (sourceName, newName) => ipcRenderer.invoke('duplicate-api-profile', sourceName, newName)
duplicateApiProfile: (sourceName, newName) => ipcRenderer.invoke('duplicate-api-profile', sourceName, newName),
// 托盘事件监听
onApiProfileSwitched: (callback) => {
ipcRenderer.on('api-profile-switched', (event, profileName) => callback(profileName))
}
})

View File

@@ -391,7 +391,7 @@
</div>
</template>
<script setup>
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
import { Save, Config, Key, Server, Globe, Setting, Add, Edit, Delete, Exchange, Copy } from '@icon-park/vue-next'
const settings = ref({
language: 'zh-CN',
@@ -851,6 +851,15 @@ watch(
onMounted(async () => {
await loadApiProfiles()
await loadSettings()
// 监听托盘切换 API 配置事件
window.electronAPI.onApiProfileSwitched(async (profileName) => {
currentApiProfile.value = profileName
await loadSettings()
})
})
onUnmounted(() => {
// 清理监听器
})
</script>
<style>