// src/js/mainPage.js
/**
* [重构] 主页面管理器
* 使用新的核心框架系统
*/
import configManager from './configManager.js';
import uiManager from './uiManager.js';
import i18n from './i18n.js';
import weatherWidget from './weatherWidget.js';
// [重构] 不再使用模态框版本的工具健康检查,已移除导入
import Application from './core/Application.js';
import ToolFramework from './core/ToolFramework.js';
import EventBus from './core/EventBus.js';
import ServiceContainer from './core/ServiceContainer.js';
// 简易 Markdown 解析器
function parseMarkdown(text) {
if (!text) return '';
let html = text
.replace(/\*\*(.*?)\*\*/g, '$1')
.replace(/\*(.*?)\*/g, '$1')
.replace(/`(.*?)`/g, '$1')
.replace(/^- (.*)$/gm, '
.*<\/li>)+)/s, '');
}
return html;
}
class MainPage {
// [重构] 模态框ID常量(统一管理,避免冲突)
static APPEARANCE_FONT_MODAL_ID = 'appearance-font-download-modal';
constructor() {
this.keySequence = '';
this.keyTimeout = null;
this.isDownloadingUpdate = false;
this.dbSettings = {};
this.settingsInterval = null;
this.dashboardInterval = null;
this.activeChartInstance = null;
this.settingsIntervalHeavy = null;
// [重构] 注册到应用核心
Application.registerModule('mainPage', this);
Application.registerModule('uiManager', uiManager);
Application.registerModule('configManager', configManager);
// [重构] 注册服务
ServiceContainer.register('uiManager', uiManager, true);
ServiceContainer.register('configManager', configManager, true);
ServiceContainer.register('toolFramework', ToolFramework, true);
ServiceContainer.register('eventBus', EventBus, true);
// [重构] 注册错误处理器
Application.onError((error, context) => {
if (uiManager && uiManager.showNotification) {
uiManager.showNotification('发生错误', `详情已记录到日志 (${context})`, 'error');
}
});
}
/**
* [重构] 统一初始化入口
*/
async init() {
try {
// 使用新的Application核心初始化
await Application.init();
// 初始化主页面特定功能
await this.initializePage();
// 确保初始化完成后才导航
console.log('[MainPage] 初始化完成,准备导航到首页');
} catch (error) {
console.error('主页面初始化失败:', error);
Application.handleError(error, 'MainPage.init');
// 即使初始化失败,也尝试显示错误页面
const contentArea = document.getElementById('content-area');
if (contentArea) {
contentArea.innerHTML = `
初始化失败
${error.message || '未知错误'}
`;
}
}
}
/**
* [重构] 初始化页面特定功能
*/
async initializePage() {
// [重构] 页面特定初始化(Application已处理核心初始化)
this.dbSettings = configManager.config?.dbSettings || {};
// 初始化天气组件
if (weatherWidget && weatherWidget.init) {
const config = configManager.config || {};
weatherWidget.init(config.initWeatherData || {});
}
// 绑定页面事件
this.bindWindowControls();
this.bindNavigationEvents();
this.bindThemeToggle();
this.addRippleEffectListener();
this.bindGlobalKeyListener();
// [重构] 监听应用事件(已由 Application 核心处理,无需额外绑定)
// this._bindAppEvents(); // 已移除:方法不存在,事件绑定已由 Application 核心处理
// 全局点击监听器:处理所有自定义下拉菜单的关闭逻辑
document.addEventListener('click', (e) => {
document.querySelectorAll('.custom-select-options.dynamic-active').forEach(openDropdown => {
const wrapperId = openDropdown.id.replace('dd-options-', 'dd-wrapper-');
const trigger = document.getElementById(wrapperId);
if (openDropdown && !openDropdown.contains(e.target) && (!trigger || !trigger.contains(e.target))) {
openDropdown.classList.remove('dynamic-active');
}
});
});
window.addEventListener('error', (event) => {
const error = event.error || {};
configManager.logAction(`渲染进程发生错误: ${error.message || 'Unknown'}\nStack: ${error.stack || ''}`, 'error');
uiManager.showNotification('发生内部错误', '详情已记录到日志中', 'error');
});
// [关键修复] 使用 requestAnimationFrame 确保在下一帧(DOM完全就绪)后导航
requestAnimationFrame(() => {
try {
this.navigateTo('home').catch(error => {
console.error('[MainPage] 导航到首页失败:', error);
Application.handleError(error, 'MainPage.init.navigateTo');
});
const homeBtn = document.getElementById('home-btn');
if (homeBtn) {
homeBtn.classList.add('active');
setTimeout(() => this.updateActiveNavButton(homeBtn), 100);
}
} catch (error) {
console.error('[MainPage] 初始化导航失败:', error);
Application.handleError(error, 'MainPage.init');
}
});
// 监听下载进度 (复用逻辑)
this.listenForDownloadProgress();
this.listenForGlobalNetworkSpeed();
this.autoCheckUpdates();
// [重构] 监听页面切换,如果健康检查正在进行,切换到后台模式
this._setupHealthCheckBackgroundMode();
configManager.logAction('主窗口初始化完成', 'system');
}
/**
* [重构] 设置健康检查后台模式
* 当用户切换页面时,如果健康检查正在进行,切换到后台模式
*/
_setupHealthCheckBackgroundMode() {
// 监听导航事件
const originalNavigateTo = this.navigateTo.bind(this);
this.navigateTo = async (page, subPage) => {
// 如果从工具箱切换到其他页面,且健康检查正在进行
if (window.toolboxVerificationPage && window.toolboxVerificationPage.isChecking) {
// 切换到后台模式
await window.toolboxVerificationPage.continueCheckInBackground();
}
// 执行原始导航
return originalNavigateTo(page, subPage);
};
// 监听窗口关闭事件
window.addEventListener('beforeunload', () => {
if (window.toolboxVerificationPage && window.toolboxVerificationPage.isChecking) {
// 窗口关闭时,健康检查会在后台继续(如果可能)
// 注意:实际上窗口关闭后无法继续,但可以保存当前进度
if (window.toolHealthChecker) {
// 保存当前检查进度到数据库
const progress = window.toolHealthChecker.getProgress();
if (progress && progress.checked > 0) {
// 可以在这里保存部分结果
console.log('[MainPage] 窗口关闭,保存健康检查进度:', progress);
}
}
}
});
}
async loadLanguage() {
try {
const langConfig = await window.electronAPI.getLanguageConfig();
if(langConfig) {
i18n.init(langConfig.pack, langConfig.fallback, langConfig.actual || langConfig.current);
}
} catch (e) {
console.error("无法加载语言配置:", e);
i18n.init(null, {}, 'zh-CN');
}
}
async autoCheckUpdates() {
const lastCheck = localStorage.getItem('last_auto_update_check');
const now = Date.now();
const threeDays = 3 * 24 * 60 * 60 * 1000;
if (!lastCheck || (now - parseInt(lastCheck)) > threeDays) {
console.log('[Update] 开始后台自动检查更新...');
try {
const result = await window.electronAPI.checkUpdates();
localStorage.setItem('last_auto_update_check', now.toString());
if (result.hasUpdate) {
configManager.logAction(`自动检测到新版本: ${result.remoteVersion}`, 'update');
uiManager.showActionableNotification(
'发现新版本',
`V${result.remoteVersion} 现已发布,包含多项优化。`,
'去更新',
() => {
this.navigateToSettingsForUpdate();
}
);
}
} catch (e) {
console.warn('[Update] 自动检查更新失败:', e.message);
}
}
}
bindGlobalKeyListener() {
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
clearTimeout(this.keyTimeout);
this.keySequence += e.key.toLowerCase();
if (this.keySequence.length > 5) {
this.keySequence = this.keySequence.slice(-5);
}
if (this.keySequence.includes('vv')) {
const aboutPanel = document.getElementById('about-panel');
if (aboutPanel && aboutPanel.classList.contains('active')) {
this.handleSecretTrigger(true);
this.keySequence = '';
}
}
this.keyTimeout = setTimeout(() => {
this.keySequence = '';
}, 1500);
});
}
async handleSecretTrigger(skipValidation = false) {
const access = await window.electronAPI.checkSecretAccess();
const showLockoutMessage = (until) => {
const timeLeftMs = until - Date.now();
const minutes = Math.ceil(timeLeftMs / 60000);
const message = access.status === 'ip-banned'
? `由于安全策略,您的IP已被临时限制访问此功能,请在 ${minutes} 分钟后重试。`
: `暗号尝试次数过多,该功能已锁定。请在 ${minutes} 分钟后重试。`;
uiManager.showNotification('访问受限', message, 'error');
};
if (access.status === 'ip-banned' || access.status === 'locked') {
showLockoutMessage(access.until);
return;
}
const onValidationSuccess = () => {
configManager.logAction('触发了神秘档案馆彩蛋', 'general');
window.electronAPI.resetSecretAttempts();
const currentTheme = document.body.getAttribute('data-theme') || 'dark';
uiManager.showActionableNotification(
'暗号正确!',
'一个通往过去的入口已为你敞开。',
'进入档案馆',
() => { window.electronAPI.openSecretWindow(currentTheme); }
);
};
if (skipValidation) {
onValidationSuccess();
return;
}
uiManager.showPrompt({
title: '秘密入口',
label: '请输入暗号以继续…',
placeholder: '注意大小写哦~',
}, async (inputValue) => {
if (inputValue && inputValue.toLowerCase() === 'vv') {
onValidationSuccess();
} else if (inputValue !== null) {
const result = await window.electronAPI.recordSecretFailure();
configManager.logAction(`彩蛋暗号尝试失败,剩余次数: ${result.attemptsLeft}`, 'general');
if (result.isLocked) {
showLockoutMessage(result.lockoutUntil);
} else {
uiManager.showNotification('暗号错误', `访问被拒绝。你还有 ${result.attemptsLeft} 次机会。`, 'error');
}
}
});
}
bindWindowControls() {
document.getElementById('minimize-btn').addEventListener('click', () => window.electronAPI.minimizeWindow());
document.getElementById('maximize-btn').addEventListener('click', () => window.electronAPI.maximizeWindow());
document.getElementById('close-btn').addEventListener('click', () => window.electronAPI.closeWindow());
}
bindNavigationEvents() {
document.getElementById('home-btn').title = i18n.t('nav.home');
document.getElementById('toolbox-btn').title = i18n.t('nav.toolbox');
document.getElementById('log-btn').title = i18n.t('nav.logs');
document.getElementById('settings-btn').title = i18n.t('nav.settings');
const navActions = {
'home-btn': () => this.navigateTo('home').catch(error => {
console.error('[MainPage] 导航到首页失败:', error);
Application.handleError(error, 'MainPage.navActions.home');
}),
'toolbox-btn': () => this.navigateTo('toolbox').catch(error => {
console.error('[MainPage] 导航到工具箱失败:', error);
Application.handleError(error, 'MainPage.navActions.toolbox');
}),
'log-btn': () => this.navigateTo('logs').catch(error => {
console.error('[MainPage] 导航到日志失败:', error);
Application.handleError(error, 'MainPage.navActions.logs');
}),
'settings-btn': () => this.navigateTo('settings').catch(error => {
console.error('[MainPage] 导航到设置失败:', error);
Application.handleError(error, 'MainPage.navActions.settings');
}),
};
for (const [id, action] of Object.entries(navActions)) {
const btn = document.getElementById(id);
if (btn) {
btn.addEventListener('click', (e) => {
try {
action();
this.updateActiveNavButton(e.currentTarget);
} catch (error) {
console.error(`[MainPage] 导航按钮 ${id} 点击处理失败:`, error);
Application.handleError(error, `MainPage.navActions.${id}`);
}
});
} else {
console.warn(`[MainPage] 导航按钮 ${id} 不存在`);
}
}
}
/**
* [修复] 导航方法 - 直接使用方法引用,避免 methodMap 属性被压缩
* 直接使用 this.methodName.bind(this),确保方法名在保留列表中
*/
async navigateTo(page, subPage = null) {
// [修复] 直接使用方法引用,避免 methodMap 属性被压缩导致的问题
// 这些方法名已在构建脚本的保留列表中,不会被压缩
const renderWelcomePage = this.renderWelcomePage?.bind(this);
const renderToolHealthCheckPage = this.renderToolHealthCheckPage?.bind(this);
const renderLogsPage = this.renderLogsPage?.bind(this);
const renderSettingsPage = this.renderSettingsPage?.bind(this);
const updateActiveNavButton = this.updateActiveNavButton?.bind(this);
// [修复] 检查方法是否存在
if (!renderWelcomePage || !renderLogsPage || !renderSettingsPage) {
console.error('[MainPage] 页面渲染方法未定义');
Application.handleError(new Error('页面渲染方法未定义'), 'MainPage.navigateTo');
return;
}
if (this.settingsInterval) { clearInterval(this.settingsInterval); this.settingsInterval = null; }
if (this.settingsIntervalHeavy) { clearInterval(this.settingsIntervalHeavy); this.settingsIntervalHeavy = null; }
if (this.dashboardInterval) { clearInterval(this.dashboardInterval); this.dashboardInterval = null; }
if (this.activeChartInstance) { this.activeChartInstance.destroy(); this.activeChartInstance = null; }
document.getElementById('update-slide-out-panel')?.remove();
// [修复] 关闭并移除公告模态框(如果打开的话)
// [重构] 使用通用模态框框架关闭公告模态框
import('./ui/ModalManager.js').then(({ default: modalManager }) => {
if (modalManager.isOpen('announcement-modal')) {
modalManager.close('announcement-modal');
}
}).catch(() => {
// 如果 ModalManager 未加载,忽略
});
document.getElementById('update-panel-overlay')?.remove();
document.getElementById('dd-options-lang-select-wrapper')?.remove();
// [重构] 清理字体下载相关元素(使用通用模态框框架)
const APPEARANCE_FONT_SELECT_OPTIONS_ID_STR = 'appearance-font-select-options';
document.getElementById(APPEARANCE_FONT_SELECT_OPTIONS_ID_STR)?.remove();
// [重构] 使用通用模态框框架关闭字体下载模态框
import('./ui/ModalManager.js').then(({ default: modalManager }) => {
if (modalManager.isOpen(MainPage.APPEARANCE_FONT_MODAL_ID)) {
modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID);
}
}).catch(() => {
// 如果 ModalManager 未加载,忽略
});
uiManager.unloadActiveTool();
configManager.logAction(`导航到: ${page}`, 'navigation');
const contentArea = document.getElementById('content-area');
if (!contentArea) {
console.error('[MainPage] content-area 元素不存在');
Application.handleError(new Error('content-area 元素不存在'), 'MainPage.navigateTo');
return;
}
if (contentArea.children.length > 0) {
contentArea.classList.add('page-exit');
await new Promise(r => setTimeout(r, 150));
contentArea.classList.remove('page-exit');
}
switch (page) {
case 'home':
if (renderWelcomePage) {
await renderWelcomePage();
} else {
console.error('[MainPage] renderWelcomePage 方法不存在');
}
break;
case 'toolbox':
// 检查今天是否自检过
// 确保工具健康检查模块已加载
if (!window.toolHealthChecker) {
// 如果模块未加载,等待加载完成(最多等待3秒)
let retries = 0;
const maxRetries = 30; // 30 * 100ms = 3秒
while (!window.toolHealthChecker && retries < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 100));
retries++;
}
}
// 检查今天是否已经自检过
let needCheck = true;
if (window.toolHealthChecker) {
// 检查今天是否已经检查过(不修改日期)
const today = new Date().toDateString();
const savedDate = configManager.config?.tool_health_last_check || null;
if (savedDate === today) {
needCheck = false; // 今天已经检查过
}
}
if (needCheck) {
// 没有自检过,直接跳转到工具箱,健康检查在后台进行
// 确保模态框模块已加载
// [重构] 不再自动显示模态框,健康检查由工具箱验证页面处理
// 如果需要后台检查,可以直接调用 toolHealthChecker.checkAllTools(true, true)
}
// 直接跳转工具箱界面(会先显示验证界面)
await uiManager.renderToolboxPage();
// 确保导航按钮状态正确更新
const toolboxBtn = document.getElementById('toolbox-btn');
if (toolboxBtn) {
updateActiveNavButton(toolboxBtn);
}
break;
case 'logs':
if (renderLogsPage) {
renderLogsPage(new Date().toISOString().split('T')[0]);
} else {
console.error('[MainPage] renderLogsPage 方法不存在');
}
break;
case 'settings':
if (renderSettingsPage) {
await renderSettingsPage();
} else {
console.error('[MainPage] renderSettingsPage 方法不存在');
}
if (subPage) {
setTimeout(() => {
const targetNavItem = document.querySelector(`.island-nav-item[data-panel="${subPage}"]`);
if (targetNavItem) targetNavItem.click();
}, 100);
}
break;
}
contentArea.classList.add('page-enter');
setTimeout(() => {
contentArea.classList.remove('page-enter');
}, 200);
}
updateActiveNavButton(activeButton) {
if (!activeButton) return;
document.querySelectorAll('.nav-btn').forEach(btn => btn.classList.remove('active'));
activeButton.classList.add('active');
const slider = document.getElementById('nav-active-slider');
if (slider) {
const targetTop = activeButton.offsetTop;
const targetHeight = activeButton.offsetHeight;
slider.style.top = `${targetTop}px`;
slider.style.height = `${targetHeight}px`;
}
}
/**
* [新增] 渲染工具健康检查页面(如果需要)
* 这个方法用于在工具箱页面显示健康检查状态
*/
renderToolHealthCheckPage() {
// 这个方法实际上不需要单独渲染页面
// 健康检查在后台进行,工具箱页面正常显示
// 如果需要在页面中显示健康检查状态,可以在工具箱页面中添加
return Promise.resolve();
}
async renderWelcomePage() {
const toolStatus = configManager.config?.tool_status || {};
const searchToolStatus = toolStatus['smart-search'] || { enabled: true };
const isSmartSearchEnabled = searchToolStatus.enabled;
const searchDisabledMessage = isSmartSearchEnabled ? "" : (searchToolStatus.message || i18n.t('home.search.disabled'));
const contentArea = document.getElementById('content-area');
const appVersion = await window.electronAPI.getAppVersion();
// [修复] 获取公告内容,如果为空则不显示公告卡片
const fullAnnouncement = configManager.config?.home_notes || '';
const stripHtml = (html) => {
if (!html) return '';
let tmp = document.createElement("DIV");
tmp.innerHTML = html;
return tmp.textContent || tmp.innerText || "";
};
const plainText = stripHtml(fullAnnouncement);
const hasAnnouncement = plainText && plainText.trim().length > 0;
const truncatedText = plainText.length > 50 ? plainText.substring(0, 50) + '...' : plainText;
const newNotes = configManager.config?.update_notes || {};
const getGreeting = () => {
const hour = new Date().getHours();
if (hour < 6) return i18n.t('home.greeting.night');
if (hour < 12) return i18n.t('home.greeting.morning');
if (hour < 14) return i18n.t('home.greeting.noon');
if (hour < 18) return i18n.t('home.greeting.afternoon');
return i18n.t('home.greeting.evening');
};
const createNotesHtml = (notes) => {
const entries = Object.entries(notes);
if (entries.length === 0) return `${i18n.t('home.updates.empty', null, '暂无更新详情。')}
`;
return entries.map(([key, value]) => `
${key}
${parseMarkdown(value)}
`).join('');
};
contentArea.innerHTML = `
${hasAnnouncement ? `
${i18n.t('home.announcement.viewFull')}
` : ''}
${!isSmartSearchEnabled ? `
${i18n.t('tool.smartSearch.name')} 已禁用
${searchDisabledMessage}
` : ''}
v${appVersion}
`;
// [修复] 根据是否有公告内容来决定淡入的元素
const fadeInSelectors = hasAnnouncement
? '.welcome-header, .compact-announcement, .welcome-search-bar'
: '.welcome-header, .welcome-search-bar';
uiManager.fadeInContent(fadeInSelectors);
// 更新日志面板
if (!document.getElementById('update-slide-out-panel')) {
const panelHtml = `
${createNotesHtml(newNotes)}
`;
document.body.insertAdjacentHTML('beforeend', panelHtml);
}
// [修复] 公告弹窗(仅在需要时创建,并确保默认隐藏)
// [重构] 公告模态框已由通用模态框框架动态创建,无需在HTML中预定义
// 遮罩层
if (!document.getElementById('update-panel-overlay')) {
const overlayHtml = ``;
document.body.insertAdjacentHTML('beforeend', overlayHtml);
}
if (this.dashboardInterval) { clearInterval(this.dashboardInterval); this.dashboardInterval = null; }
const searchInput = document.getElementById('welcome-search-input');
const searchBtn = document.getElementById('welcome-search-btn');
const updatePanel = document.getElementById('update-slide-out-panel');
const showUpdateBtn = document.getElementById('show-update-log-btn');
const closeUpdateBtn = document.getElementById('close-update-panel-btn');
// [修复] 仅在存在公告内容时获取相关元素
const announcementCard = hasAnnouncement ? document.getElementById('compact-announcement-card') : null;
// [重构] 公告模态框已由通用模态框框架动态创建,无需获取DOM元素
const updateOverlay = document.getElementById('update-panel-overlay');
if (isSmartSearchEnabled) {
const onSearch = () => {
const query = searchInput.value.trim();
if (query) { this._performWelcomeSearch(query); }
else { uiManager.showNotification(i18n.t('common.notification.title.info'), i18n.t('home.search.placeholder'), 'info'); }
};
searchBtn?.addEventListener('click', onSearch);
searchInput?.addEventListener('keydown', (e) => { if (e.key === 'Enter') onSearch(); });
}
const closeUpdatePanel = () => {
updatePanel?.classList.remove('active');
updateOverlay?.classList.remove('active');
};
const openUpdatePanel = () => {
updatePanel?.classList.add('active');
updateOverlay?.classList.add('active');
};
showUpdateBtn?.addEventListener('click', () => {
if (updatePanel.classList.contains('active')) {
closeUpdatePanel();
} else {
openUpdatePanel();
}
});
closeUpdateBtn?.addEventListener('click', closeUpdatePanel);
updateOverlay?.addEventListener('click', closeUpdatePanel);
// [重构] 绑定公告模态框事件(使用通用模态框框架)
if (hasAnnouncement && announcementCard && fullAnnouncement && fullAnnouncement.trim()) {
announcementCard.addEventListener('click', async () => {
// 动态导入 ModalManager
const { default: modalManager } = await import('./ui/ModalManager.js');
// 使用通用模态框框架创建公告模态框
modalManager.create({
id: 'announcement-modal',
title: ` ${i18n.t('home.updates.modal.title', '完整公告')}`,
content: `${fullAnnouncement}
`,
size: 'medium',
closable: true,
closeOnBackdrop: true,
className: 'announcement-modal-wrapper'
});
});
}
}
async _performWelcomeSearch(query) {
const resultsContainer = document.getElementById('welcome-search-results-container');
if (!resultsContainer) return;
resultsContainer.innerHTML = `
${i18n.t('common.loading')}...
`;
const apiKey = configManager.config?.api_keys?.uapipro || null;
const requestBody = { query: query, timeout_ms: 10000, fetch_full: false };
const requestHeaders = { 'Content-Type': 'application/json' };
if (apiKey) { requestHeaders['Authorization'] = `Bearer ${apiKey}`; }
try {
const response = await fetch('https://uapis.cn/api/v1/search/aggregate', {
method: 'POST',
headers: requestHeaders,
body: JSON.stringify(requestBody)
});
const data = await response.json();
if (!response.ok) throw new Error(data.message || `HTTP 错误 ${response.status}`);
configManager.logAction(`[主页搜索] 成功: ${query}`, 'tool');
this._renderWelcomeResults(data, query);
} catch (error) {
configManager.logAction(`[主页搜索] 失败: ${error.message}`, 'error');
resultsContainer.innerHTML = `
${i18n.t('home.search.failed.title')}
${error.message}
`;
}
}
_renderWelcomeResults(data, query) {
const resultsContainer = document.getElementById('welcome-search-results-container');
if (!resultsContainer) return;
const results = data.results || [];
if (results.length === 0) {
resultsContainer.innerHTML = `
${i18n.t('home.search.results.empty.title', { query: query })}
${i18n.t('home.search.results.empty.sub')}
`;
return;
}
const formatTime = (isoString) => {
if (!isoString) return '';
try { return new Date(isoString).toLocaleDateString(); } catch (e) { return ''; }
};
const resultsHtml = results.map(item => `
${item.snippet}
${item.domain}
${item.author || '未知作者'}
${formatTime(item.publish_time) || '未知时间'}
AI得分: ${item.score.toFixed(3)}
`).join('');
const statsHtml = `
${i18n.t('home.search.results.stats', { count: data.total_results || 0, time: data.process_time_ms })}
`;
const detailsButtonHtml = `
`;
resultsContainer.innerHTML = statsHtml + resultsHtml + detailsButtonHtml;
resultsContainer.querySelectorAll('a[data-url]').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
window.electronAPI.openExternalLink(e.currentTarget.dataset.url);
});
});
resultsContainer.querySelector('#view-full-results-btn')?.addEventListener('click', () => {
configManager.logAction(`[主页搜索] 跳转到工具: ${query}`, 'navigation');
// 1. 切换 Tab
this.navigateTo('toolbox');
this.updateActiveNavButton(document.getElementById('toolbox-btn'));
// 2. 启动工具 (同步操作,因为 launchModularTool 是同步替换 DOM)
uiManager.launchModularTool('smart-search');
// 3. 延时填充数据,确保 DOM 渲染完成
setTimeout(() => {
const toolInput = document.getElementById('ss-query-input');
const toolSearchBtn = document.getElementById('ss-search-btn');
if (toolInput && toolSearchBtn) {
toolInput.value = query;
toolSearchBtn.click(); // 自动触发搜索
} else {
console.warn('[SmartSearch] 自动填充失败:未找到工具 DOM 元素');
}
}, 300); // 增加延时到 300ms
});
}
renderLogsPage(filterDate = new Date().toISOString().split('T')[0]) {
const contentArea = document.getElementById('content-area');
const todayString = new Date().toISOString().split('T')[0];
contentArea.innerHTML = `
${i18n.t('common.loading')}...
`;
this.bindLogPageEvents();
this._fetchAndRenderLogs(filterDate);
}
async _fetchAndRenderLogs(filterDate) {
const dateQuery = filterDate === 'all' ? null : filterDate;
let logs;
const logsContainer = document.getElementById('logs-content-container');
try {
logs = await window.electronAPI.getLogs(dateQuery);
} catch (error) {
if (logsContainer) {
logsContainer.innerHTML = `
`;
}
return;
}
const groupedLogs = logs.reduce((acc, log) => {
const date = new Date(log.timestamp).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-');
if (!acc[date]) acc[date] = [];
acc[date].push(log);
return acc;
}, {});
const renderLogEntries = (grouped) => {
if (Object.keys(grouped).length === 0) {
return `
`;
}
let html = '';
const categoryMap = {
system: { icon: 'fa-server', class: 'log-tag-system' },
navigation: { icon: 'fa-location-arrow',class: 'log-tag-nav' },
ui: { icon: 'fa-swatchbook', class: 'log-tag-ui' },
tool: { icon: 'fa-microchip', class: 'log-tag-tool' },
update: { icon: 'fa-rocket', class: 'log-tag-update' },
settings: { icon: 'fa-sliders-h', class: 'log-tag-settings' },
error: { icon: 'fa-bomb', class: 'log-tag-error' },
general: { icon: 'fa-info-circle', class: 'log-tag-general' }
};
for (const date in grouped) {
const dayOfWeek = new Date(grouped[date][0].timestamp).toLocaleDateString('zh-CN', { weekday: 'long' });
html += ``;
html += `
${date} · ${dayOfWeek}
`;
html += grouped[date].map(log => {
const catInfo = categoryMap[log.category] || categoryMap.general;
const time = new Date(log.timestamp).toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
let mainAction = log.action;
let subTagHtml = '';
const subCategoryMatch = log.action.match(/^\[(.*?)\]\s(.*)$/);
if (subCategoryMatch) {
const subCategoryName = subCategoryMatch[1];
mainAction = subCategoryMatch[2];
subTagHtml = `
${subCategoryName}`;
}
// 使用 data 属性存储要复制的内容,避免内联 onclick 被混淆器破坏
const logId = `log-${log.timestamp}-${Math.random().toString(36).substr(2, 9)}`;
return `
${time}
${subTagHtml}${mainAction}
`;
}).join('');
html += `
`;
}
return html;
};
if (logsContainer) {
logsContainer.innerHTML = renderLogEntries(groupedLogs);
const cards = logsContainer.querySelectorAll('.log-card-island');
cards.forEach((card, i) => {
card.style.opacity = '0';
card.style.transform = 'translateY(10px)';
card.style.animation = `contentFadeIn 0.4s ease forwards ${i * 0.03}s`;
});
// 绑定日志复制按钮事件(避免内联 onclick 被混淆器破坏)
logsContainer.querySelectorAll('.log-copy-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const copyText = btn.getAttribute('data-copy-text');
if (copyText) {
try {
await navigator.clipboard.writeText(copyText);
// 可以添加一个短暂的视觉反馈
const icon = btn.querySelector('i');
if (icon) {
const originalClass = icon.className;
icon.className = 'fas fa-check';
setTimeout(() => {
icon.className = originalClass;
}, 1000);
}
} catch (err) {
console.error('复制失败:', err);
}
}
});
});
}
}
bindLogPageEvents() {
const datePicker = document.getElementById('log-date-picker');
const logsContainer = document.getElementById('logs-content-container');
const loadLogs = (filterDate) => {
if (logsContainer) {
logsContainer.innerHTML = `
${i18n.t('common.loading')}...
`;
}
this._fetchAndRenderLogs(filterDate);
};
datePicker.addEventListener('change', (e) => { loadLogs(e.target.value); });
document.getElementById('show-today-logs').addEventListener('click', () => {
const todayString = new Date().toISOString().split('T')[0];
datePicker.value = todayString;
loadLogs(todayString);
});
document.getElementById('show-all-logs').addEventListener('click', () => { loadLogs('all'); });
document.getElementById('clear-logs')?.addEventListener('click', async () => {
if (confirm('确定要清空所有日志记录吗?此操作不可逆!')) {
await window.electronAPI.clearLogs();
uiManager.showNotification(i18n.t('common.notification.title.success'), '所有日志已被清空。');
loadLogs(datePicker.value || 'all');
}
});
}
async renderSettingsPage() {
const contentArea = document.getElementById('content-area');
const [appVersion, traffic, langConfig] = await Promise.all([
window.electronAPI.getAppVersion(),
window.electronAPI.getTrafficStats(),
window.electronAPI.getLanguageConfig()
]);
const configVersion = configManager.config?.config_version || 'N/A';
const langOptions = [
{ key: 'auto', label: i18n.t('settings.appearance.language.auto') },
{ key: 'zh-CN', label: i18n.t('settings.appearance.language.zh-CN') },
{ key: 'en-US', label: i18n.t('settings.appearance.language.en-US') }
];
const langSelectHtml = `
${langOptions.find(o => o.key === langConfig.current)?.label || 'N/A'}
`;
const langOptionsHtml = `
${langOptions.map(opt => `
${opt.label}
`).join('')}
`;
const availableFonts = [
{ name: 'Default', label: '系统默认 (Default)', url: '' },
{ name: 'MeiGanShouXieTi', label: '梅干手写体', url: 'https://update.ymhut.cn/fonts/MeiGanShouXieTi-2.ttf' },
{ name: 'QianTuBiFeng', label: '千图笔锋手写体', url: 'https://update.ymhut.cn/fonts/QianTuBiFengShouXieTi-2.ttf' },
{ name: 'YOzBS', label: 'YOz手写体', url: 'https://update.ymhut.cn/fonts/YOzBS-2.otf' }
];
const currentFontName = this.dbSettings.customFontName || 'Default';
// [重构] 字体下载相关常量定义(统一管理,避免冲突)
// 注意:APPEARANCE_FONT_MODAL_ID 已在类级别定义为静态常量
const APPEARANCE_FONT_STATUS_ID = 'appearance-font-download-status';
const APPEARANCE_FONT_PROGRESS_BAR_ID = 'appearance-font-download-bar';
const APPEARANCE_FONT_PERCENT_ID = 'appearance-font-download-percent';
const APPEARANCE_FONT_SELECT_WRAPPER_ID = 'appearance-font-select-wrapper';
const APPEARANCE_FONT_SELECT_OPTIONS_ID = 'appearance-font-select-options';
const APPEARANCE_FONT_SELECT_VALUE_ID = 'appearance-font-select-value';
const APPEARANCE_DEFAULT_FONT_VALUE = 'Default';
const APPEARANCE_FONT_CONFIG_TYPE = 'appearance_config_font';
const fontSelectHtml = `
${availableFonts.find(f => f.name === currentFontName)?.label || '系统默认'}
`;
const fontOptionsHtml = `
${availableFonts.map(f => `
${f.label}
`).join('')}
`;
// [重构] 字体下载模态框已由通用模态框框架动态创建,无需在HTML中预定义
contentArea.innerHTML = `
${i18n.t('settings.appearance.title', null, '个性化')}
${i18n.t('settings.appearance.language', null, '界面语言')}${langSelectHtml}
${i18n.t('settings.appearance.font', null, 'UI 字体')}${fontSelectHtml}
${i18n.t('settings.traffic.title', null, '网络监控')}
${i18n.t('settings.traffic.total', null, '累计消耗')}
${configManager.formatBytes(traffic)}
YMHUT
${i18n.t('settings.about.motto', null, '功不唐捐,玉汝于成')}
${i18n.t('settings.about.title', null, '关于与软件环境')}
${i18n.t('settings.about.version', null, '客户端版本')}v${appVersion}
${i18n.t('settings.about.configVersion', null, '配置版本')}${configVersion}
${langOptionsHtml}
${fontOptionsHtml}
`;
const langOptionsEl = document.getElementById('dd-options-lang-select-wrapper');
if (langOptionsEl) document.body.appendChild(langOptionsEl);
// [修复] 使用已在方法开头定义的常量(不重复声明)
const fontOptionsEl = document.getElementById(APPEARANCE_FONT_SELECT_OPTIONS_ID);
if (fontOptionsEl) document.body.appendChild(fontOptionsEl);
// [重构] 字体下载模态框已由通用模态框框架动态创建,无需在HTML中预定义
// 字体下载模态框将在用户选择字体时动态创建
this.bindSettingsPageEvents();
// [修复] 延时渲染,确保 DOM 插入完成
setTimeout(() => {
this.renderTrafficChart();
this.renderUpdateUI('idle', { currentVersion: appVersion });
}, 150);
}
// --- [核心修改区] 更新管理逻辑重构 ---
// 1. 检查更新入口
async checkForUpdates(manual = false) {
if (this.isDownloadingUpdate) return;
// 如果是手动检查,显示 Loading 状态
if (manual) {
this.renderUpdateUI('checking');
}
try {
const result = await window.electronAPI.checkUpdates();
// 模拟一点延时让动画不至于一闪而过
setTimeout(() => {
if (result.hasUpdate) {
this.renderUpdateUI('available', result);
} else {
if (manual) {
this.renderUpdateUI('latest', { currentVersion: result.currentVersion });
}
}
}, 800);
} catch (error) {
if (manual) {
uiManager.showNotification('检查失败', error.message, 'error');
// 恢复到当前版本显示
const ver = configManager.config.app_version || 'Unknown';
this.renderUpdateUI('idle', { currentVersion: ver });
}
}
}
// 2. 渲染更新 UI (多状态机)
renderUpdateUI(state, data = {}) {
const container = document.getElementById('update-state-container');
if (!container) return;
let html = '';
const containerClass = `update-status-container update-state-${state}`;
switch (state) {
case 'idle':
html = `
更新管理
当前版本: v${data.currentVersion}
点击下方按钮检查是否有新版本发布。
`;
break;
case 'checking':
html = `
正在检查...
正在连接服务器获取最新版本信息,请稍候。
`;
break;
case 'latest':
html = `
已是最新版本
您当前运行的是 v${data.currentVersion}。
系统运行在最佳状态。
`;
break;
case 'available':
// [新增] 多线路下载 UI 渲染逻辑
const notes = Object.entries(data.updateNotes || {}).map(([k, v]) => `${k}
${parseMarkdown(v)}`).join('
');
// 构建镜像列表 HTML
let mirrorsHtml = '';
const mirrors = data.downloadMirrors || [];
// 如果有配置镜像列表,显示选择器
if (mirrors.length > 0) {
mirrorsHtml = `
选择下载线路:
${mirrors.filter(m => m.enabled !== false).map(m => {
const icon = m.type === 'web_redirect' ? 'fa-external-link-alt' : 'fa-cloud-download-alt';
const tagClass = m.type === 'web_redirect' ? 'web' : 'direct';
const tagName = m.type === 'web_redirect' ? '网页' : '直连';
return `
`;
}).join('')}
`;
} else {
// 兼容旧逻辑:默认按钮
mirrorsHtml = `
`;
}
html = `
发现新版本 v${data.remoteVersion}
${notes || '暂无更新日志'}
${mirrorsHtml}
`;
break;
case 'downloading':
html = `
`;
break;
}
container.innerHTML = html;
// 重新绑定事件
if (state === 'idle' || state === 'latest') {
const btn = document.getElementById(state === 'idle' ? 'update-check-btn' : 'update-recheck-btn');
btn?.addEventListener('click', () => this.checkForUpdates(true));
}
else if (state === 'available') {
// 绑定镜像按钮点击
const mirrorBtns = container.querySelectorAll('.mirror-btn');
if (mirrorBtns.length > 0) {
mirrorBtns.forEach(btn => {
btn.addEventListener('click', () => {
this._handleDownloadAction(btn.dataset.url, btn.dataset.type, data);
});
});
} else {
// 兼容旧按钮
document.getElementById('update-dl-pkg-btn')?.addEventListener('click', (e) => this.startUpdateDownload(e.target.dataset.url, data, false));
document.getElementById('update-install-btn')?.addEventListener('click', (e) => this.startUpdateDownload(e.target.dataset.url, data, true));
}
}
else if (state === 'downloading') {
document.getElementById('update-cancel-btn')?.addEventListener('click', () => window.electronAPI.cancelDownload());
}
}
// 3. 处理下载动作分流
_handleDownloadAction(url, type, updateInfo) {
if (type === 'web_redirect') {
uiManager.showNotification('提示', '正在打开下载页面,请在弹出的窗口中手动完成下载。', 'info');
if (window.electronAPI.openDownloadWindow) {
window.electronAPI.openDownloadWindow(url);
} else {
window.electronAPI.openExternalLink(url);
}
} else {
// 直连下载,默认下载后询问安装
this.startUpdateDownload(url, updateInfo, true);
}
}
// 4. 开始直连下载
async startUpdateDownload(url, updateInfo, installAfter) {
if (this.isDownloadingUpdate) return;
this.isDownloadingUpdate = true;
this.renderUpdateUI('downloading', updateInfo);
try {
const result = await window.electronAPI.downloadUpdate(url);
if (result.success) {
if (installAfter) {
document.getElementById('dl-status-text').textContent = '下载完成,正在启动安装...';
setTimeout(() => window.electronAPI.installUpdate(result.path), 1000);
} else {
window.electronAPI.showItemInFolder(result.path);
this.renderUpdateUI('idle', { currentVersion: updateInfo.currentVersion });
}
}
} catch (error) {
uiManager.showNotification('下载失败', error.error, 'error');
this.renderUpdateUI('available', updateInfo);
} finally {
this.isDownloadingUpdate = false;
}
}
// 5. 监听进度 (逻辑保持,适配新UI ID)
listenForDownloadProgress() {
window.electronAPI.onDownloadProgress(p => {
const bar = document.getElementById('dl-progress-bar');
const percent = document.getElementById('dl-percent-text');
const speed = document.getElementById('dl-speed-text');
if (bar && percent) {
bar.style.width = `${p.percent}%`;
percent.textContent = `${Math.round(p.percent)}%`;
if(speed) speed.textContent = configManager.formatBytes(p.speed || 0) + '/s';
}
});
}
listenForGlobalNetworkSpeed() {
const netSpeedEl = document.getElementById('network-speed');
window.electronAPI.onNetworkSpeedUpdate(({ speed }) => {
netSpeedEl.innerHTML = ` ${configManager.formatBytes(speed)}/s`;
});
}
// --- [修改结束] ---
navigateToSettingsForUpdate() {
this.navigateTo('settings', 'update');
this.updateActiveNavButton(document.getElementById('settings-btn'));
setTimeout(() => {
// 如果已经在 update 面板且是 idle 状态,自动点检查
const container = document.getElementById('update-state-container');
if (container && container.querySelector('.update-state-idle')) {
const btn = document.getElementById('update-check-btn');
if(btn) btn.click();
}
}, 500);
}
addRippleEffectListener() {
document.addEventListener('click', function (e) {
const target = e.target.closest('.ripple');
if (target) {
const rect = target.getBoundingClientRect();
const ripple = document.createElement('span');
const diameter = Math.max(target.clientWidth, target.clientHeight);
const radius = diameter / 2;
ripple.style.width = ripple.style.height = `${diameter}px`;
ripple.style.left = `${e.clientX - rect.left - radius}px`;
ripple.style.top = `${e.clientY - rect.top - radius}px`;
ripple.classList.add('ripple-effect');
const existingRipple = target.querySelector('.ripple-effect');
if (existingRipple) existingRipple.remove();
target.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
}
});
}
async renderTrafficChart() {
if (this.activeChartInstance) { this.activeChartInstance.destroy(); this.activeChartInstance = null; }
const history = await window.electronAPI.getTrafficHistory();
const labels = [], dataPoints = [], today = new Date();
for (let i = 13; i >= 0; i--) {
const d = new Date(); d.setDate(today.getDate() - i);
const dateStr = d.toISOString().split('T')[0];
const record = history ? history.find(h => h.log_date === dateStr) : null;
labels.push(dateStr.substring(5));
dataPoints.push(((record ? record.bytes_used : 0) / (1024 * 1024)).toFixed(2));
}
const ctx = document.getElementById('traffic-chart')?.getContext('2d');
if (!ctx) return;
const style = getComputedStyle(document.documentElement);
const currentFontFamily = style.getPropertyValue('--font-family').trim() || "sans-serif";
const primaryColorRGB = style.getPropertyValue('--primary-rgb').trim();
const textColor = style.getPropertyValue('--text-color').trim();
const borderColor = style.getPropertyValue('--border-color').trim();
const gradient = ctx.createLinearGradient(0, 0, 0, 250);
gradient.addColorStop(0, `rgba(${primaryColorRGB}, 0.5)`);
gradient.addColorStop(1, `rgba(${primaryColorRGB}, 0.05)`);
this.activeChartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: '每日流量 (MB)',
data: dataPoints,
borderColor: `rgba(${primaryColorRGB}, 1)`,
backgroundColor: gradient,
borderWidth: 2,
fill: true,
tension: 0.4,
pointBackgroundColor: `rgba(${primaryColorRGB}, 1)`,
pointRadius: 3,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
mode: 'index',
intersect: false,
backgroundColor: 'rgba(0,0,0,0.8)',
titleColor: '#fff',
titleFont: { family: currentFontFamily, weight: 'bold' },
bodyFont: { family: currentFontFamily },
callbacks: { label: (ctx) => ` ${ctx.parsed.y} MB` }
}
},
scales: {
y: { beginAtZero: true, grid: { color: borderColor, borderDash: [5, 5] }, ticks: { color: textColor, font: { size: 10, family: currentFontFamily } }, title: { display: true, text: '每日使用量 (MB)', color: textColor, font: { family: currentFontFamily } } },
x: { grid: { display: false }, ticks: { color: textColor, font: { size: 10, family: currentFontFamily } } }
},
interaction: { mode: 'nearest', axis: 'x', intersect: false }
}
});
}
toggleOpacityControls(enabled) {}
async toggleTheme(event = null) {
// [修复] 确保字体下载模态框在主题切换开始时关闭
// [重构] 使用通用模态框框架关闭字体下载模态框
try {
const { default: modalManager } = await import('./ui/ModalManager.js');
if (modalManager.isOpen(MainPage.APPEARANCE_FONT_MODAL_ID)) {
modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID);
}
} catch (e) {
// ModalManager 未加载时忽略(不应该发生)
console.warn('[MainPage] ModalManager 未加载,无法关闭字体下载模态框');
}
const currentTheme = document.body.getAttribute('data-theme') || 'dark';
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
// [日志] 记录主题切换
if (configManager && configManager.logAction) {
configManager.logAction(`切换主题: ${currentTheme} -> ${newTheme}`, 'ui');
}
// [重构] 使用新的月亮/太阳过渡动画
await this._applyThemeTransitionWithAnimation(newTheme, event);
}
/**
* [重构] 应用主题过渡动画(带月亮/太阳动画)
* 创建流畅的月亮/太阳滑入和发光动画
* @param {string} newTheme - 新主题名称 ('dark' | 'light')
* @param {Event} event - 触发事件(用于获取点击位置)
*/
async _applyThemeTransitionWithAnimation(newTheme, event = null) {
// [修复] 始终使用屏幕中心位置,确保月亮/太阳在软件正中间
const triggerX = window.innerWidth / 2;
const triggerY = window.innerHeight / 2;
// 创建月亮/太阳动画覆盖层(固定在屏幕中心)
this._createThemeCelestialAnimation(triggerX, triggerY, newTheme);
// 等待动画开始后应用主题
await new Promise(resolve => setTimeout(resolve, 200));
// 应用主题过渡
await this._applyThemeTransition(newTheme);
}
/**
* [新增] 创建月亮/太阳主题切换动画
* @param {number} x - 触发X坐标
* @param {number} y - 触发Y坐标
* @param {string} newTheme - 新主题
*/
_createThemeCelestialAnimation(x, y, newTheme) {
// 移除旧的动画元素
const oldAnimation = document.getElementById('theme-celestial-animation');
if (oldAnimation) oldAnimation.remove();
// 创建动画容器
const animation = document.createElement('div');
animation.id = 'theme-celestial-animation';
animation.className = 'theme-celestial-animation';
animation.setAttribute('data-theme', newTheme);
// [修复] 不再使用触发位置,固定使用屏幕中心(CSS中已设置为50%)
// 注意:x和y参数保留用于未来可能的扩展,但实际位置由CSS固定为屏幕中心
// 创建月亮或太阳图标
const isDark = newTheme === 'dark';
const iconSize = 120;
const iconHtml = isDark ? `
` : `
`;
animation.innerHTML = iconHtml;
document.body.appendChild(animation);
// 触发动画
requestAnimationFrame(() => {
animation.classList.add('active');
});
// 动画结束后移除元素
setTimeout(() => {
animation.classList.add('fade-out');
setTimeout(() => {
if (animation.parentNode) {
animation.remove();
}
}, 600);
}, 1200);
}
/**
* [重构] 应用主题过渡动画
* 解决文字显示问题,使用更平滑的过渡
* 与月亮/太阳动画配合使用
* @param {string} newTheme - 新主题名称 ('dark' | 'light')
*/
async _applyThemeTransition(newTheme) {
// [重构] 检查是否有打开的模态框,需要特殊处理(使用通用模态框框架)
let isModalOpen = false;
let modalManager = null;
try {
const { default: mgr } = await import('./ui/ModalManager.js');
modalManager = mgr;
isModalOpen = modalManager.isOpen(MainPage.APPEARANCE_FONT_MODAL_ID);
} catch (e) {
// ModalManager 未加载时忽略(不应该发生)
console.warn('[MainPage] ModalManager 未加载');
}
// [重构] 确保字体下载模态框在主题切换时不会意外显示(已由通用模态框框架处理)
// 添加过渡类,确保所有元素都有过渡效果
document.documentElement.classList.add('theme-transitioning');
// 等待月亮/太阳动画开始(200ms后)
await new Promise(resolve => setTimeout(resolve, 200));
// 使用 requestAnimationFrame 确保在下一帧应用主题
await new Promise(resolve => requestAnimationFrame(resolve));
// 应用新主题
document.body.setAttribute('data-theme', newTheme);
this.dbSettings.theme = newTheme;
// [修复] 如果模态框打开,强制更新其样式
if (isModalOpen && modalManager) {
const modal = modalManager.get(MainPage.APPEARANCE_FONT_MODAL_ID);
if (modal) {
const modalCard = modal.querySelector('.universal-modal-container');
if (modalCard) {
// 强制重排模态框,确保样式更新
modalCard.offsetHeight;
}
}
}
// 更新主题切换按钮状态
const themeSwitchInput = document.getElementById('theme-switch-input');
if (themeSwitchInput) {
themeSwitchInput.checked = newTheme === 'dark';
}
// [降级] 如果使用旧的图标方式,更新图标
const themeIcon = document.querySelector('#theme-toggle i');
if (themeIcon) {
themeIcon.className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
}
// 保存到数据库
window.electronAPI.setTheme(newTheme);
// 强制重绘,确保文字正确显示
// 使用双重 requestAnimationFrame 确保浏览器完成重排和重绘
await new Promise(resolve => {
requestAnimationFrame(() => {
// 强制同步布局,确保所有样式已应用
document.body.offsetHeight;
// [修复] 如果模态框打开,再次强制更新
// [重构] 重新获取模态框状态(使用通用模态框框架)
if (isModalOpen && modalManager) {
const currentFontModal = modalManager.get(MainPage.APPEARANCE_FONT_MODAL_ID);
if (currentFontModal) {
const modalCard = currentFontModal.querySelector('.universal-modal-container');
if (modalCard) {
modalCard.offsetHeight;
}
}
}
requestAnimationFrame(() => {
// 再次强制布局
document.body.offsetHeight;
// [修复] 如果模态框打开,确保其样式已更新
if (isModalOpen && modalManager) {
const modal = modalManager.get(MainPage.APPEARANCE_FONT_MODAL_ID);
if (modal) {
const modalCard = modal.querySelector('.universal-modal-container');
if (modalCard) {
modalCard.offsetHeight;
}
}
}
// 更新图表(如果需要)
if (document.getElementById('traffic-chart')) {
this.renderTrafficChart();
}
// 再次等待一帧,确保文字渲染完成
requestAnimationFrame(() => {
resolve();
});
});
});
});
// 等待过渡完成后再移除过渡类(与动画时长同步)
setTimeout(() => {
document.documentElement.classList.remove('theme-transitioning');
}, 1200);
}
/**
* [已废弃] 创建主题切换的水面波纹扩散动画
* 已替换为新的月亮/太阳动画系统
*/
_createThemeRippleAnimation(x, y, newTheme) {
// 此方法已不再使用,保留用于向后兼容
// 现在使用 _createThemeCelestialAnimation 代替
}
bindThemeToggle() {
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
// [恢复] 使用之前的简单图标按钮样式
const icon = themeToggle.querySelector('i');
if (icon) {
const currentTheme = document.body.getAttribute('data-theme') || 'dark';
icon.className = currentTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
}
themeToggle.addEventListener('click', async (e) => {
// [修复] 确保字体下载模态框在主题切换时保持关闭
// [重构] 使用通用模态框框架关闭字体下载模态框
try {
const { default: modalManager } = await import('./ui/ModalManager.js');
if (modalManager.isOpen(MainPage.APPEARANCE_FONT_MODAL_ID)) {
modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID);
}
} catch (e) {
// ModalManager 未加载时忽略(不应该发生)
console.warn('[MainPage] ModalManager 未加载,无法关闭字体下载模态框');
}
// [修复] 检查当前是否在设置-外观界面,如果是则额外保护
const appearancePanel = document.getElementById('appearance-panel');
if (appearancePanel && appearancePanel.classList.contains('active')) {
// [重构] 在设置-外观界面,确保字体选择下拉框不会触发(使用常量字符串)
const APPEARANCE_FONT_SELECT_WRAPPER_ID_STR = 'appearance-font-select-wrapper';
const APPEARANCE_FONT_SELECT_OPTIONS_ID_STR = 'appearance-font-select-options';
const fontSelectWrapper = document.getElementById(APPEARANCE_FONT_SELECT_WRAPPER_ID_STR);
if (fontSelectWrapper) {
const fontOptions = document.getElementById(APPEARANCE_FONT_SELECT_OPTIONS_ID_STR);
if (fontOptions && fontOptions.classList.contains('dynamic-active')) {
fontOptions.classList.remove('dynamic-active');
}
}
}
this.toggleTheme(e);
});
}
}
bindSettingsPageEvents() {
if (this.settingsInterval) clearInterval(this.settingsInterval);
if (this.settingsIntervalHeavy) clearInterval(this.settingsIntervalHeavy);
const updateLightStats = async () => {
try {
const [stats, mem] = await Promise.all([
window.electronAPI.getRealtimeStats(),
window.electronAPI.getMemoryUpdate()
]);
const cpuText = document.getElementById('cpu-usage-text');
if (cpuText) cpuText.textContent = `${stats.cpu}%`;
const cpuBar = document.getElementById('cpu-usage-bar');
if (cpuBar) cpuBar.style.width = `${stats.cpu}%`;
const memText = document.getElementById('mem-usage-text');
if (memText) memText.textContent = `${mem.usagePercentage}%`;
const memBar = document.getElementById('mem-usage-bar');
if (memBar) memBar.style.width = `${mem.usagePercentage}%`;
const uptimeText = document.getElementById('app-uptime-text');
if (uptimeText) uptimeText.textContent = stats.uptime;
} catch (error) {
if (this.settingsInterval) clearInterval(this.settingsInterval);
}
};
this.settingsInterval = setInterval(updateLightStats, 2000);
updateLightStats();
const navItems = document.querySelectorAll('.island-nav-item');
navItems.forEach(item => {
item.addEventListener('click', () => {
const targetId = item.dataset.panel + '-panel';
const targetPanel = document.getElementById(targetId);
const currentPanel = document.querySelector('.settings-panel.active');
if (currentPanel === targetPanel) return;
navItems.forEach(n => n.classList.remove('active'));
item.classList.add('active');
if (currentPanel) {
currentPanel.classList.add('panel-exit');
currentPanel.classList.remove('active');
setTimeout(() => {
currentPanel.classList.remove('panel-exit');
}, 300);
}
if (targetPanel) {
void targetPanel.offsetWidth;
targetPanel.classList.add('active');
}
});
});
uiManager.setupAdaptiveDropdown('dd-wrapper-lang-select-wrapper', 'dd-options-lang-select-wrapper',
async (value) => {
await this.handleLanguageChange(value);
}
);
// [修复] 使用字符串常量(避免重复声明,bindSettingsPageEvents 是独立方法)
// 注意:APPEARANCE_FONT_MODAL_ID 使用类级别静态常量 MainPage.APPEARANCE_FONT_MODAL_ID
const APPEARANCE_FONT_SELECT_WRAPPER_ID_STR = 'appearance-font-select-wrapper';
const APPEARANCE_FONT_SELECT_OPTIONS_ID_STR = 'appearance-font-select-options';
const APPEARANCE_FONT_STATUS_ID_STR = 'appearance-font-download-status';
const APPEARANCE_FONT_PROGRESS_BAR_ID_STR = 'appearance-font-download-bar';
const APPEARANCE_FONT_PERCENT_ID_STR = 'appearance-font-download-percent';
const APPEARANCE_DEFAULT_FONT_VALUE_STR = 'Default';
const APPEARANCE_FONT_CONFIG_TYPE_STR = 'appearance_config_font';
uiManager.setupAdaptiveDropdown(APPEARANCE_FONT_SELECT_WRAPPER_ID_STR, APPEARANCE_FONT_SELECT_OPTIONS_ID_STR,
async (value, text, dataset, triggerElement) => {
// [重构] 防止主题切换时误触发字体下载
// 验证触发来源:必须是字体选择下拉框的选项被点击
if (!value || !text) {
console.warn('[AppearanceFontDownload] 无效的字体选择参数,忽略触发');
return;
}
// [重构] 验证 dataset 是否包含有效的字体信息(除非是默认字体)
if (value !== APPEARANCE_DEFAULT_FONT_VALUE_STR && (!dataset || !dataset.url)) {
console.warn('[AppearanceFontDownload] 无效的字体数据,忽略触发');
return;
}
// [重构] 处理默认字体(在创建模态框之前)
if (value === APPEARANCE_DEFAULT_FONT_VALUE_STR) {
// [日志] 记录恢复默认字体
if (configManager && configManager.logAction) {
configManager.logAction('恢复系统默认字体', 'ui');
}
this.dbSettings.customFontName = '';
this.dbSettings.customFontPath = '';
configManager.applyAppSettings({ customFontFamily: false });
await window.electronAPI.saveMedia({
type: APPEARANCE_FONT_CONFIG_TYPE_STR,
name: '',
path: ''
});
uiManager.showNotification('设置成功', '已恢复系统默认字体');
triggerElement.innerHTML = `系统默认 (${APPEARANCE_DEFAULT_FONT_VALUE_STR})`;
return;
}
const fontUrl = dataset?.url || '';
// [重构] 参数验证
if (!fontUrl) {
uiManager.showNotification('错误', '字体下载地址无效', 'error');
return;
}
// [重构] 双重验证:确保不是主题切换触发的
// 检查当前是否有主题切换正在进行
if (document.documentElement.classList.contains('theme-transitioning')) {
console.warn('[AppearanceFontDownload] 主题切换正在进行,忽略字体下载请求');
return;
}
// [重构] 检查当前是否在设置-外观界面
const appearancePanel = document.getElementById('appearance-panel');
if (appearancePanel && appearancePanel.classList.contains('active')) {
// 在设置-外观界面,额外检查是否真的是字体选择操作
const fontSelectWrapper = document.getElementById(APPEARANCE_FONT_SELECT_WRAPPER_ID_STR);
if (!fontSelectWrapper || !fontSelectWrapper.contains(document.activeElement)) {
console.warn('[AppearanceFontDownload] 在设置-外观界面,但字体选择下拉框未激活,忽略触发');
return;
}
}
// [日志] 记录字体下载开始
if (configManager && configManager.logAction) {
configManager.logAction(`开始下载字体: ${value} (${text})`, 'ui');
}
// [完善] 更新UI状态
triggerElement.innerHTML = `${text}`;
// [重构] 使用通用模态框框架创建字体下载模态框
const { default: modalManager } = await import('./ui/ModalManager.js');
// 创建字体下载模态框
const fontModalContent = `
`;
const modal = modalManager.create({
id: MainPage.APPEARANCE_FONT_MODAL_ID,
title: '字体下载',
content: fontModalContent,
size: 'small',
closable: false, // 下载过程中不允许关闭
closeOnBackdrop: false,
className: 'font-download-modal-wrapper'
});
// 获取模态框内的元素
const statusText = document.getElementById('appearance-font-download-status');
const progressBar = document.getElementById('appearance-font-download-bar');
const percentText = document.getElementById('appearance-font-download-percent');
// [重构] 参数验证
if (!modal || !statusText || !progressBar || !percentText) {
console.error('[AppearanceFontDownload] 模态框元素未找到');
uiManager.showNotification('错误', '字体下载界面初始化失败', 'error');
return;
}
// 更新初始状态
statusText.textContent = `正在准备下载 ${value}...`;
progressBar.style.width = '0%';
percentText.textContent = '0%';
// [完善] 延迟执行下载,确保UI已更新
setTimeout(async () => {
// [重构] 注册进度回调(使用命名函数,便于移除)
const progressCallback = (data) => {
if (!data || typeof data.percent !== 'number') {
console.warn('[AppearanceFontDownload] 无效的进度数据:', data);
return;
}
const p = Math.round(data.percent);
progressBar.style.width = `${p}%`;
percentText.textContent = `${p}%`;
if (data.received !== undefined && data.total !== undefined) {
statusText.textContent = `下载中... ${configManager.formatBytes(data.received)} / ${configManager.formatBytes(data.total)}`;
} else {
statusText.textContent = `下载中... ${p}%`;
}
};
window.electronAPI.onFontDownloadProgress(progressCallback);
try {
// [重构] 使用明确的参数名称
const downloadParams = {
fontName: value,
fontUrl: fontUrl
};
const result = await window.electronAPI.downloadFont(downloadParams);
if (result && result.success) {
progressBar.style.width = '100%';
percentText.textContent = '100%';
statusText.textContent = result.cached
? '字体已存在,验证通过'
: '下载完成,正在应用...';
// [日志] 记录字体下载成功
if (configManager && configManager.logAction) {
configManager.logAction(
`字体下载成功: ${value}${result.cached ? ' (已缓存)' : ''}`,
'ui'
);
}
// [重构] 保存字体配置(使用新的常量)
this.dbSettings.customFontName = value;
this.dbSettings.customFontPath = result.path || '';
await window.electronAPI.saveMedia({
type: APPEARANCE_FONT_CONFIG_TYPE_STR,
name: value,
path: result.path || ''
});
setTimeout(async () => {
// [重构] 使用通用模态框框架关闭模态框
const { default: modalManager } = await import('./ui/ModalManager.js');
modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID);
uiManager.showNotification('设置成功', '即将重启软件以应用字体...');
setTimeout(() => {
window.electronAPI.relaunchApp();
}, 1000);
}, 800);
} else {
throw new Error(result?.error || '下载失败:未知错误');
}
} catch (err) {
console.error('[AppearanceFontDownload] 下载失败:', err);
// [日志] 记录字体下载失败
if (configManager && configManager.logAction) {
configManager.logAction(`字体下载失败: ${value} - ${err.message}`, 'error');
}
// [重构] 使用通用模态框框架关闭模态框
const { default: modalManager } = await import('./ui/ModalManager.js');
modalManager.close(MainPage.APPEARANCE_FONT_MODAL_ID);
uiManager.showNotification('下载失败', err.message || '未知错误', 'error');
}
}, 500);
}
);
const aboutNavButton = document.querySelector('.island-nav-item[data-panel="about"]');
if (aboutNavButton) aboutNavButton.addEventListener('contextmenu', (e) => { e.preventDefault(); this.handleSecretTrigger(false); });
document.getElementById('more-info-btn')?.addEventListener('click', () => {
const currentTheme = document.body.getAttribute('data-theme') || 'dark';
const versions = configManager.config?.dbSettings?.versions || {};
window.electronAPI.openAcknowledgementsWindow(currentTheme, versions);
});
document.getElementById('author-website-btn')?.addEventListener('click', () => window.electronAPI.openExternalLink('https://blog.ymhut.cn'));
// 工具健康检查按钮
document.getElementById('tool-health-check-btn')?.addEventListener('click', async () => {
const btn = document.getElementById('tool-health-check-btn');
if (btn.disabled) return; // 防止重复点击
const progressContainer = document.getElementById('tool-health-progress-container');
const progressStatus = document.getElementById('tool-health-progress-status');
const progressText = document.getElementById('tool-health-progress-text');
const progressBar = document.getElementById('tool-health-progress-bar');
const progressSuccess = document.getElementById('tool-health-progress-success');
const progressFailed = document.getElementById('tool-health-progress-failed');
const progressPending = document.getElementById('tool-health-progress-pending');
btn.disabled = true;
const btnContent = btn.querySelector('.tool-health-btn-content');
if (btnContent) {
btnContent.innerHTML = ` ${i18n.t('settings.toolHealth.checking', '检查中...')}`;
} else {
btn.innerHTML = ` ${i18n.t('settings.toolHealth.checking', '检查中...')}`;
}
// 显示进度容器
if (progressContainer) {
progressContainer.style.display = 'block';
// 重置进度
if (progressText) progressText.textContent = '0 / 0';
if (progressBar) progressBar.style.width = '0%';
if (progressSuccess) progressSuccess.textContent = '0';
if (progressFailed) progressFailed.textContent = '0';
if (progressPending) progressPending.textContent = '0';
}
try {
// [重构] 直接使用 toolHealthChecker,不显示模态框
if (!window.toolHealthChecker) {
let retries = 0;
while (!window.toolHealthChecker && retries < 20) {
await new Promise(resolve => setTimeout(resolve, 100));
retries++;
}
}
if (window.toolHealthChecker) {
// 设置进度更新回调
const progressCallback = (progress) => {
const { checked = 0, total = 0, success = 0, failed = 0 } = progress;
// 更新设置页面的进度显示
if (progressText) {
progressText.textContent = `${checked} / ${total}`;
}
if (progressBar) {
const percent = total > 0 ? (checked / total) * 100 : 0;
progressBar.style.width = `${percent}%`;
}
if (progressSuccess) progressSuccess.textContent = String(success);
if (progressFailed) progressFailed.textContent = String(failed);
if (progressPending) {
const pending = total - checked;
progressPending.textContent = String(pending);
}
if (progressStatus) {
if (checked < total) {
progressStatus.innerHTML = `${i18n.t('settings.toolHealth.checking', '检查中...')}`;
} else {
progressStatus.innerHTML = `${i18n.t('toolHealthCheck.completed', '检查完成')}`;
}
}
};
// 注册进度回调
window.toolHealthChecker.onProgress(progressCallback);
// 强制检查(force = true),忽略每日检查限制
await window.toolHealthChecker.checkAllTools(true, false);
// 移除进度回调
window.toolHealthChecker.offProgress(progressCallback);
// 检查完成后更新UI
this.updateLockedToolsList();
this.updateToolHealthStats();
} else {
uiManager.showNotification('错误', '工具健康检查模块未加载', 'error');
}
} catch (error) {
console.error('工具健康检查失败:', error);
uiManager.showNotification('检查失败', error.message || '未知错误', 'error');
if (progressStatus) {
progressStatus.innerHTML = `${i18n.t('toolHealthCheck.error', '检查失败')}`;
}
} finally {
btn.disabled = false;
const btnContent = btn.querySelector('.tool-health-btn-content');
if (btnContent) {
btnContent.innerHTML = ` ${i18n.t('settings.toolHealth.startCheck', '开始检查')}`;
} else {
btn.innerHTML = ` ${i18n.t('settings.toolHealth.startCheck', '开始检查')}`;
}
// 延迟隐藏进度容器,让用户看到最终结果
setTimeout(() => {
if (progressContainer) {
progressContainer.style.display = 'none';
}
}, 3000);
}
});
// 更新已锁定工具列表和统计信息
this.updateLockedToolsList();
this.updateToolHealthStats();
// [新增] 绑定锁定工具卡片点击事件
const lockedCard = document.getElementById('tool-health-locked-card');
if (lockedCard) {
lockedCard.addEventListener('click', () => {
this.showLockedToolsModal();
});
}
// [重构] 模态框事件绑定已由 ModalManager 统一处理,无需手动绑定
// 通用模态框框架会自动处理关闭按钮、背景点击和ESC键
document.getElementById('feedback-btn')?.addEventListener('click', () => window.electronAPI.openExternalLink('mailto:admin@ymhut.cn'));
this.renderTrafficChart();
}
/**
* [重构] 更新已锁定工具列表
* 使用 toolStatusManager 统一管理锁定状态
* 支持模态框显示(使用通用模态框框架)
*/
updateLockedToolsList() {
const lockedListEl = document.getElementById('tool-health-locked-list');
const lockedCardEl = document.getElementById('tool-health-locked-card');
// [健康检查预留位置] 使用 toolStatusManager 获取锁定工具列表
// 动态导入 toolStatusManager 以避免循环依赖
import('./toolStatusManager.js').then(module => {
const toolStatusManager = module.default;
const lockedTools = toolStatusManager.getLockedTools();
// 更新统计卡片显示
if (lockedListEl) {
if (lockedTools.length > 0) {
const count = lockedTools.length;
lockedListEl.innerHTML = `${count} 个工具已锁定`;
// 如果有锁定工具,卡片可点击
if (lockedCardEl) {
lockedCardEl.classList.add('has-locked');
}
} else {
lockedListEl.innerHTML = `${i18n.t('settings.toolHealth.noLocked', '暂无锁定工具')}`;
// 没有锁定工具,移除可点击状态
if (lockedCardEl) {
lockedCardEl.classList.remove('has-locked');
}
}
}
// [重构] 如果模态框已打开(使用通用模态框框架),更新其内容
import('./ui/ModalManager.js').then(({ default: modalManager }) => {
if (modalManager.isOpen('tool-health-locked-modal')) {
const modalContent = this._generateLockedToolsContent(lockedTools, toolStatusManager);
modalManager.updateContent('tool-health-locked-modal',
`${modalContent}
`
);
}
}).catch(() => {
// 如果 ModalManager 未加载,忽略
});
}).catch((error) => {
console.error('[MainPage] 更新锁定工具列表失败:', error);
// 如果导入失败,延迟重试
setTimeout(() => this.updateLockedToolsList(), 500);
});
}
/**
* [新增] 生成锁定工具内容HTML
* @private
*/
_generateLockedToolsContent(lockedTools, toolStatusManager) {
if (lockedTools.length === 0) {
return `
`;
}
return lockedTools.map(toolId => {
let toolName = toolId;
let toolDescription = '工具已锁定';
// 尝试获取工具信息
if (window.toolRegistry && window.toolRegistry[toolId]) {
const toolClass = window.toolRegistry[toolId];
toolName = toolClass.name || toolId;
toolDescription = toolClass.description || '工具已锁定';
} else if (window.uiManager && window.uiManager.modularTools) {
const toolMeta = window.uiManager.modularTools[toolId];
if (toolMeta) {
toolName = toolMeta.name || toolId;
toolDescription = toolMeta.description || '工具已锁定';
}
}
const status = toolStatusManager.getToolStatus(toolId);
const lockReason = status?.lockReason || '健康检查失败';
const lockTime = status?.lastHealthCheck ? new Date(status.lastHealthCheck).toLocaleString('zh-CN') : '未知时间';
return `
`;
}).join('');
}
/**
* [重构] 显示锁定工具模态框(使用通用模态框框架)
*/
async showLockedToolsModal() {
// 动态导入 ModalManager
const { default: modalManager } = await import('./ui/ModalManager.js');
// 获取锁定工具列表
const lockedTools = await this._getLockedToolsList();
// 生成内容HTML
let contentHtml = '';
if (lockedTools.length > 0) {
contentHtml = lockedTools.map(tool => {
const toolName = tool.name || tool.id;
const toolDescription = tool.description || '工具已锁定';
const lockReason = tool.lockReason || '健康检查失败';
const lockTime = tool.lockTime || '未知时间';
return `
`;
}).join('');
} else {
contentHtml = `
`;
}
// 使用通用模态框框架创建模态框
modalManager.create({
id: 'tool-health-locked-modal',
title: ' 已锁定工具',
content: `${contentHtml}
`,
size: 'medium',
closable: true,
closeOnBackdrop: true,
className: 'tool-health-locked-modal-wrapper',
onClose: () => {
// 关闭回调
}
});
}
/**
* [重构] 隐藏锁定工具模态框(使用通用模态框框架)
*/
async hideLockedToolsModal() {
const { default: modalManager } = await import('./ui/ModalManager.js');
modalManager.close('tool-health-locked-modal');
}
/**
* [新增] 获取锁定工具列表
* @private
*/
async _getLockedToolsList() {
try {
const { default: toolStatusManager } = await import('./toolStatusManager.js');
const lockedTools = toolStatusManager.getLockedTools();
return lockedTools.map(toolId => {
let toolName = toolId;
let toolDescription = '工具已锁定';
// 尝试获取工具信息
if (window.toolRegistry && window.toolRegistry[toolId]) {
const toolClass = window.toolRegistry[toolId];
toolName = toolClass.name || toolId;
toolDescription = toolClass.description || '工具已锁定';
}
const status = toolStatusManager.getToolStatus(toolId);
return {
id: toolId,
name: toolName,
description: toolDescription,
lockReason: status?.lockReason || '健康检查失败',
lockTime: status?.lastHealthCheck ? new Date(status.lastHealthCheck).toLocaleString('zh-CN') : '未知时间'
};
});
} catch (error) {
console.error('[MainPage] 获取锁定工具列表失败:', error);
return [];
}
}
/**
* [重构] 显示检查历史记录模态框(使用通用模态框框架)
*/
async showToolHealthHistoryModal() {
// [日志] 记录查看检查历史
if (configManager && configManager.logAction) {
configManager.logAction('查看工具健康检查历史记录', 'ui');
}
// 动态导入 ModalManager
const { default: modalManager } = await import('./ui/ModalManager.js');
// 显示加载状态
const loadingContent = `
`;
// 创建模态框(先显示加载状态)
const modal = modalManager.create({
id: 'tool-health-history-modal',
title: ' 检查历史记录',
content: `${loadingContent}
`,
size: 'large',
closable: true,
closeOnBackdrop: true,
className: 'tool-health-history-modal-wrapper'
});
// 获取检查历史记录
try {
if (window.electronAPI && window.electronAPI.getAllToolHealthCheckSummaries) {
const result = await window.electronAPI.getAllToolHealthCheckSummaries(50);
if (result && result.success && result.data && result.data.length > 0) {
// 显示历史记录列表
const historyContent = result.data.map(summary => {
const checkDate = new Date(summary.check_date);
const formatted = checkDate.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
const successCount = summary.success_count || 0;
const failedCount = summary.failed_count || 0;
const totalCount = summary.total_count || 0;
return `
`;
}).join('');
// 更新模态框内容
modalManager.updateContent('tool-health-history-modal',
`${historyContent}
`
);
} else {
// 没有历史记录
modalManager.updateContent('tool-health-history-modal',
``
);
}
} else {
modalManager.updateContent('tool-health-history-modal',
``
);
}
} catch (error) {
console.error('[MainPage] 加载检查历史记录失败:', error);
modalManager.updateContent('tool-health-history-modal',
``
);
}
}
/**
* [重构] 隐藏检查历史记录模态框(使用通用模态框框架)
*/
async hideToolHealthHistoryModal() {
const { default: modalManager } = await import('./ui/ModalManager.js');
modalManager.close('tool-health-history-modal');
}
/**
* 更新工具健康检查统计信息
*/
updateToolHealthStats() {
// [优化] 更新上次检查时间(从数据库获取最新记录)
const lastCheckTimeEl = document.getElementById('tool-health-last-check-time');
const lastCheckCard = document.getElementById('tool-health-last-check-card');
if (lastCheckTimeEl) {
// 从数据库获取最新的检查时间
if (window.electronAPI && window.electronAPI.getLastHealthCheckDate) {
window.electronAPI.getLastHealthCheckDate().then(result => {
if (result && result.success && result.data) {
const lastCheckDate = result.data;
if (lastCheckDate) {
const date = new Date(lastCheckDate);
const formatted = date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
lastCheckTimeEl.textContent = formatted;
lastCheckTimeEl.style.color = 'var(--text-primary)';
// [优化] 如果有检查记录,使卡片可点击
if (lastCheckCard) {
lastCheckCard.classList.add('has-history');
}
} else {
lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查');
lastCheckTimeEl.style.color = 'var(--text-secondary)';
}
} else {
lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查');
lastCheckTimeEl.style.color = 'var(--text-secondary)';
}
}).catch(() => {
lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查');
lastCheckTimeEl.style.color = 'var(--text-secondary)';
});
} else {
// 降级方案:使用 toolHealthChecker 的 lastCheckDate
if (window.toolHealthChecker) {
const lastCheckDate = window.toolHealthChecker.lastCheckDate;
if (lastCheckDate) {
const date = new Date(lastCheckDate);
const formatted = date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
lastCheckTimeEl.textContent = formatted;
lastCheckTimeEl.style.color = 'var(--text-primary)';
if (lastCheckCard) {
lastCheckCard.classList.add('has-history');
}
} else {
lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查');
lastCheckTimeEl.style.color = 'var(--text-secondary)';
}
} else {
lastCheckTimeEl.textContent = i18n.t('settings.toolHealth.never', '从未检查');
lastCheckTimeEl.style.color = 'var(--text-secondary)';
}
}
}
// [新增] 绑定检查历史记录模态框事件
if (lastCheckCard) {
// 移除旧的事件监听器(如果存在)
const newLastCheckCard = lastCheckCard.cloneNode(true);
lastCheckCard.parentNode?.replaceChild(newLastCheckCard, lastCheckCard);
const updatedLastCheckCard = document.getElementById('tool-health-last-check-card');
if (updatedLastCheckCard) {
updatedLastCheckCard.addEventListener('click', () => {
this.showToolHealthHistoryModal();
});
}
}
// [优化] 更新网络工具总数(使用动态导入避免依赖问题)
const totalCountEl = document.getElementById('tool-health-total-count');
if (totalCountEl) {
// 使用动态导入获取 toolRegistry
import('./tool-registry.js').then(({ toolRegistry }) => {
if (window.toolHealthChecker && toolRegistry) {
const allTools = Object.keys(toolRegistry);
const networkTools = allTools.filter(toolId =>
window.toolHealthChecker.isNetworkTool(toolId)
);
totalCountEl.textContent = `${networkTools.length} ${i18n.t('settings.toolHealth.tools', '个工具')}`;
totalCountEl.style.color = 'var(--text-primary)';
} else if (window.toolHealthChecker) {
// 如果 toolHealthChecker 可用但 toolRegistry 未加载,延迟重试
setTimeout(() => this.updateToolHealthStats(), 500);
} else {
totalCountEl.textContent = i18n.t('settings.toolHealth.calculating', '计算中...');
totalCountEl.style.color = 'var(--text-secondary)';
// 延迟重试
setTimeout(() => this.updateToolHealthStats(), 1000);
}
}).catch(() => {
// 如果导入失败,延迟重试
totalCountEl.textContent = i18n.t('settings.toolHealth.calculating', '计算中...');
totalCountEl.style.color = 'var(--text-secondary)';
setTimeout(() => this.updateToolHealthStats(), 1000);
});
}
}
/**
* 处理语言切换
* @param {string} targetLang - 目标语言代码 ('auto', 'zh-CN', 'en-US')
*/
async handleLanguageChange(targetLang) {
// 如果是auto,直接保存并重启
if (targetLang === 'auto') {
uiManager.showNotification(i18n.t('common.notification.title.info'), '正在保存语言设置并重启...', 'info');
try {
await window.electronAPI.saveLanguageConfig(targetLang);
} catch (err) {
uiManager.showNotification(i18n.t('common.notification.title.error'), err.message, 'error');
}
return;
}
// 获取当前语言配置
const langConfig = await window.electronAPI.getLanguageConfig();
const currentLang = langConfig.actual || 'zh-CN';
// 如果目标语言和当前语言相同,直接返回
if (targetLang === currentLang) {
uiManager.showNotification(i18n.t('common.notification.title.info'), '语言设置已保存', 'info');
await window.electronAPI.saveLanguageConfig(targetLang);
return;
}
// 检查目标语言包是否存在
const hasLangPack = langConfig.pack && Object.keys(langConfig.pack).length > 0 && targetLang !== 'zh-CN';
// 如果语言包不存在或需要翻译,显示翻译遮罩层
if (!hasLangPack && targetLang !== 'zh-CN') {
// 动态导入翻译服务
const { default: translationService } = await import('./translationService.js');
const { default: translationOverlay } = await import('./translationOverlay.js');
// 显示翻译遮罩层
translationOverlay.show(i18n.t('translation.progress') || '正在准备翻译...', 0);
try {
// 获取源语言包(中文)
const sourcePack = langConfig.fallback || {};
// 确定目标语言代码
const targetLangCode = targetLang === 'en-US' ? 'en' : targetLang.split('-')[0];
const sourceLangCode = 'zh';
// 翻译语言包
let translatedCount = 0;
const totalKeys = Object.keys(sourcePack).length;
const translatedPack = await translationService.translateObject(
sourcePack,
targetLangCode,
sourceLangCode,
(current, total, key) => {
translatedCount = current;
const progress = (current / total) * 100;
translationOverlay.updateProgress(
i18n.t('translation.progress') || '正在翻译语言包...',
progress,
key
);
}
);
// 保存翻译后的语言包
translationOverlay.updateProgress(i18n.t('translation.saving') || '正在保存翻译结果...', 95);
await window.electronAPI.saveTranslatedLanguagePack(targetLang, translatedPack);
// 保存语言配置
translationOverlay.updateProgress('正在保存语言设置...', 98);
await window.electronAPI.saveLanguageConfig(targetLang);
// 隐藏遮罩层
translationOverlay.hide();
// 显示成功消息并重启
uiManager.showNotification(
i18n.t('common.notification.title.success'),
i18n.t('translation.complete') || '语言包翻译完成,正在重启应用...',
'success'
);
// 延迟重启,让用户看到消息
setTimeout(() => {
window.location.reload();
}, 1000);
} catch (error) {
translationOverlay.hide();
console.error('[Language] Translation failed:', error);
uiManager.showNotification(
i18n.t('common.notification.title.error'),
`${i18n.t('translation.failed') || '翻译失败'}: ${error.message}。将使用默认语言包。`,
'error'
);
// 即使翻译失败,也保存语言设置(使用已有的语言包)
try {
await window.electronAPI.saveLanguageConfig(targetLang);
} catch (err) {
console.error('[Language] Failed to save language config:', err);
}
}
} else {
// 语言包已存在,直接保存并重启
uiManager.showNotification(i18n.t('common.notification.title.info'), '正在保存语言设置并重启...', 'info');
try {
await window.electronAPI.saveLanguageConfig(targetLang);
} catch (err) {
uiManager.showNotification(i18n.t('common.notification.title.error'), err.message, 'error');
}
}
}
}
const mainPageInstance = new MainPage();
window.mainPage = mainPageInstance;
// 暴露工具健康检查模块到全局(延迟加载)
setTimeout(async () => {
try {
const toolHealthCheckerModule = await import('./toolHealthChecker.js');
window.toolHealthChecker = toolHealthCheckerModule.default;
// [重构] 不再暴露模态框版本的工具健康检查到全局
// const toolHealthCheckModalModule = await import('./toolHealthCheckModal.js');
// window.toolHealthCheckModal = toolHealthCheckModalModule.default;
// [重构] 暴露工具箱验证页面
const toolboxVerificationPageModule = await import('./toolboxVerificationPage.js');
window.toolboxVerificationPage = toolboxVerificationPageModule.default;
} catch (error) {
console.error('Failed to load tool health check modules:', error);
}
}, 100);
// [修复] 确保 DOM 加载后再启动初始化,防止白屏
document.addEventListener('DOMContentLoaded', () => {
mainPageInstance.init();
});