From fe5d9973981a7991e911092285feff9ea3f1a72e Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Mon, 17 Nov 2025 12:56:53 +0800 Subject: [PATCH] feat(config): add section-based caching and tunable status interval --- app.js | 102 +++++++++++---- src/modules/settings.js | 65 +++++++--- src/utils/dom.js | 279 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 403 insertions(+), 43 deletions(-) create mode 100644 src/utils/dom.js diff --git a/app.js b/app.js index 70b8d16..293b1d1 100644 --- a/app.js +++ b/app.js @@ -24,7 +24,8 @@ import { MIN_AUTH_FILES_PAGE_SIZE, MAX_AUTH_FILES_PAGE_SIZE, OAUTH_CARD_IDS, - STORAGE_KEY_AUTH_FILES_PAGE_SIZE + STORAGE_KEY_AUTH_FILES_PAGE_SIZE, + STATUS_UPDATE_INTERVAL_MS } from './src/utils/constants.js'; // 核心服务导入 @@ -41,9 +42,9 @@ class CLIProxyManager { this.isConnected = false; this.isLoggedIn = false; - // 配置缓存 - this.configCache = null; - this.cacheTimestamp = null; + // 配置缓存 - 改为分段缓存 + this.configCache = {}; // 改为对象,按配置段缓存 + this.cacheTimestamps = {}; // 每个配置段的时间戳 this.cacheExpiry = CACHE_EXPIRY_MS; // 状态更新定时器 @@ -708,37 +709,91 @@ class CLIProxyManager { } // 检查缓存是否有效 - isCacheValid() { - if (!this.configCache || !this.cacheTimestamp) { + isCacheValid(section = null) { + if (section) { + // 检查特定配置段的缓存 + // 注意:配置值可能是 false、0、'' 等 falsy 值,不能用 ! 判断 + if (!(section in this.configCache) || !(section in this.cacheTimestamps)) { + return false; + } + return (Date.now() - this.cacheTimestamps[section]) < this.cacheExpiry; + } + // 检查全局缓存(兼容旧代码) + if (!this.configCache['__full__'] || !this.cacheTimestamps['__full__']) { return false; } - return (Date.now() - this.cacheTimestamp) < this.cacheExpiry; + return (Date.now() - this.cacheTimestamps['__full__']) < this.cacheExpiry; } - // 获取配置(优先使用缓存) - async getConfig(forceRefresh = false) { - if (!forceRefresh && this.isCacheValid()) { - this.updateConnectionStatus(); // 更新状态显示 - return this.configCache; + // 获取配置(优先使用缓存,支持按段获取) + async getConfig(section = null, forceRefresh = false) { + const now = Date.now(); + + // 如果请求特定配置段且该段缓存有效 + if (section && !forceRefresh && this.isCacheValid(section)) { + this.updateConnectionStatus(); + return this.configCache[section]; + } + + // 如果请求全部配置且全局缓存有效 + if (!section && !forceRefresh && this.isCacheValid()) { + this.updateConnectionStatus(); + return this.configCache['__full__']; } try { const config = await this.makeRequest('/config'); - this.configCache = config; - this.cacheTimestamp = Date.now(); - this.updateConnectionStatus(); // 更新状态显示 - return config; + + if (section) { + // 缓存特定配置段 + this.configCache[section] = config[section]; + this.cacheTimestamps[section] = now; + // 同时更新全局缓存中的这一段 + if (this.configCache['__full__']) { + this.configCache['__full__'][section] = config[section]; + } else { + // 如果全局缓存不存在,也创建它 + this.configCache['__full__'] = config; + this.cacheTimestamps['__full__'] = now; + } + this.updateConnectionStatus(); + return config[section]; + } else { + // 缓存全部配置 + this.configCache['__full__'] = config; + this.cacheTimestamps['__full__'] = now; + + // 同时缓存各个配置段 + Object.keys(config).forEach(key => { + this.configCache[key] = config[key]; + this.cacheTimestamps[key] = now; + }); + + this.updateConnectionStatus(); + return config; + } } catch (error) { console.error('获取配置失败:', error); throw error; } } - // 清除缓存 - clearCache() { - this.configCache = null; - this.cacheTimestamp = null; - this.configYamlCache = ''; + // 清除缓存(支持清除特定配置段) + clearCache(section = null) { + if (section) { + // 清除特定配置段的缓存 + delete this.configCache[section]; + delete this.cacheTimestamps[section]; + // 同时清除全局缓存中的这一段 + if (this.configCache['__full__']) { + delete this.configCache['__full__'][section]; + } + } else { + // 清除所有缓存 + this.configCache = {}; + this.cacheTimestamps = {}; + this.configYamlCache = ''; + } } // 启动状态更新定时器 @@ -750,7 +805,7 @@ class CLIProxyManager { if (this.isConnected) { this.updateConnectionStatus(); } - }, 1000); // 每秒更新一次 + }, STATUS_UPDATE_INTERVAL_MS); } // 停止状态更新定时器 @@ -766,7 +821,8 @@ class CLIProxyManager { try { console.log(i18n.t('system_info.real_time_data')); // 使用新的 /config 端点一次性获取所有配置 - const config = await this.getConfig(forceRefresh); + // 注意:getConfig(section, forceRefresh),不传 section 表示获取全部 + const config = await this.getConfig(null, forceRefresh); // 获取一次usage统计数据,供渲染函数和loadUsageStats复用 let usageData = null; diff --git a/src/modules/settings.js b/src/modules/settings.js index 48bf65b..2197cc2 100644 --- a/src/modules/settings.js +++ b/src/modules/settings.js @@ -1,81 +1,106 @@ // 设置与开关相关方法模块 -// 注意:这些函数依赖于在 CLIProxyManager 实例上提供的 makeRequest/clearCache/showNotification 等基础能力 +// 注意:这些函数依赖于在 CLIProxyManager 实例上提供的 makeRequest/clearCache/showNotification/errorHandler 等基础能力 export async function updateDebug(enabled) { + const previousValue = !enabled; try { await this.makeRequest('/debug', { method: 'PUT', body: JSON.stringify({ value: enabled }) }); - this.clearCache(); // 清除缓存 + this.clearCache('debug'); // 仅清除 debug 配置段的缓存 this.showNotification(i18n.t('notification.debug_updated'), 'success'); } catch (error) { - this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error'); - // 恢复原状态 - document.getElementById('debug-toggle').checked = !enabled; + this.errorHandler.handleUpdateError( + error, + i18n.t('settings.debug_mode') || '调试模式', + () => document.getElementById('debug-toggle').checked = previousValue + ); } } export async function updateProxyUrl() { const proxyUrl = document.getElementById('proxy-url').value.trim(); + const previousValue = document.getElementById('proxy-url').getAttribute('data-previous-value') || ''; try { await this.makeRequest('/proxy-url', { method: 'PUT', body: JSON.stringify({ value: proxyUrl }) }); - this.clearCache(); // 清除缓存 + this.clearCache('proxy-url'); // 仅清除 proxy-url 配置段的缓存 + document.getElementById('proxy-url').setAttribute('data-previous-value', proxyUrl); this.showNotification(i18n.t('notification.proxy_updated'), 'success'); } catch (error) { - this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error'); + this.errorHandler.handleUpdateError( + error, + i18n.t('settings.proxy_url') || '代理设置', + () => document.getElementById('proxy-url').value = previousValue + ); } } export async function clearProxyUrl() { + const previousValue = document.getElementById('proxy-url').value; + try { await this.makeRequest('/proxy-url', { method: 'DELETE' }); document.getElementById('proxy-url').value = ''; - this.clearCache(); // 清除缓存 + document.getElementById('proxy-url').setAttribute('data-previous-value', ''); + this.clearCache('proxy-url'); // 仅清除 proxy-url 配置段的缓存 this.showNotification(i18n.t('notification.proxy_cleared'), 'success'); } catch (error) { - this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error'); + this.errorHandler.handleUpdateError( + error, + i18n.t('settings.proxy_url') || '代理设置', + () => document.getElementById('proxy-url').value = previousValue + ); } } export async function updateRequestRetry() { - const retryCount = parseInt(document.getElementById('request-retry').value); + const retryInput = document.getElementById('request-retry'); + const retryCount = parseInt(retryInput.value); + const previousValue = retryInput.getAttribute('data-previous-value') || '0'; try { await this.makeRequest('/request-retry', { method: 'PUT', body: JSON.stringify({ value: retryCount }) }); - this.clearCache(); // 清除缓存 + this.clearCache('request-retry'); // 仅清除 request-retry 配置段的缓存 + retryInput.setAttribute('data-previous-value', retryCount.toString()); this.showNotification(i18n.t('notification.retry_updated'), 'success'); } catch (error) { - this.showNotification(`${i18n.t('notification.update_failed')}: ${error.message}`, 'error'); + this.errorHandler.handleUpdateError( + error, + i18n.t('settings.request_retry') || '重试设置', + () => retryInput.value = previousValue + ); } } export async function loadDebugSettings() { try { - const config = await this.getConfig(); - if (config.debug !== undefined) { - document.getElementById('debug-toggle').checked = config.debug; + const debugValue = await this.getConfig('debug'); // 仅获取 debug 配置段 + if (debugValue !== undefined) { + document.getElementById('debug-toggle').checked = debugValue; } } catch (error) { - console.error('加载调试设置失败:', error); + this.errorHandler.handleLoadError(error, i18n.t('settings.debug_mode') || '调试设置'); } } export async function loadProxySettings() { try { - const config = await this.getConfig(); - if (config['proxy-url'] !== undefined) { - document.getElementById('proxy-url').value = config['proxy-url'] || ''; + const proxyUrl = await this.getConfig('proxy-url'); // 仅获取 proxy-url 配置段 + const proxyInput = document.getElementById('proxy-url'); + if (proxyUrl !== undefined) { + proxyInput.value = proxyUrl || ''; + proxyInput.setAttribute('data-previous-value', proxyUrl || ''); } } catch (error) { - console.error('加载代理设置失败:', error); + this.errorHandler.handleLoadError(error, i18n.t('settings.proxy_settings') || '代理设置'); } } diff --git a/src/utils/dom.js b/src/utils/dom.js new file mode 100644 index 0000000..3fb22b5 --- /dev/null +++ b/src/utils/dom.js @@ -0,0 +1,279 @@ +/** + * DOM 操作工具函数模块 + * 提供高性能的 DOM 操作方法 + */ + +/** + * 批量渲染列表项,使用 DocumentFragment 减少重绘 + * @param {HTMLElement} container - 容器元素 + * @param {Array} items - 数据项数组 + * @param {Function} renderItemFn - 渲染单个项目的函数,返回 HTML 字符串或 Element + * @param {boolean} append - 是否追加模式(默认 false,清空后渲染) + * + * @example + * renderList(container, files, (file) => ` + *