feat: Refactor channel management and UI components

- Removed hardcoded channel data from `channel.ts` and replaced it with a dynamic channel dictionary.
- Introduced a new Pinia store `channel.ts` to manage selected and available channels.
- Reworked `AddChannelDialog.vue` to allow users to search and select channels dynamically.
- Updated `TaskCenter.vue` to utilize the new channel store and handle empty channel selections gracefully.
- Enhanced IPC communication for loading and saving selected channels in the configuration.
- Adjusted `runTaskOperationService.ts` to ensure proper handling of channel data.
- Improved styling and structure of UI components for better user experience.
This commit is contained in:
DEV_DSW
2026-04-16 15:13:30 +08:00
parent 7bd5a1aa20
commit 411f4f3421
15 changed files with 668 additions and 214 deletions

View File

@@ -152,7 +152,9 @@ if (started) {
// logManager.error('unhandledRejection', reason, promise);
// });
app.whenReady().then(() => {
app.whenReady().then(async () => {
await configManager.init();
gatewayManager.init();
onProviderChange(() => {

View File

@@ -205,14 +205,20 @@ export function runTaskOperationService() {
openedTabIndexByChannelName.clear()
if (Array.isArray(channels)) {
for (let i = 0; i < channels.length; i++) {
const name = channels[i]?.channelName
if (name) openedTabIndexByChannelName.set(String(name), i)
}
const validChannels = Array.isArray(channels)
? channels.filter((c) => c && typeof c === 'object' && c.channelUrl)
: []
if (validChannels.length === 0) {
return { success: false, error: '没有可用的渠道配置' }
}
const result = await executeScriptServiceInstance.executeScript(scriptPath, { channels })
for (let i = 0; i < validChannels.length; i++) {
const name = validChannels[i]?.channelName
if (name) openedTabIndexByChannelName.set(String(name), i)
}
const result = await executeScriptServiceInstance.executeScript(scriptPath, { channels: validChannels })
return { success: true, result }
} catch (error) {
return { success: false, error: (error as any)?.message || 'open channel failed' }

View File

@@ -1,9 +1,7 @@
import { app, BrowserWindow, ipcMain } from 'electron'
import { BrowserWindow, ipcMain } from 'electron'
import type { ConfigKeys, IConfig } from '@lib/types'
import { CONFIG_KEYS, IPC_EVENTS } from '@lib/constants'
import { debounce, simpleCloneDeep } from '@lib/utils'
import * as fs from 'fs'
import * as path from 'path'
import { debounce } from '@lib/utils'
import logManager from '@electron/service/logger'
@@ -15,33 +13,47 @@ const DEFAULT_CONFIG: IConfig = {
[CONFIG_KEYS.MINIMIZE_TO_TRAY]: false,
[CONFIG_KEYS.PROVIDER]: '',
[CONFIG_KEYS.DEFAULT_MODEL]: null,
[CONFIG_KEYS.SELECTED_CHANNELS]: [],
}
export class ConfigService {
private static _instance: ConfigService;
private _config: IConfig;
private _configPath: string;
private _store: any;
private _defaultConfig: IConfig = DEFAULT_CONFIG;
private _listeners: Array<(config: IConfig) => void> = [];
private constructor() {
// 获取配置文件路径
this._configPath = path.join(app.getPath('userData'), 'config.json');
// 加载配置
this._config = this._loadConfig();
// 设置 IPC 事件
// store 通过 init() 异步初始化,避免 ESM 模块在 CJS 打包环境中同步导入的问题
}
public async init() {
if (this._store) return;
const { default: Store } = await import('electron-store');
this._store = new Store<IConfig>({
name: 'config',
defaults: DEFAULT_CONFIG,
});
this._setupIpcEvents();
logManager.info('ConfigService initialized successfully.')
}
private _ensureStore() {
if (!this._store) {
throw new Error('ConfigService has not been initialized. Call init() first.');
}
}
private _setupIpcEvents() {
const duration = 200;
const handelUpdate = debounce((val) => this.update(val), duration);
ipcMain.handle(IPC_EVENTS.GET_CONFIG, (_, key) => this.get(key));
ipcMain.on(IPC_EVENTS.SET_CONFIG, (_, key, val) => this.set(key, val));
ipcMain.handle(IPC_EVENTS.SET_CONFIG, (_, key, val) => {
this.set(key, val);
return { success: true };
});
ipcMain.on(IPC_EVENTS.UPDATE_CONFIG, (_, updates) => handelUpdate(updates));
}
@@ -52,66 +64,51 @@ export class ConfigService {
return this._instance;
}
private _loadConfig(): IConfig {
try {
if (fs.existsSync(this._configPath)) {
const configContent = fs.readFileSync(this._configPath, 'utf-8');
const config = { ...this._defaultConfig, ...JSON.parse(configContent) };
logManager.info('Config loaded successfully from:', this._configPath);
return config;
}
} catch (error) {
logManager.error('Failed to load config:', error);
}
return { ...this._defaultConfig };
}
private _saveConfig(): void {
try {
// 确保目录存在
fs.mkdirSync(path.dirname(this._configPath), { recursive: true });
// 写入
fs.writeFileSync(this._configPath, JSON.stringify(this._config, null, 2), 'utf-8');
// 通知监听者
this._notifyListeners();
logManager.info('Config saved successfully to:', this._configPath);
} catch (error) {
logManager.error('Failed to save config:', error);
}
}
private _notifyListeners(): void {
BrowserWindow.getAllWindows().forEach(win => win.webContents.send(IPC_EVENTS.CONFIG_UPDATED, this._config));
this._listeners.forEach(listener => listener({ ...this._config }));
this._ensureStore();
BrowserWindow.getAllWindows().forEach(win => win.webContents.send(IPC_EVENTS.CONFIG_UPDATED, this._store.store));
this._listeners.forEach(listener => listener({ ...this._store.store }));
}
public getConfig(): IConfig {
return simpleCloneDeep(this._config);
if (!this._store) {
return { ...this._defaultConfig };
}
return this._store.store;
}
public get<T = any>(key: ConfigKeys): T {
return this._config[key] as T
if (!this._store) {
return this._defaultConfig[key] as T;
}
return this._store.get(key) as T
}
public set(key: ConfigKeys, value: unknown, autoSave: boolean = true): void {
if (!(key in this._config)) return;
const oldValue = this._config[key];
this._ensureStore();
const oldValue = this._store.get(key);
if (oldValue === value) return;
this._config[key] = value as never;
logManager.debug(`Config set: ${key} = ${value}`);
autoSave && this._saveConfig();
this._store.set(key, value);
logManager.info(`Config set: ${key} = ${JSON.stringify(value)}`);
logManager.info(`Config file path: ${this._store.path}`);
autoSave && this._notifyListeners();
}
public update(updates: Partial<IConfig>, autoSave: boolean = true): void {
this._config = { ...this._config, ...updates };
autoSave && this._saveConfig();
this._ensureStore();
(Object.keys(updates) as ConfigKeys[]).forEach((key) => {
this._store.set(key, updates[key]);
});
autoSave && this._notifyListeners();
}
public resetToDefault(): void {
this._config = { ...this._defaultConfig };
this._ensureStore();
(Object.keys(this._defaultConfig) as ConfigKeys[]).forEach((key) => {
this._store.set(key, this._defaultConfig[key]);
});
logManager.info('Config reset to default.');
this._saveConfig();
this._notifyListeners();
}
public onConfigChange(listener: ((config: IConfig) => void)): () => void {