// 网址管理器应用 class UrlManager { constructor() { this.urls = []; this.categories = []; this.selectedUrls = new Set(); this.currentCategory = 'all'; this.currentSearch = ''; this.currentTag = ''; this.viewMode = 'grid'; this.editingUrlId = null; } async init() { await this.loadData(); this.setupEventListeners(); this.render(); this.setupKeyboardShortcuts(); } // 数据存储 async loadData() { try { // 优先从服务器加载数据 const response = await fetch('/api/urls'); if (response.ok) { const data = await response.json(); this.urls = data.urls && data.urls.length > 0 ? data.urls : this.getDefaultUrls(); this.categories = data.categories && data.categories.length > 0 ? data.categories : this.getDefaultCategories(); // 同时保存到 localStorage 作为备份 localStorage.setItem('urlManager_urls', JSON.stringify(this.urls)); localStorage.setItem('urlManager_categories', JSON.stringify(this.categories)); return; } } catch (error) { console.warn('从服务器加载数据失败,尝试从 localStorage 加载:', error); } // 降级到 localStorage const savedUrls = localStorage.getItem('urlManager_urls'); const savedCategories = localStorage.getItem('urlManager_categories'); if (savedUrls) { this.urls = JSON.parse(savedUrls); } else { // 添加一些示例数据 this.urls = this.getDefaultUrls(); } if (savedCategories) { this.categories = JSON.parse(savedCategories); } else { this.categories = this.getDefaultCategories(); } } async saveData() { try { const dataToSave = { urls: this.urls || [], categories: this.categories || [] }; console.log('保存数据:', { urlsCount: dataToSave.urls.length, categoriesCount: dataToSave.categories.length }); // 优先保存到服务器 const response = await fetch('/api/urls', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataToSave) }); if (response.ok) { const result = await response.json(); console.log('服务器保存成功:', result); // 服务器保存成功,同时保存到 localStorage 作为备份 localStorage.setItem('urlManager_urls', JSON.stringify(this.urls)); localStorage.setItem('urlManager_categories', JSON.stringify(this.categories)); return true; } else { const errorText = await response.text(); console.error('服务器返回错误:', response.status, errorText); throw new Error(`服务器返回错误: ${response.status} - ${errorText}`); } } catch (error) { console.error('保存到服务器失败:', error); // 降级到 localStorage try { localStorage.setItem('urlManager_urls', JSON.stringify(this.urls)); localStorage.setItem('urlManager_categories', JSON.stringify(this.categories)); console.warn('已降级保存到 localStorage'); } catch (e) { console.error('保存数据到localStorage也失败:', e); } throw error; // 重新抛出错误,让调用者知道保存失败 } } getDefaultUrls() { return [ { id: '1', title: 'Android开发者文档', url: 'https://developer.android.com', description: '官方Android开发文档和API参考', category: 'dev-docs', icon: 'fas fa-book', tags: ['文档', '官方', 'Android'], favorite: true, createdAt: new Date().toISOString(), lastAccessed: null, accessCount: 0 }, { id: '2', title: 'GitHub', url: 'https://github.com', description: '代码托管和版本控制平台', category: 'tools', icon: 'fas fa-code', tags: ['工具', '代码', '版本控制'], favorite: true, createdAt: new Date().toISOString(), lastAccessed: null, accessCount: 0 }, { id: '3', title: 'Stack Overflow', url: 'https://stackoverflow.com', description: '开发者问答社区', category: 'community', icon: 'fas fa-comments', tags: ['社区', '问答', '帮助'], favorite: false, createdAt: new Date().toISOString(), lastAccessed: null, accessCount: 0 }, { id: '4', title: 'Material Design', url: 'https://material.io', description: 'Google的设计系统', category: 'design', icon: 'fas fa-palette', tags: ['设计', 'UI', 'Material'], favorite: false, createdAt: new Date().toISOString(), lastAccessed: null, accessCount: 0 } ]; } getDefaultCategories() { return [ { id: 'dev-docs', name: '开发文档', color: '#3498db', icon: 'fas fa-book' }, { id: 'tools', name: '工具', color: '#2ecc71', icon: 'fas fa-tools' }, { id: 'community', name: '社区', color: '#9b59b6', icon: 'fas fa-comments' }, { id: 'design', name: '设计', color: '#e74c3c', icon: 'fas fa-palette' } ]; } // 网址管理方法 addUrl(urlData) { const newUrl = { id: Date.now().toString(), createdAt: new Date().toISOString(), lastAccessed: null, accessCount: 0, ...urlData }; // 先保存数据到内存和localStorage(核心操作,不能失败) this.urls.push(newUrl); this.saveData(); // 如果当前有分类过滤或搜索过滤,切换到"全部网址"以确保新添加的网址能显示 if (this.currentCategory !== 'all' || this.currentSearch || this.currentTag) { this.currentCategory = 'all'; this.currentSearch = ''; this.currentTag = ''; // 清除搜索框 const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = ''; } } // 渲染页面(必须执行,确保页面刷新) // render() 会更新分类选择器的UI状态,所以不需要单独更新 this.render(); return newUrl; } updateUrl(id, updates) { const index = this.urls.findIndex(url => url.id === id); if (index !== -1) { // 先保存数据到内存和localStorage(核心操作,不能失败) this.urls[index] = { ...this.urls[index], ...updates }; this.saveData(); // 渲染页面(必须执行,确保页面刷新) this.render(); return true; } return false; } deleteUrl(id) { const index = this.urls.findIndex(url => url.id === id); if (index !== -1) { this.urls.splice(index, 1); this.selectedUrls.delete(id); this.saveData(); this.render(); return true; } return false; } deleteSelectedUrls() { this.urls = this.urls.filter(url => !this.selectedUrls.has(url.id)); this.selectedUrls.clear(); this.saveData(); this.render(); this.hideBulkActions(); } toggleFavorite(id) { const url = this.urls.find(url => url.id === id); if (url) { url.favorite = !url.favorite; this.saveData(); this.render(); } } // 分类管理 addCategory(categoryData) { const newCategory = { id: Date.now().toString(), ...categoryData }; this.categories.push(newCategory); this.saveData(); this.renderCategories(); return newCategory; } // 过滤和搜索 getFilteredUrls() { let filtered = [...this.urls]; // 按分类过滤 if (this.currentCategory === 'favorites') { filtered = filtered.filter(url => url.favorite); } else if (this.currentCategory === 'recent') { filtered = filtered.filter(url => url.lastAccessed) .sort((a, b) => new Date(b.lastAccessed) - new Date(a.lastAccessed)) .slice(0, 20); } else if (this.currentCategory !== 'all') { filtered = filtered.filter(url => url.category === this.currentCategory); } // 按搜索词过滤 if (this.currentSearch) { const searchLower = this.currentSearch.toLowerCase(); filtered = filtered.filter(url => url.title.toLowerCase().includes(searchLower) || url.url.toLowerCase().includes(searchLower) || url.description?.toLowerCase().includes(searchLower) || url.tags?.some(tag => tag.toLowerCase().includes(searchLower)) ); } // 按标签过滤 if (this.currentTag) { filtered = filtered.filter(url => url.tags?.some(tag => tag.toLowerCase() === this.currentTag.toLowerCase()) ); } return filtered; } // 渲染方法 render() { // 分别处理每个渲染方法,确保即使某个方法失败,其他方法也能执行 try { this.renderCategories(); } catch (error) { console.warn('渲染分类时出错:', error); } try { this.renderUrls(); } catch (error) { console.warn('渲染网址列表时出错:', error); } try { this.renderStats(); } catch (error) { console.warn('渲染统计信息时出错:', error); } try { this.renderTags(); } catch (error) { console.warn('渲染标签时出错:', error); } } renderCategories() { const categoriesList = document.getElementById('categoriesList'); if (!categoriesList) return; const allCount = this.urls.length; const favoritesCount = this.urls.filter(url => url.favorite).length; const recentCount = this.urls.filter(url => url.lastAccessed).length; let html = `
全部网址 ${allCount}
收藏夹 ${favoritesCount}
最近访问 ${recentCount}
`; // 添加自定义分类 this.categories.forEach(category => { const count = this.urls.filter(url => url.category === category.id).length; html += `
${category.name} ${count}
`; }); categoriesList.innerHTML = html; // 更新分类选择器 this.updateCategorySelector(); } renderUrls() { const urlsContainer = document.getElementById('urlsContainer'); const emptyState = document.getElementById('emptyState'); if (!urlsContainer) { console.warn('urlsContainer 元素不存在'); return; } const filteredUrls = this.getFilteredUrls(); if (filteredUrls.length === 0) { if (emptyState) { emptyState.style.display = 'block'; } urlsContainer.innerHTML = ''; if (emptyState) { urlsContainer.appendChild(emptyState); } return; } if (emptyState) { emptyState.style.display = 'none'; } let html = ''; filteredUrls.forEach(url => { const category = this.categories.find(c => c.id === url.category); const isSelected = this.selectedUrls.has(url.id); html += `
${url.title}
${url.url}
${url.description ? `
${url.description}
` : ''}
`; }); urlsContainer.innerHTML = html; } renderStats() { const totalUrlsEl = document.getElementById('totalUrls'); const totalCategoriesEl = document.getElementById('totalCategories'); const totalFavoritesEl = document.getElementById('totalFavorites'); if (totalUrlsEl) totalUrlsEl.textContent = this.urls.length; if (totalCategoriesEl) totalCategoriesEl.textContent = this.categories.length; if (totalFavoritesEl) totalFavoritesEl.textContent = this.urls.filter(url => url.favorite).length; } renderTags() { const tagsContainer = document.getElementById('tagsContainer'); if (!tagsContainer) return; // 收集所有标签 const tagCounts = {}; this.urls.forEach(url => { url.tags?.forEach(tag => { tagCounts[tag] = (tagCounts[tag] || 0) + 1; }); }); const tags = Object.entries(tagCounts) .sort((a, b) => b[1] - a[1]) .slice(0, 10); let html = ''; tags.forEach(([tag, count]) => { const isActive = this.currentTag === tag; html += `${tag} (${count})`; }); tagsContainer.innerHTML = html; } updateCategorySelector() { try { const categorySelect = document.getElementById('urlCategory'); if (!categorySelect) return; let html = ''; this.categories.forEach(category => { html += ``; }); categorySelect.innerHTML = html; } catch (error) { console.warn('更新分类选择器时出错:', error); // 不影响其他操作 } } // 事件监听器 setupEventListeners() { // 主题切换 document.getElementById('themeToggle').addEventListener('click', () => { this.toggleTheme(); }); // 添加网址按钮 document.getElementById('addUrlBtn').addEventListener('click', () => { this.showUrlModal(); }); document.getElementById('addFirstUrlBtn').addEventListener('click', () => { this.showUrlModal(); }); // 添加分类按钮 document.getElementById('addCategoryBtn').addEventListener('click', () => { this.showCategoryModal(); }); // 搜索 document.getElementById('searchInput').addEventListener('input', (e) => { this.currentSearch = e.target.value; this.renderUrls(); }); document.getElementById('clearSearch').addEventListener('click', () => { document.getElementById('searchInput').value = ''; this.currentSearch = ''; this.renderUrls(); }); // 视图切换 document.getElementById('gridViewBtn').addEventListener('click', () => { this.setViewMode('grid'); }); document.getElementById('listViewBtn').addEventListener('click', () => { this.setViewMode('list'); }); // 批量操作 document.getElementById('bulkActionsBtn').addEventListener('click', () => { this.showBulkActions(); }); document.getElementById('bulkCancelBtn').addEventListener('click', () => { this.hideBulkActions(); }); document.getElementById('bulkDeleteBtn').addEventListener('click', () => { if (confirm(`确定要删除选中的 ${this.selectedUrls.size} 个网址吗?`)) { this.deleteSelectedUrls(); } }); document.getElementById('bulkOpenBtn').addEventListener('click', () => { this.openSelectedUrls(); }); // 导入/导出 document.getElementById('importBtn').addEventListener('click', () => { this.showImportExportModal('import'); }); document.getElementById('exportBtn').addEventListener('click', () => { this.showImportExportModal('export'); }); // 模态框关闭 document.querySelectorAll('.close-modal').forEach(btn => { btn.addEventListener('click', () => { this.closeAllModals(); }); }); // 网址表单提交 - 使用bind确保正确的this上下文 document.getElementById('urlForm').addEventListener('submit', (e) => { e.preventDefault(); this.handleUrlFormSubmit(); }); // 分类表单提交 document.getElementById('categoryForm').addEventListener('submit', (e) => { e.preventDefault(); this.handleCategoryFormSubmit(); }); // 标签输入 document.getElementById('tagsInput').addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); this.addTagToInput(e.target.value); e.target.value = ''; } }); // 文件导入 document.getElementById('jsonFileInput').addEventListener('change', (e) => { this.handleFileImport(e.target.files[0]); }); // 添加调试日志 console.log('事件监听器已设置完成'); // 事件委托 document.addEventListener('click', (e) => { // 分类点击 if (e.target.closest('.category-item')) { const categoryItem = e.target.closest('.category-item'); const categoryId = categoryItem.dataset.id; this.setCurrentCategory(categoryId); } // 标签点击 if (e.target.closest('.tag')) { const tag = e.target.closest('.tag').dataset.tag; this.setCurrentTag(tag); } // 网址卡片操作 if (e.target.closest('.url-card')) { const urlCard = e.target.closest('.url-card'); const urlId = urlCard.dataset.id; // 复选框选择 if (e.target.type === 'checkbox') { this.toggleUrlSelection(urlId, e.target.checked); } // 收藏按钮 if (e.target.closest('.favorite-btn')) { e.preventDefault(); e.stopPropagation(); this.toggleFavorite(urlId); } // 编辑按钮 if (e.target.closest('.edit-url')) { e.preventDefault(); e.stopPropagation(); this.editUrl(urlId); } // 删除按钮 if (e.target.closest('.delete-url')) { e.preventDefault(); e.stopPropagation(); if (confirm('确定要删除这个网址吗?')) { this.deleteUrl(urlId); } } // 打开网址按钮 if (e.target.closest('.open-url')) { e.preventDefault(); e.stopPropagation(); this.openUrl(urlId); } } // 导入/导出标签页切换 if (e.target.closest('.tab-btn')) { const tabBtn = e.target.closest('.tab-btn'); const tab = tabBtn.dataset.tab; this.switchImportExportTab(tab); } // 下载JSON按钮 if (e.target.id === 'downloadJsonBtn') { this.downloadJson(); } }); } // 模态框方法 showUrlModal(urlId = null) { this.editingUrlId = urlId; const modal = document.getElementById('urlModal'); const title = document.getElementById('modalTitle'); const form = document.getElementById('urlForm'); if (urlId) { // 编辑模式 title.textContent = '编辑网址'; const url = this.urls.find(u => u.id === urlId); if (url) { document.getElementById('urlTitle').value = url.title; document.getElementById('urlAddress').value = url.url; document.getElementById('urlDescription').value = url.description || ''; document.getElementById('urlCategory').value = url.category || ''; document.getElementById('urlIcon').value = url.icon || 'fas fa-globe'; document.getElementById('urlFavorite').checked = url.favorite || false; // 设置标签 const tagsDisplay = document.getElementById('tagsDisplay'); tagsDisplay.innerHTML = ''; if (url.tags) { url.tags.forEach(tag => { this.addTagToDisplay(tag); }); } } } else { // 添加模式 title.textContent = '添加网址'; form.reset(); document.getElementById('tagsDisplay').innerHTML = ''; } modal.classList.add('active'); } showCategoryModal() { const modal = document.getElementById('categoryModal'); modal.classList.add('active'); } showImportExportModal(tab = 'export') { const modal = document.getElementById('importExportModal'); const title = document.getElementById('importExportTitle'); if (tab === 'export') { title.textContent = '导出数据'; this.updateExportPreview(); } else { title.textContent = '导入数据'; } this.switchImportExportTab(tab); modal.classList.add('active'); } closeAllModals() { document.querySelectorAll('.modal').forEach(modal => { modal.classList.remove('active'); }); this.editingUrlId = null; // 强制触发重排,确保弹窗关闭 void document.body.offsetHeight; } // 表单处理 handleUrlFormSubmit() { const title = document.getElementById('urlTitle').value.trim(); const url = document.getElementById('urlAddress').value.trim(); const description = document.getElementById('urlDescription').value.trim(); const category = document.getElementById('urlCategory').value; const icon = document.getElementById('urlIcon').value; const favorite = document.getElementById('urlFavorite').checked; // 获取标签 const tags = []; document.querySelectorAll('#tagsDisplay .tag-input-tag').forEach(tagEl => { const span = tagEl.querySelector('span'); if (span) { tags.push(span.textContent); } }); if (!title || !url) { alert('标题和网址是必填项'); return false; } const urlData = { title, url, description, category, icon, tags, favorite }; // 保存数据(只对核心保存操作进行错误处理) try { if (this.editingUrlId) { const success = this.updateUrl(this.editingUrlId, urlData); if (!success) { alert('更新失败:未找到要更新的网址'); return false; } } else { this.addUrl(urlData); } // 如果执行到这里,说明保存成功(addUrl/updateUrl 没有抛出异常) // render() 已经在 addUrl/updateUrl 中调用,页面应该已经刷新 } catch (error) { console.error('保存网址时出错:', error); // 只有在真正的保存操作失败时才提示 alert('保存失败,请重试'); return false; } // 重置表单(这些操作不应该影响保存结果) try { const form = document.getElementById('urlForm'); if (form) { form.reset(); } const tagsDisplay = document.getElementById('tagsDisplay'); if (tagsDisplay) { tagsDisplay.innerHTML = ''; } } catch (error) { console.warn('重置表单时出错:', error); // 不影响保存结果,继续执行 } // 关闭弹窗(addUrl/updateUrl 已经调用了 render() 刷新页面) this.closeAllModals(); return false; } handleCategoryFormSubmit() { const name = document.getElementById('categoryName').value.trim(); const color = document.getElementById('categoryColor').value; const icon = document.getElementById('categoryIcon').value; if (!name) { alert('分类名称是必填项'); return; } const categoryData = { name, color, icon }; this.addCategory(categoryData); this.closeAllModals(); } // 标签处理 addTagToInput(tagText) { if (!tagText.trim()) return; const tagsDisplay = document.getElementById('tagsDisplay'); const existingTags = Array.from(tagsDisplay.querySelectorAll('.tag-input-tag span')) .map(span => span.textContent); if (!existingTags.includes(tagText.trim())) { this.addTagToDisplay(tagText.trim()); } } addTagToDisplay(tagText) { const tagsDisplay = document.getElementById('tagsDisplay'); const tagEl = document.createElement('div'); tagEl.className = 'tag-input-tag'; tagEl.innerHTML = ` ${tagText} `; tagEl.querySelector('button').addEventListener('click', () => { tagEl.remove(); }); tagsDisplay.appendChild(tagEl); } // 文件导入/导出 handleFileImport(file) { if (!file) return; const reader = new FileReader(); reader.onload = async (e) => { try { const data = JSON.parse(e.target.result); console.log('导入数据:', data); if (confirm('导入数据将覆盖当前的所有数据,确定要继续吗?')) { // 更新数据 if (data.urls) { this.urls = data.urls; console.log('导入网址数量:', this.urls.length); } else { this.urls = []; } if (data.categories) { this.categories = data.categories; console.log('导入分类数量:', this.categories.length); } else { this.categories = []; } // 等待数据保存到服务器 console.log('开始保存数据到服务器...'); await this.saveData(); console.log('数据保存完成'); // 验证保存是否成功 const verifyResponse = await fetch('/api/urls'); if (verifyResponse.ok) { const savedData = await verifyResponse.json(); console.log('服务器上的数据:', savedData); console.log('服务器网址数量:', savedData.urls ? savedData.urls.length : 0); } this.render(); this.closeAllModals(); alert(`数据导入成功!已导入 ${this.urls.length} 个网址,${this.categories.length} 个分类,并已保存到服务器!`); } } catch (error) { console.error('导入数据失败:', error); alert('文件格式错误或保存失败: ' + error.message); } }; reader.readAsText(file); } updateExportPreview() { const preview = document.getElementById('exportDataPreview'); const data = { urls: this.urls, categories: this.categories, exportedAt: new Date().toISOString() }; preview.textContent = JSON.stringify(data, null, 2); } downloadJson() { const data = { urls: this.urls, categories: this.categories, exportedAt: new Date().toISOString() }; const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `url-manager-backup-${new Date().toISOString().split('T')[0]}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } switchImportExportTab(tab) { // 更新按钮状态 document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tab); }); // 更新内容显示 document.querySelectorAll('.tab-content').forEach(content => { content.classList.toggle('active', content.id === `${tab}Tab`); }); } // 其他方法 setCurrentCategory(categoryId) { this.currentCategory = categoryId; this.render(); // 更新UI状态 document.querySelectorAll('.category-item').forEach(item => { item.classList.toggle('active', item.dataset.id === categoryId); }); } setCurrentTag(tag) { if (this.currentTag === tag) { this.currentTag = ''; // 取消选择 } else { this.currentTag = tag; } this.renderUrls(); this.renderTags(); } setViewMode(mode) { this.viewMode = mode; const urlsContainer = document.getElementById('urlsContainer'); const gridBtn = document.getElementById('gridViewBtn'); const listBtn = document.getElementById('listViewBtn'); urlsContainer.classList.remove('grid-view', 'list-view'); urlsContainer.classList.add(`${mode}-view`); gridBtn.classList.toggle('active', mode === 'grid'); listBtn.classList.toggle('active', mode === 'list'); } toggleUrlSelection(urlId, checked) { if (checked) { this.selectedUrls.add(urlId); } else { this.selectedUrls.delete(urlId); } this.updateBulkActions(); this.renderUrls(); } updateBulkActions() { const selectedCount = document.getElementById('selectedCount'); const bulkPanel = document.getElementById('bulkActionsPanel'); selectedCount.textContent = `已选择 ${this.selectedUrls.size} 个项目`; if (this.selectedUrls.size > 0) { bulkPanel.classList.add('active'); } else { bulkPanel.classList.remove('active'); } } showBulkActions() { // 显示批量操作面板 const bulkPanel = document.getElementById('bulkActionsPanel'); bulkPanel.classList.add('active'); } hideBulkActions() { // 清除选择并隐藏面板 this.selectedUrls.clear(); const bulkPanel = document.getElementById('bulkActionsPanel'); bulkPanel.classList.remove('active'); this.renderUrls(); } openUrl(urlId) { const url = this.urls.find(u => u.id === urlId); if (url) { window.open(url.url, '_blank'); // 更新访问记录 url.lastAccessed = new Date().toISOString(); url.accessCount = (url.accessCount || 0) + 1; this.saveData(); } } openSelectedUrls() { this.urls.forEach(url => { if (this.selectedUrls.has(url.id)) { window.open(url.url, '_blank'); // 更新访问记录 url.lastAccessed = new Date().toISOString(); url.accessCount = (url.accessCount || 0) + 1; } }); this.saveData(); } editUrl(urlId) { this.showUrlModal(urlId); } toggleTheme() { const currentTheme = document.documentElement.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; const themeIcon = document.querySelector('#themeToggle i'); document.documentElement.setAttribute('data-theme', newTheme); themeIcon.className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; localStorage.setItem('urlManager_theme', newTheme); } setupKeyboardShortcuts() { document.addEventListener('keydown', (e) => { // Ctrl+F 聚焦搜索框 if (e.ctrlKey && e.key === 'f') { e.preventDefault(); document.getElementById('searchInput').focus(); } // Ctrl+N 添加新网址 if (e.ctrlKey && e.key === 'n') { e.preventDefault(); this.showUrlModal(); } // Esc 关闭模态框或取消批量选择 if (e.key === 'Escape') { if (this.selectedUrls.size > 0) { this.hideBulkActions(); } else { this.closeAllModals(); } } // Ctrl+S 保存数据(导出) if (e.ctrlKey && e.key === 's') { e.preventDefault(); this.showImportExportModal('export'); } }); // 加载保存的主题 const savedTheme = localStorage.getItem('urlManager_theme') || 'light'; document.documentElement.setAttribute('data-theme', savedTheme); const themeIcon = document.querySelector('#themeToggle i'); themeIcon.className = savedTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; } } // 初始化应用 document.addEventListener('DOMContentLoaded', async () => { window.urlManager = new UrlManager(); await window.urlManager.init(); });