书签导航转换器

将浏览器书签转换为美观的导航网站

上传书签文件 ℹ️
书签解析成功!共检测到 0 个文件夹和 0 个书签。

拖放书签文件到此处

或点击下方按钮选择本地文件

超过10MB可能需要更长时间处理

支持所有导出的书签文件格式 (Chrome, Firefox, Edge, IE, Safari)

导出书签指南:

  • Chrome: 点击右上角三个点 → 书签 → 管理书签 → 点击右上角三个点 → 导出书签
  • Firefox: 点击右上角三个横线 → 书签 → 显示所有书签 → 点击右上角三个点 → 备份 → 导出书签HTML
  • Edge: 点击右上角三个点 → 收藏夹 → 点击右上角三个点 → 导出收藏夹
  • IE: 点击右上角齿轮 → 导入和导出 → 导出收藏夹
  • Safari: 点击菜单栏 "文件" → 导出书签

推荐浏览器扩展:

请确保粘贴的是完整的书签HTML内容

内容格式说明:

粘贴的内容应该以 <!DOCTYPE NETSCAPE-Bookmark-file-1> 开头,包含完整的HTML结构。

等待输入书签内容...
自定义选项
大图标预览
大图标预览
小图标预览
小图标预览

修改书签图标

当前图标: 当前图标
预览效果

生成导航页面后,将在此处显示预览

预览说明

  • 点击 "预览网站" 可查看完整导航页面效果
  • 点击 "移动端预览" 可模拟移动设备上的显示效果
  • 点击 "预览源码" 可查看生成的 HTML 代码
一键部署设置

下载模式:自动下载HTML文件后跳转到部署平台

点击按钮后根据选择的部署方式自动执行,请根据提示完成后续操作

HTML转网页工具:

facaitool.my anytoweb.com pinme.eth.limo neocities.org luangao.de
设置

自动移除书签中的分界线项目,如 "---"、"===" 等分隔符

每行一个规则,支持正则表达式语法

勾选后在修改设置或图标时会自动更新导航页面

为书签项添加随机编号,提高导航效率和访问体验

为重复下载的文件自动添加序号,默认关闭

选择文件名中空格的处理方式

开启操作音效和提示音

开启操作结果的语音通知

开启界面动画和过渡效果

上传书签时自动过滤无效的base64图标

每行一个规则,支持正则表达式语法

`; generatedHTML = html; previewContainer.innerHTML = html; // 保存生成的HTML到localStorage try { localStorage.setItem('generatedHTML', generatedHTML); } catch (e) { console.error('保存生成的HTML到localStorage失败:', e); } // 启用下载和复制按钮 downloadBtn.disabled = false; copyCodeBtn.disabled = false; // 启用一键部署按钮 document.getElementById('deployBtn').disabled = false; showStatus('导航页面生成成功', 'success'); // 播放成功音效 SoundManager.play('success'); } // 下载HTML文件 function downloadHTML() { // 添加防重复下载标志 if (window.isDownloading) { console.log('下载已在进行中,跳过重复下载'); return; } window.isDownloading = true; const blob = new Blob([generatedHTML], {type: 'text/html'}); const url = URL.createObjectURL(blob); // 生成文件名,默认格式为 "导航页_年月日" let baseFileName = '导航页_' + new Date().toISOString().slice(0, 10).replace(/-/g, ''); // 优先使用自定义区域的网站标题(根据需求重点使用) const siteTitle = document.getElementById('siteTitle')?.value.trim() || ''; if (siteTitle) { // 去除标题中的特殊字符,避免文件名非法 const cleanSiteTitle = siteTitle.replace(/[\\/*?:"<>|]/g, ''); if (cleanSiteTitle) { baseFileName = cleanSiteTitle; } } // 其次尝试使用书签标题 else if (bookmarkTitle) { // 去掉尾部常见词,支持更多常见后缀,如:收藏夹、收藏、Favorites等 const commonSuffixes = ['的书签', '书签栏', 'Bookmarks', '收藏夹', '收藏', 'Favorites', '收藏列表', 'Links']; const suffixPattern = new RegExp(`(${commonSuffixes.join('|')})$`, 'i'); let cleanTitle = bookmarkTitle.replace(suffixPattern, '').trim(); // 去除标题中的特殊字符,避免文件名非法 cleanTitle = cleanTitle.replace(/[\\/*?:"<>|]/g, ''); if (cleanTitle) { baseFileName = cleanTitle; } } // 如果标题处理后为空,尝试使用文件名 else if (bookmarkFileName) { baseFileName = bookmarkFileName; } // 从localStorage获取下载历史记录 let downloadHistory = {}; try { const savedHistory = localStorage.getItem('downloadHistory'); if (savedHistory) { downloadHistory = JSON.parse(savedHistory); } } catch (e) { console.error('读取下载历史失败:', e); } // 生成文件名 let fileName = baseFileName; // 检查是否启用了文件名编号系统 const enableFileNumbering = localStorage.getItem('enableFileNumbering') === 'true'; if (enableFileNumbering) { // 检查是否是第二次下载相同名称的文件 if (downloadHistory[baseFileName]) { // 增加下载计数 downloadHistory[baseFileName]++; // 生成带编号的文件名 fileName = `${baseFileName}_${downloadHistory[baseFileName]}`; } else { // 首次下载此文件名,记录到历史 downloadHistory[baseFileName] = 1; } // 保存更新后的下载历史 try { localStorage.setItem('downloadHistory', JSON.stringify(downloadHistory)); } catch (e) { console.error('保存下载历史失败:', e); } } // 应用文件命名模式 const fileNameMode = localStorage.getItem('fileNameMode') || 'keep'; switch (fileNameMode) { case 'replace': // 空格替换为下划线 fileName = fileName.replace(/\s+/g, '_'); break; case 'remove': // 移除所有空格 fileName = fileName.replace(/\s+/g, ''); break; case 'dash': // 空格替换为横线 fileName = fileName.replace(/\s+/g, '-'); break; case 'keep': default: // 保持原样 break; } // 确保文件名以.html结尾 if (!fileName.endsWith('.html')) { fileName += '.html'; } const a = document.createElement('a'); a.href = url; a.download = fileName; document.body.appendChild(a); a.click(); // Use a safer approach to clean up the element // Set a small timeout to ensure the download starts before removing setTimeout(() => { try { // Only remove if the element is still in the DOM if (a.parentNode === document.body) { document.body.removeChild(a); } } catch (e) { // Silently ignore removal errors } window.isDownloading = false; }, 100); URL.revokeObjectURL(url); // 显示文件下载成功的状态消息,并调用显示成功消息的函数 SoundManager.play('download'); showStatus('文件已下载: ' + fileName, 'success'); showToast(`文件已下载: ${fileName}`, 'success'); showSuccess(); } // 复制HTML代码 function copyHTMLCode() { navigator.clipboard.writeText(generatedHTML).then(() => { SoundManager.play('success'); showStatus('代码已复制到剪贴板', 'success'); showToast('代码已复制到剪贴板', 'success'); }).catch(() => { SoundManager.play('error'); showToast('复制失败,请手动复制', 'error'); }); } // 一键部署功能:根据用户设置选择部署工具和部署方式 function deployWebsite() { // 检查是否已生成导航页面 if (!generatedHTML) { SoundManager.play('error'); showStatus('请先生成导航页面', 'error'); return; } // 防重复部署标志 if (window.isDeploying) { console.log('部署已在进行中,跳过重复操作'); return; } window.isDeploying = true; SoundManager.play('deploy'); // 获取用户选择的部署模式 const deployModeSelector = document.getElementById('deployModeSelector'); const deployToolSelector = document.getElementById('deployToolSelector'); const validTools = ['facaitool', 'anytoweb', 'Deploy', 'luangao', 'neocities']; // 优先使用localStorage中的值 const savedTool = localStorage.getItem('selectedDeployTool'); let deployTool = validTools.includes(savedTool) ? savedTool : 'facaitool'; // 如果有活动的选择器且其值有效,则使用其值覆盖 if (deployModeSelector && validTools.includes(deployModeSelector.value)) { deployTool = deployModeSelector.value; } else if (deployToolSelector && validTools.includes(deployToolSelector.value)) { deployTool = deployToolSelector.value; } // 确保两个选择器都显示正确的值 if (deployModeSelector && deployModeSelector.value !== deployTool) { deployModeSelector.value = deployTool; } if (deployToolSelector && deployToolSelector.value !== deployTool) { deployToolSelector.value = deployTool; } // 保存最终选定的值到localStorage localStorage.setItem('selectedDeployTool', deployTool); // 获取部署方式(下载或复制) const deployMethodSelector = document.getElementById('deployMethodSelector'); const deployMethod = deployMethodSelector ? deployMethodSelector.value : 'download'; // 根据选择的部署工具获取URL const platformMap = { facaitool: { name: 'FacaiTool', url: 'https://facaitool.my/' }, anytoweb: { name: 'AnyToWeb', url: 'https://www.anytoweb.com/' }, Deploy: { name: 'PinMe', url: 'https://pinme.eth.limo/' }, luangao: { name: 'LuanGao', url: 'https://www.luangao.de/' }, neocities: { name: 'Neocities', url: 'https://neocities.org/dashboard' } }; const platform = platformMap[deployTool] || platformMap['facaitool']; // 根据部署方式执行不同操作 if (deployMethod === 'copy') { // 复制模式:复制代码到剪贴板,然后跳转 if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(generatedHTML).then(() => { SoundManager.play('success'); showStatus(`代码已复制,正在跳转到 ${platform.name}...`, 'success'); // 2秒后跳转到部署平台 setTimeout(() => { window.open(platform.url, '_blank'); showStatus('请在部署平台粘贴代码完成部署', 'info'); // 5秒后重置部署标志,允许下次部署 setTimeout(() => { window.isDeploying = false; }, 5000); }, 2000); }).catch(() => { SoundManager.play('error'); showStatus('复制代码失败,请手动复制或使用下载模式', 'error'); window.isDeploying = false; }); } else { // 降级处理 SoundManager.play('warning'); showStatus('浏览器不支持自动复制,已自动切换到下载模式', 'warning'); // 自动切换到下载模式 const deployMethodSelector = document.getElementById('deployMethodSelector'); if (deployMethodSelector) deployMethodSelector.value = 'download'; updateDeployMethodHint(); // 延迟后执行下载模式 setTimeout(() => deployWithDownload(platform, 'downloadFirst'), 500); // 注意:下载模式内部会处理isDeploying的重置 } } else if (deployMethod === 'download') { // 下载模式:先下载文件,然后跳转 deployWithDownload(platform, 'downloadFirst'); } else if (deployMethod === 'downloadAfter') { // 跳转后下载模式:先跳转到部署平台,然后下载文件 deployWithDownload(platform, 'downloadAfter'); } } // 下载模式部署 function deployWithDownload(platform, mode = 'downloadFirst') { // 生成文件名 let baseFileName = '导航页_' + new Date().toISOString().slice(0, 10).replace(/-/g, ''); // 优先使用自定义区域的网站标题 const siteTitle = document.getElementById('siteTitle')?.value.trim() || ''; if (siteTitle) { const cleanSiteTitle = siteTitle.replace(/[\\/*?:"<>|]/g, ''); if (cleanSiteTitle) baseFileName = cleanSiteTitle; } // 其次尝试使用书签标题 else if (bookmarkTitle) { const commonSuffixes = ['的书签', '书签栏', 'Bookmarks', '收藏夹', '收藏', 'Favorites', '收藏列表', 'Links']; const suffixPattern = new RegExp(`(${commonSuffixes.join('|')})$`, 'i'); let cleanTitle = bookmarkTitle.replace(suffixPattern, '').trim(); cleanTitle = cleanTitle.replace(/[\\/*?:"<>|]/g, ''); if (cleanTitle) baseFileName = cleanTitle; } // 如果标题处理后为空,尝试使用文件名 else if (bookmarkFileName) { baseFileName = bookmarkFileName; } // 从localStorage获取下载历史记录 let downloadHistory = {}; try { const savedHistory = localStorage.getItem('downloadHistory'); if (savedHistory) downloadHistory = JSON.parse(savedHistory); } catch (e) { console.error('读取下载历史失败:', e); } // 生成文件名 let fileName = baseFileName; const enableFileNumbering = localStorage.getItem('enableFileNumbering') === 'true'; if (enableFileNumbering) { if (downloadHistory[baseFileName]) { downloadHistory[baseFileName]++; fileName = `${baseFileName}_${downloadHistory[baseFileName]}`; } else { downloadHistory[baseFileName] = 1; } try { localStorage.setItem('downloadHistory', JSON.stringify(downloadHistory)); } catch (e) { console.error('保存下载历史失败:', e); } } // 应用文件命名模式 const fileNameMode = localStorage.getItem('fileNameMode') || 'keep'; switch (fileNameMode) { case 'replace': // 空格替换为下划线 fileName = fileName.replace(/\s+/g, '_'); break; case 'remove': // 移除所有空格 fileName = fileName.replace(/\s+/g, ''); break; case 'dash': // 空格替换为横线 fileName = fileName.replace(/\s+/g, '-'); break; case 'keep': default: // 保持原样 break; } // 确保文件名以.html结尾 if (!fileName.endsWith('.html')) fileName += '.html'; // 创建下载 const blob = new Blob([generatedHTML], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = fileName; // 添加防重复下载标志 if (window.isDownloading) { console.log('下载已在进行中,跳过重复下载'); return; } window.isDownloading = true; if (mode === 'downloadFirst') { // 先下载后跳转 document.body.appendChild(a); a.click(); SoundManager.play('download'); showStatus(`文件已下载,正在跳转到 ${platform.name}...`, 'success'); // 等待下载开始后跳转 setTimeout(() => { try { if (document.body.contains(a)) { document.body.removeChild(a); } } catch (e) { console.log('元素已被移除或不在DOM中'); } URL.revokeObjectURL(url); window.isDownloading = false; // 跳转到部署平台 window.open(platform.url, '_blank'); showStatus('请在部署平台上传刚才下载的文件进行部署', 'info'); // 显示免责声明 setTimeout(() => { showStatus('免责声明:由于技术手段,本程序使用了跳转式进入部署平台,请根据平台规定进行部署,如果违规不归本程序管,因为是你自己行为。这个部署平台不归本程序管,因为属于第三方平台。', 'info'); }, 1000); // 重置部署标志,允许下次部署 window.isDeploying = false; }, 2500); } else if (mode === 'downloadAfter') { // 先跳转后下载 showStatus(`正在跳转到 ${platform.name},稍后开始下载...`, 'success'); // 跳转到部署平台 window.open(platform.url, '_blank'); // 显示免责声明 setTimeout(() => { showStatus('免责声明:由于技术手段,本程序使用了跳转式进入部署平台,请根据平台规定进行部署,如果违规不归本程序管,因为是你自己行为。这个部署平台不归本程序管,因为属于第三方平台。', 'info'); }, 500); // 延迟后开始下载 setTimeout(() => { document.body.appendChild(a); a.click(); SoundManager.play('download'); showStatus('文件已开始下载,请在部署平台上传文件进行部署', 'success'); // 清理 setTimeout(() => { try { if (document.body.contains(a)) { document.body.removeChild(a); } } catch (e) { console.log('元素已被移除或不在DOM中'); } URL.revokeObjectURL(url); window.isDownloading = false; }, 1000); }, 1000); // 重置部署标志,允许下次部署 window.isDeploying = false; } } // 一键部署备用方案:跳转到Neocities function deployWebsiteAlternative() { // 检查是否已生成导航页面 if (!generatedHTML) { showStatus('请先生成导航页面', 'error'); return; } // 检查剪贴板API是否可用 if (navigator.clipboard && navigator.clipboard.writeText) { // 使用现代剪贴板API navigator.clipboard.writeText(generatedHTML).then(() => { // 显示通知 showStatus('代码已复制到剪贴板,正在跳转到部署平台...', 'success'); // 2秒后跳转到Neocities部署网站 setTimeout(() => { window.open('https://neocities.org/dashboard', '_blank'); // 显示免责声明 setTimeout(() => { showStatus('免责声明:由于技术手段,本程序使用了跳转式进入部署平台,请根据平台规定进行部署,如果违规不归本程序管,因为是你自己行为。这个部署平台不归本程序管,因为属于第三方平台。', 'info'); }, 500); }, 2000); }).catch(() => { showStatus('复制代码失败,请手动复制后点击部署按钮', 'error'); }); } else { // 降级处理:显示提示信息,让用户手动复制 showStatus('浏览器不支持自动复制,请手动复制代码后点击部署按钮', 'error'); // 仍然跳转到Neocities部署网站 setTimeout(() => { window.open('https://neocities.org/dashboard', '_blank'); // 显示免责声明 setTimeout(() => { showStatus('免责声明:由于技术手段,本程序使用了跳转式进入部署平台,请根据平台规定进行部署,如果违规不归本程序管,因为是你自己行为。这个部署平台不归本程序管,因为属于第三方平台。', 'info'); }, 500); }, 2000); } } // 显示成功消息 function showSuccess() { successMessage.style.display = 'block'; } // 显示状态消息 function showStatus(message, type = 'info') { // 更新原有状态栏 statusBar.textContent = message; statusBar.style.color = type === 'error' ? '#e74c3c' : type === 'success' ? '#2ecc71' : type === 'warning' ? '#f39c12' : '#6c757d'; // 播放音效 SoundManager.play(type); // 播放语音通知(所有类型的通知都会播放语音) speakNotification(message); // 根据消息长度计算显示时间 // 中文语音平均每秒约3-4个字,加上1秒缓冲 // 最低3秒,最高1分钟(60000毫秒) const estimatedDuration = Math.max(3000, Math.min(60000, message.length * 300 + 1000)); // 显示右下角toast提示(使用队列机制,按顺序消失) showToast(message, type, estimatedDuration); } // 语音通知队列 let speechQueue = []; let isSpeaking = false; // 处理语音队列 function processSpeechQueue() { if (isSpeaking || speechQueue.length === 0) return; isSpeaking = true; const message = speechQueue.shift(); const enableVoiceNotification = document.getElementById('enableVoiceNotification'); if (enableVoiceNotification && enableVoiceNotification.checked) { SoundManager.speak(message); // 估算语音播放时间(中文每秒约3-4个字) const speechDuration = Math.max(1000, message.length * 250); setTimeout(() => { isSpeaking = false; processSpeechQueue(); }, speechDuration); } else { isSpeaking = false; processSpeechQueue(); } } // 播放语音通知 function speakNotification(message) { const enableVoiceNotification = document.getElementById('enableVoiceNotification'); if (enableVoiceNotification && enableVoiceNotification.checked) { // 将消息添加到队列 speechQueue.push(message); // 限制队列长度,避免堆积过多 if (speechQueue.length > 3) { speechQueue = speechQueue.slice(-3); // 只保留最后3个 } // 开始处理队列 processSpeechQueue(); } } // 显示右下角toast提示 // 用于防止重复通知的变量 let lastToastMessage = ''; let lastToastTime = 0; const TOAST_DEBOUNCE_TIME = 500; // 500ms内相同消息不重复显示 const MAX_TOAST_COUNT = 5; // 最多同时显示5个通知 // Toast队列系统 let toastQueue = []; let activeToasts = []; const DEFAULT_TOAST_DURATION = 5000; // 默认显示5秒 function showToast(message, type = 'info', duration = DEFAULT_TOAST_DURATION) { const container = document.getElementById('toastContainer'); if (!container) return; // 防止相同消息在短时间内重复显示 const now = Date.now(); if (message === lastToastMessage && now - lastToastTime < TOAST_DEBOUNCE_TIME) { return; } lastToastMessage = message; lastToastTime = now; // 将通知添加到队列 toastQueue.push({ message, type, duration, id: Date.now() + Math.random() }); // 处理队列 processToastQueue(); } // 处理Toast队列 function processToastQueue() { const container = document.getElementById('toastContainer'); if (!container) return; // 如果已经有最大数量的通知在显示,等待 if (activeToasts.length >= MAX_TOAST_COUNT) { return; } // 处理队列中的通知 while (toastQueue.length > 0 && activeToasts.length < MAX_TOAST_COUNT) { const toastData = toastQueue.shift(); createToast(toastData); } } // 创建Toast function createToast(toastData) { const container = document.getElementById('toastContainer'); const { message, type, duration, id } = toastData; // 创建toast元素 const toast = document.createElement('div'); toast.className = `toast-notification ${type}`; toast.style.position = 'relative'; toast.dataset.toastId = id; // 根据类型选择图标 let iconClass = 'fa-info-circle'; if (type === 'success') iconClass = 'fa-check-circle'; else if (type === 'error') iconClass = 'fa-times-circle'; else if (type === 'warning') iconClass = 'fa-exclamation-triangle'; toast.innerHTML = `
${message}
`; // 添加到容器 container.appendChild(toast); // 添加到活动通知列表 activeToasts.push({ id, element: toast, duration, startTime: Date.now() }); // 触发动画显示 requestAnimationFrame(() => { toast.classList.add('show'); }); // 点击关闭按钮 const closeBtn = toast.querySelector('.toast-close'); if (closeBtn) { closeBtn.addEventListener('click', () => { removeToast(id); }); } // 检查是否只有这一个通知,如果是则启动倒计时 if (activeToasts.length === 1) { startToastTimer(id, duration); } } // 启动Toast倒计时 function startToastTimer(id, duration) { const toastData = activeToasts.find(t => t.id === id); if (!toastData) return; const { element } = toastData; const progressBar = element.querySelector('.toast-progress'); // 启动进度条动画 if (progressBar) { progressBar.style.transition = 'none'; progressBar.style.width = '0%'; requestAnimationFrame(() => { progressBar.style.transition = `width ${duration}ms linear`; progressBar.style.width = '100%'; }); } // 设置自动关闭 toastData.timer = setTimeout(() => { removeToast(id); }, duration); } // 移除Toast function removeToast(id) { const index = activeToasts.findIndex(t => t.id === id); if (index === -1) return; const toastData = activeToasts[index]; const { element, timer } = toastData; // 清除定时器 if (timer) { clearTimeout(timer); } // 从活动列表中移除 activeToasts.splice(index, 1); // 关闭动画 closeToast(element); // 如果这是第一个通知,为下一个通知启动倒计时 if (index === 0 && activeToasts.length > 0) { const nextToast = activeToasts[0]; const remainingTime = nextToast.duration - (Date.now() - nextToast.startTime); startToastTimer(nextToast.id, Math.max(1000, remainingTime)); } // 继续处理队列 setTimeout(() => { processToastQueue(); }, 100); } // 关闭toast function closeToast(toast) { toast.classList.remove('show'); toast.classList.add('removing'); setTimeout(() => { if (toast.parentElement) { toast.remove(); } }, 400); // 等待动画完成 } // 更新部署方式提示文本和样式 function updateDeployMethodHint() { const hintElement = document.getElementById('deployMethodHint'); const deployMethodSelector = document.getElementById('deployMethodSelector'); // 更新提示文本 if (hintElement && deployMethodSelector) { const selectedMethod = deployMethodSelector.value; if (selectedMethod === 'download') { hintElement.textContent = '下载后跳转:先下载HTML文件,然后自动跳转到部署平台'; } else if (selectedMethod === 'downloadAfter') { hintElement.textContent = '跳转后下载:先跳转到部署平台,然后自动下载HTML文件'; } else if (selectedMethod === 'copy') { hintElement.textContent = '复制代码:自动复制代码到剪贴板后跳转到部署平台'; } } } // 初始化部署工具选择器并实现联动 function initDeployToolSelector() { const deployModeSelector = document.getElementById('deployModeSelector'); const deployToolSelector = document.getElementById('deployToolSelector'); const validTools = ['facaitool', 'anytoweb', 'Deploy','luangao', 'neocities']; // 从localStorage中读取保存的选项 const savedTool = localStorage.getItem('selectedDeployTool'); let toolToUse = savedTool && validTools.includes(savedTool) ? savedTool : 'facaitool'; // 确保localStorage中有有效值 localStorage.setItem('selectedDeployTool', toolToUse); // 更新两个选择器的值,确保它们同步 if (deployModeSelector) { deployModeSelector.value = toolToUse; } if (deployToolSelector) { deployToolSelector.value = toolToUse; } // 为deployModeSelector添加事件监听器,实现联动 if (deployModeSelector) { deployModeSelector.addEventListener('change', function() { const selectedValue = this.value; if (validTools.includes(selectedValue)) { localStorage.setItem('selectedDeployTool', selectedValue); // 同步更新另一个选择器 if (deployToolSelector && deployToolSelector.value !== selectedValue) { deployToolSelector.value = selectedValue; } console.log('部署工具已更改为:', selectedValue); } }); } // 为deployToolSelector添加事件监听器,实现联动 if (deployToolSelector) { deployToolSelector.addEventListener('change', function() { const selectedValue = this.value; if (validTools.includes(selectedValue)) { localStorage.setItem('selectedDeployTool', selectedValue); // 同步更新另一个选择器 if (deployModeSelector && deployModeSelector.value !== selectedValue) { deployModeSelector.value = selectedValue; } console.log('部署工具已更改为:', selectedValue); } }); } // 如果两个选择器都不存在,记录警告 if (!deployModeSelector && !deployToolSelector) { console.warn('两个部署工具选择器元素都未找到'); } } // 初始化事件监听 function initEventListeners() { // 初始化图标API选择器 initIconApiSelector(); // 初始化部署工具选择器 initDeployToolSelector(); // 添加事件监听器 // 添加自定义列数逻辑 const columnsSelect = document.getElementById('columns'); const customColumnsInput = document.getElementById('customColumns'); columnsSelect.addEventListener('change', () => { if (columnsSelect.value === 'custom') { customColumnsInput.style.display = 'block'; } else { customColumnsInput.style.display = 'none'; } }); // 添加标签切换功能 const tabs = document.querySelectorAll('.tab'); const inputSections = document.querySelectorAll('.input-section'); tabs.forEach(tab => { tab.addEventListener('click', () => { const target = tab.getAttribute('data-target'); // 移除所有标签的active类 tabs.forEach(t => t.classList.remove('active')); // 给当前标签添加active类 tab.classList.add('active'); // 隐藏所有输入区域 inputSections.forEach(section => section.classList.remove('active')); // 显示目标输入区域 document.getElementById(target).classList.add('active'); }); }); // 添加预览切换功能 const previewWebsiteBtn = document.getElementById('previewWebsiteBtn'); const previewCodeBtn = document.getElementById('previewCodeBtn'); // 假设 previewContainer 已在别处声明,此处移除重复声明 const previewContainer = document.getElementById('previewContainer'); let currentPreviewMode = 'website'; // 默认预览网站 previewWebsiteBtn.addEventListener('click', () => { if (currentPreviewMode !== 'website') { currentPreviewMode = 'website'; previewWebsiteBtn.classList.add('active-preview'); previewCodeBtn.classList.remove('active-preview'); previewMobileBtn.classList.remove('active-preview'); // 重置容器样式 previewContainer.style.width = '100%'; previewContainer.style.height = 'auto'; previewContainer.style.border = 'none'; previewContainer.style.borderRadius = '0'; previewContainer.style.boxShadow = 'none'; previewContainer.style.padding = '0'; // 显示网站预览 if (generatedHTML) { previewContainer.innerHTML = generatedHTML; showStatus('已切换到桌面端预览', 'success'); } else { showStatus('请先生成导航页面', 'warning'); } } }); // 移动端预览功能 const previewMobileBtn = document.getElementById('previewMobileBtn'); previewMobileBtn.addEventListener('click', () => { if (currentPreviewMode !== 'mobile') { currentPreviewMode = 'mobile'; previewMobileBtn.classList.add('active-preview'); previewWebsiteBtn.classList.remove('active-preview'); previewCodeBtn.classList.remove('active-preview'); // 设置移动端预览样式 previewContainer.style.width = '375px'; previewContainer.style.height = '667px'; previewContainer.style.overflow = 'auto'; previewContainer.style.margin = '0 auto'; previewContainer.style.border = '1px solid #ddd'; previewContainer.style.borderRadius = '20px'; previewContainer.style.boxShadow = '0 0 20px rgba(0,0,0,0.1)'; previewContainer.style.padding = '20px 10px'; // 显示预览内容 if (generatedHTML) { previewContainer.innerHTML = generatedHTML; showStatus('已切换到移动端预览', 'success'); } else { showStatus('请先生成导航页面', 'warning'); } } }); previewCodeBtn.addEventListener('click', () => { if (currentPreviewMode !== 'code') { currentPreviewMode = 'code'; previewCodeBtn.classList.add('active-preview'); previewWebsiteBtn.classList.remove('active-preview'); previewMobileBtn.classList.remove('active-preview'); // 重置容器样式 previewContainer.style.width = '100%'; previewContainer.style.height = 'auto'; previewContainer.style.border = 'none'; previewContainer.style.borderRadius = '0'; previewContainer.style.boxShadow = 'none'; previewContainer.style.padding = '0'; // 显示源码预览 if (generatedHTML) { // 对生成的HTML进行转义处理,防止XSS攻击 const escapedHTML = generatedHTML .replace(/&/g, '&') // 转义&,防止HTML实体注入 .replace(//g, '>') // 转义>,防止标签注入 .replace(/"/g, '"') // 转义",防止属性注入 .replace(/'/g, ''') // 转义',防止属性注入 .replace(/`/g, '`') // 转义反引号,防止模板注入 .replace(/\(/g, '(') // 转义左括号,防止JS函数注入 .replace(/\)/g, ')') // 转义右括号,防止JS函数注入 .replace(/javascript:/gi, '') // 移除javascript:协议,防止伪协议注入 .replace(/on\w+\s*=/gi, '') // 移除所有on事件属性,防止事件注入 .replace(/data:text\/html/gi, ''); // 移除data:text/html,防止数据URI注入 // 创建一个pre元素和code元素用于格式化显示代码 const preElement = document.createElement('pre'); const codeElement = document.createElement('code'); // 设置代码元素的类名以支持语法高亮(可根据实际情况选择高亮主题) codeElement.className = 'language-html'; codeElement.textContent = escapedHTML; preElement.appendChild(codeElement); // 清空预览容器并添加格式化后的代码 previewContainer.innerHTML = ''; previewContainer.appendChild(preElement); // 尝试调用Prism.js进行语法高亮(如果已加载) if (typeof Prism !== 'undefined') { Prism.highlightAllUnder(previewContainer); } showStatus('已切换到源码预览', 'success'); } else { showStatus('请先生成导航页面', 'warning'); } } }); selectFileBtn.addEventListener('click', () => fileInputEl.click()); dropZone.addEventListener('click', () => fileInputEl.click()); fileInputEl.addEventListener('change', handleFileSelect); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('active'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('active'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('active'); if (e.dataTransfer.files.length) { fileInputEl.files = e.dataTransfer.files; handleFileSelect(); } }); parseTextBtn.addEventListener('click', parseBookmarkText); generateBtn.addEventListener('click', () => { SoundManager.play('click'); generateBookmarkHTML(); }); downloadBtn.addEventListener('click', () => { SoundManager.play('click'); downloadHTML(); }); copyCodeBtn.addEventListener('click', () => { SoundManager.play('click'); copyHTMLCode(); }); // 添加一键部署按钮事件监听器 const deployBtn = document.getElementById('deployBtn'); deployBtn.addEventListener('click', () => { SoundManager.play('click'); deployWebsite(); }); // 如果有备用部署按钮,也添加事件监听器 const deployAltBtn = document.getElementById('deployAltBtn'); if (deployAltBtn) { deployAltBtn.addEventListener('click', () => { SoundManager.play('click'); deployWebsiteAlternative(); }); } // 部署方式下拉框事件监听已在applyDefaultSettings中添加 } // 添加事件监听器 iconApiSelector.addEventListener('change', function() { // 保存用户选择的API到localStorage localStorage.setItem('selectedIconApi', this.value); // 同时同步保存到默认图标API设置,实现双向联动 localStorage.setItem('defaultIconApi', this.value); // 如果选择的是随机选项,则重新随机选择一个可用接口 if (this.value === 'random') { const availableApis = ['default', 'jxcxin', 'yuxli', 'kucat', 'faviconim', 'scdn', 'cenguigui', 'faviconkit', 'google', 'duckduckgo', 'clearbit', 'faviconsnap', 'faviconpub', 'toolb', 'freejk', 'xinac']; const randomApi = availableApis[Math.floor(Math.random() * availableApis.length)]; this.value = randomApi; localStorage.setItem('selectedIconApi', randomApi); localStorage.setItem('defaultIconApi', randomApi); } // 如果设置页面已打开,实时更新默认图标API选择器的值 const defaultApiSelector = document.getElementById('defaultApiSelector'); if (defaultApiSelector) { defaultApiSelector.value = this.value; } // 更新当前图标显示 if (currentSelectedBookmark) { updateCurrentIconDisplay(); } }); // 初始化图标API选择器 function initIconApiSelector() { // 优先从localStorage获取默认图标API设置 const savedDefaultApi = localStorage.getItem('defaultIconApi'); // 如果有默认API设置,则使用它 if (savedDefaultApi) { iconApiSelector.value = savedDefaultApi; localStorage.setItem('selectedIconApi', savedDefaultApi); return; } // 其次尝试从localStorage中读取保存的图标API选择 const savedApi = localStorage.getItem('selectedIconApi'); // 如果没有保存的选项,则随机选择一个可用接口 if (!savedApi) { const availableApis = ['default', 'jxcxin', 'yuxli', 'kucat', 'faviconim', 'scdn', 'cenguigui', 'faviconkit', 'google', 'duckduckgo', 'clearbit', 'faviconsnap', 'faviconpub', 'toolb', 'freejk', 'xinac']; const randomApi = availableApis[Math.floor(Math.random() * availableApis.length)]; iconApiSelector.value = randomApi; localStorage.setItem('selectedIconApi', randomApi); } else { // 如果有保存的选项,则使用保存的值 iconApiSelector.value = savedApi; } } // 添加深色模式切换功能 function setupThemeToggle() { // 检查本地存储中的主题偏好 const savedTheme = localStorage.getItem('preferredTheme'); const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); // 创建主题切换按钮 const themeToggle = document.createElement('button'); themeToggle.className = 'theme-toggle'; themeToggle.id = 'themeToggle'; // 根据偏好设置主题 if (savedTheme === 'dark' || (!savedTheme && prefersDarkScheme.matches)) { document.body.classList.add('dark-theme'); themeToggle.innerHTML = ''; } else { themeToggle.innerHTML = ''; } // 添加点击事件 themeToggle.addEventListener('click', () => { const isDark = document.body.classList.toggle('dark-theme'); // 更新按钮图标 themeToggle.innerHTML = isDark ? '' : ''; // 保存主题偏好 localStorage.setItem('preferredTheme', isDark ? 'dark' : 'light'); // 显示主题切换提示 showToast(isDark ? '已切换到深色模式' : '已切换到浅色模式', 'info'); }); // 将按钮添加到页面 document.body.appendChild(themeToggle); } // 优化时间显示功能 function setupAdvancedTimeDisplay() { // 检查是否已存在时间格式设置 const timeFormat = localStorage.getItem('timeFormat') || '12h'; const showSeconds = localStorage.getItem('showSeconds') !== 'false'; // 增强的时间更新函数 window.updateTime = function() { const now = new Date(); const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); let hours = now.getHours(); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); let period = ''; if (timeFormat === '12h') { period = hours >= 12 ? 'PM' : 'AM'; hours = hours % 12 || 12; } let timeString = `${year}年${month}月${day}日 `; timeString += `${String(hours).padStart(2, '0')}:${minutes}`; if (showSeconds) { timeString += `:${seconds}`; } if (timeFormat === '12h') { timeString += ` ${period}`; } const timeDisplay = document.getElementById('timeDisplay'); if (timeDisplay) { timeDisplay.textContent = timeString; } }; } // 初始化额外功能 function initAdditionalFeatures() { setupThemeToggle(); setupAdvancedTimeDisplay(); } // 输入框操作按钮功能 function initInputButtons() { // 为所有文本输入框和文本区域的静态按钮添加事件监听器 const inputs = document.querySelectorAll('input[type="text"], input[type="url"], textarea'); inputs.forEach(input => { // 找到对应的按钮容器 const buttonContainer = input.nextElementSibling; if (buttonContainer && buttonContainer.classList.contains('input-buttons')) { // 为复制按钮添加事件监听器 const copyBtn = buttonContainer.querySelector('.copy-btn'); if (copyBtn) { copyBtn.addEventListener('click', () => { input.select(); document.execCommand('copy'); SoundManager.play('success'); showStatus('已复制到剪贴板', 'success'); }); } // 为粘贴按钮添加事件监听器 const pasteBtn = buttonContainer.querySelector('.paste-btn'); if (pasteBtn) { pasteBtn.addEventListener('click', async () => { try { const text = await navigator.clipboard.readText(); input.value = text; input.dispatchEvent(new Event('input')); SoundManager.play('success'); showStatus('已粘贴', 'success'); } catch (err) { SoundManager.play('error'); showStatus('粘贴失败', 'error'); } }); } // 为清除按钮添加事件监听器 const clearBtn = buttonContainer.querySelector('.clear-btn'); if (clearBtn) { clearBtn.addEventListener('click', () => { input.value = ''; input.dispatchEvent(new Event('input')); SoundManager.play('click'); showStatus('输入已清除', 'success'); }); } // 为上传按钮添加事件监听器 const uploadBtn = buttonContainer.querySelector('.upload-btn'); if (uploadBtn) { // 找到对应的文件输入元素 const fileInput = document.getElementById('imageUpload_' + input.id); if (fileInput) { uploadBtn.addEventListener('click', () => { fileInput.click(); }); // 为文件输入元素添加change事件监听器 fileInput.addEventListener('change', function(e) { const file = e.target.files[0]; if (!file) return; // 验证文件类型 if (!file.type.startsWith('image/')) { showStatus('请选择有效的图片文件', 'error'); return; } // 限制文件大小(10MB) if (file.size > 10 * 1024 * 1024) { showStatus('图片文件过大,请选择10MB以内的图片', 'error'); return; } showStatus('正在处理图片...', 'info'); const reader = new FileReader(); reader.onload = function(ev) { const dataURL = ev.target.result; // 优化:对大图片进行压缩 if (dataURL.length > 500000) { // 如果超过500KB compressImage(dataURL, 80).then(compressedDataURL => { // 提取纯base64编码(可选) const pureB64 = compressedDataURL.split(',')[1]; // 显示预览 const previewElement = buttonContainer.nextElementSibling; if (previewElement && previewElement.classList.contains('image-preview')) { previewElement.src = compressedDataURL; previewElement.style.display = 'block'; } // 设置输入框值为完整的dataURL input.value = compressedDataURL; applyInputOptimizations(); showStatus('图片已上传并优化', 'success'); }).catch(error => { console.log('图片压缩失败,使用原图:', error); // 压缩失败时使用原图 const pureB64 = dataURL.split(',')[1]; // 显示预览 const previewElement = buttonContainer.nextElementSibling; if (previewElement && previewElement.classList.contains('image-preview')) { previewElement.src = dataURL; previewElement.style.display = 'block'; } // 设置输入框值为完整的dataURL input.value = dataURL; applyInputOptimizations(); showStatus('图片已上传(未压缩)', 'success'); }); } else { const pureB64 = dataURL.split(',')[1]; // 显示预览 const previewElement = buttonContainer.nextElementSibling; if (previewElement && previewElement.classList.contains('image-preview')) { previewElement.src = dataURL; previewElement.style.display = 'block'; } // 设置输入框值为完整的dataURL input.value = dataURL; applyInputOptimizations(); showStatus('图片已上传', 'success'); } }; reader.onerror = function() { showStatus('图片读取失败,请重试', 'error'); }; reader.readAsDataURL(file); }); } } // 为转Base64按钮添加事件监听器 const convertToBase64Btn = buttonContainer.querySelector('.convert-base64-btn'); if (convertToBase64Btn) { convertToBase64Btn.addEventListener('click', async () => { const url = input.value.trim(); if (!url) { SoundManager.play('error'); showStatus('请先输入图片URL', 'error'); return; } // 验证URL格式 try { new URL(url); } catch (e) { SoundManager.play('error'); showStatus('请输入有效的URL格式', 'error'); return; } // 显示“正在转换为 Base64…”状态提示,并附带旋转加载动画 showStatus('正在转换为 Base64...', 'info', { icon: 'spinner', spin: true }); // 定义专用的图片转Base64 API const imageToBase64Apis = [ { name: 'uapis.cn API', url: `https://uapis.cn/api/v1/image/tobase64?url=${encodeURIComponent(url)}`, type: 'json' // 返回JSON格式 }, { name: 'ImageKit API', url: `https://ik.imagekit.io/ikmedia/transform?src=${encodeURIComponent(url)}&transformation=format:jpg`, type: 'image' // 直接返回图片 }, { name: 'ImgBB API', url: `https://api.imgbb.com/1/upload?key=523734b029f4012732f4c4c6613f116c&image=${encodeURIComponent(url)}`, type: 'json' // 返回JSON格式 }, { name: 'Base64Encode API', url: `https://base64encode.org/api/encode?url=${encodeURIComponent(url)}`, type: 'text' // 返回文本格式 } ]; // 定义多个备用 CORS 代理服务,按稳定性与可用性排序 const proxyServices = [ // 国内可访问、速度较稳定的代理 `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`, // 海外常用,国内偶尔抽风 `https://api.codetabs.com/v1/proxy?quest=${encodeURIComponent(url)}`, // 新兴服务,支持 HTTPS `https://cors-anywhere.herokuapp.com/${encodeURIComponent(url)}`, // 国内加速节点 `https://cors.zme.ink/${encodeURIComponent(url)}`, // 另一个国内选择 `https://proxy.cors.sh/${encodeURIComponent(url)}` ]; // 尝试使用多个代理服务 let success = false; let lastError = null; // 首先尝试专用的图片转Base64 API for (const api of imageToBase64Apis) { try { showStatus(`正在尝试通过${api.name}转换...`, 'info'); const response = await fetch(api.url, { method: 'GET', mode: 'cors', credentials: 'omit', timeout: 15000 // 15秒超时 }); if (!response.ok) { throw new Error(`${api.name}请求失败: ${response.status}`); } let dataURL = ''; if (api.type === 'json') { // 处理JSON格式响应 const result = await response.json(); if (result.base64) { dataURL = result.base64; } else if (result.data && result.data.image) { // ImgBB API 响应格式 dataURL = result.data.image.url; } else { throw new Error(`${api.name}返回错误: ${result.msg || result.error || '未知错误'}`); } } else if (api.type === 'image') { // 处理直接返回图片的API const blob = await response.blob(); dataURL = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); } else { // 处理文本格式响应 const text = await response.text(); // 检查是否是有效的base64字符串 if (text.startsWith('data:image')) { dataURL = text; } else if (/^[A-Za-z0-9+/=]+$/.test(text.trim())) { // 如果只有纯base64编码,添加data:image前缀 // 尝试推断图片类型(这里简化处理,实际可能需要更复杂的逻辑) const contentType = 'image/jpeg'; // 默认类型 dataURL = `data:${contentType};base64,${text.trim()}`; } else { throw new Error(`${api.name}返回无效的base64数据`); } } // 更新输入框的值和预览 input.value = dataURL; const previewElement = buttonContainer.nextElementSibling; if (previewElement && previewElement.classList.contains('image-preview')) { previewElement.src = dataURL; previewElement.style.display = 'block'; } input.dispatchEvent(new Event('input')); // 优化用户体验:自动调整输入框大小以适应内容 input.style.height = 'auto'; input.style.height = (input.scrollHeight < 200 ? input.scrollHeight : 200) + 'px'; // 显示成功提示,包含所用 API 名称 SoundManager.play('success'); showStatus(`图片已通过 ${api.name} 成功转换为 Base64!`, 'success'); success = true; return; // 成功后直接返回,避免继续尝试其他接口 } catch (error) { console.log(`${api.name}失败:`, error); lastError = error; // 继续尝试下一个API } } // 如果专用API都失败,尝试使用代理服务 for (const proxyUrl of proxyServices) { try { showStatus(`正在尝试通过代理服务转换... (${proxyUrl.split('/')[2]})`, 'info'); const response = await fetch(proxyUrl, { method: 'GET', mode: 'cors', credentials: 'omit', headers: { 'Accept': 'image/*' }, timeout: 10000 // 10秒超时 }); if (!response.ok) { throw new Error(`代理请求失败: ${response.status}`); } // 检查响应类型 const contentType = response.headers.get('content-type'); if (!contentType || !contentType.startsWith('image/')) { throw new Error('返回的不是有效的图片内容'); } // 将响应转换为Blob const blob = await response.blob(); // 使用FileReader读取Blob并转换为DataURL const dataURL = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); // 更新输入框的值和预览 input.value = dataURL; const previewElement = buttonContainer.nextElementSibling; if (previewElement && previewElement.classList.contains('image-preview')) { previewElement.src = dataURL; previewElement.style.display = 'block'; } input.dispatchEvent(new Event('input')); // 优化用户体验:自动调整输入框大小以适应内容 input.style.height = 'auto'; input.style.height = (input.scrollHeight < 200 ? input.scrollHeight : 200) + 'px'; // 显示成功提示 SoundManager.play('success'); showStatus('图片已成功转换为Base64!', 'success'); success = true; break; // 成功后跳出循环 } catch (error) { console.log(`代理服务失败 (${proxyUrl}):`, error); lastError = error; // 继续尝试下一个代理 } } // 如果所有代理都失败,尝试直接fetch(可能对同源资源有效) if (!success) { try { showStatus('尝试直接获取图片...', 'info'); const response = await fetch(url, { mode: 'cors', credentials: 'omit', timeout: 8000 }); if (!response.ok) { throw new Error(`直接请求失败: ${response.status}`); } const blob = await response.blob(); const dataURL = await new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); // 更新输入框的值和预览 input.value = dataURL; const previewElement = buttonContainer.nextElementSibling; if (previewElement && previewElement.classList.contains('image-preview')) { previewElement.src = dataURL; previewElement.style.display = 'block'; } input.dispatchEvent(new Event('input')); input.style.height = 'auto'; input.style.height = (input.scrollHeight < 200 ? input.scrollHeight : 200) + 'px'; showStatus('图片已成功转换为Base64!', 'success'); success = true; } catch (directError) { lastError = directError; } } // 如果所有方法都失败,提供详细的错误信息和替代方案 if (!success) { console.error('所有转换方法均失败:', lastError); // 提供更详细的错误信息和替代方案 const detailedError = '转换失败!可能的原因:\n' + '1. 跨域限制 - 目标图片网站不允许跨域访问\n' + '2. 网络问题 - 代理服务不可用或网络连接不稳定\n' + '3. URL无效 - 请检查图片链接是否正确\n\n' + '推荐解决方案:\n' + '1. 点击"上传"按钮上传本地图片\n' + '2. 使用其他图片URL尝试\n' + '3. 手动下载图片后再上传'; // 显示失败提示 SoundManager.play('error'); showStatus('转换失败,请尝试上传本地图片', 'error'); // 可以考虑添加一个详细错误提示框 if (confirm(detailedError)) { // 用户点击确定,可以聚焦到文件上传按钮 const uploadBtn = buttonContainer.querySelector('.upload-btn'); if (uploadBtn) { uploadBtn.click(); } } } }); } } }); // 辅助函数:应用输入框优化 function applyInputOptimizations() { input.dispatchEvent(new Event('input')); // 自动调整输入框高度 input.style.height = 'auto'; input.style.height = (input.scrollHeight < 200 ? input.scrollHeight : 200) + 'px'; // 自动选中内容便于复制 input.select(); } // 辅助函数:压缩图片 function compressImage(dataURL, quality) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = function() { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // 设置压缩后的尺寸(保持宽高比) let width = img.width; let height = img.height; const maxDimension = 1024; if (width > height && width > maxDimension) { height = Math.round((height * maxDimension) / width); width = maxDimension; } else if (height > maxDimension) { width = Math.round((width * maxDimension) / height); height = maxDimension; } canvas.width = width; canvas.height = height; // 绘制压缩后的图片 ctx.drawImage(img, 0, 0, width, height); // 转换为压缩后的DataURL const compressedDataURL = canvas.toDataURL('image/jpeg', quality / 100); resolve(compressedDataURL); }; img.onerror = reject; img.src = dataURL; }); } } // 页面加载完成后初始化(所有初始化代码已在上方DOMContentLoaded中处理) // 添加页面可见性更改事件监听器,实现在页面重新可见时恢复图标设置 document.addEventListener('visibilitychange', () => { if (!document.hidden && bookmarkData) { // 页面变为可见时,重新加载自定义图标设置 try { const savedCustomIcons = localStorage.getItem('bookmarkCustomIcons'); if (savedCustomIcons) { customIcons = JSON.parse(savedCustomIcons); // 如果当前有选中的书签,更新其图标显示 if (currentSelectedBookmark) { updateCurrentIconDisplay(); // 更新图标输入框的值 const savedIcons = JSON.parse(localStorage.getItem('bookmarkCustomIcons') || '{}'); const savedIconUrl = savedIcons[currentSelectedBookmark]; if (savedIconUrl !== undefined) { iconUrl.value = savedIconUrl; } else if (bookmarkMap[currentSelectedBookmark] && bookmarkMap[currentSelectedBookmark].icon) { iconUrl.value = bookmarkMap[currentSelectedBookmark].icon; } } } } catch (e) { console.error('从localStorage恢复自定义图标失败:', e); } } }); // 添加窗口焦点事件监听器,实现在窗口重新获得焦点时恢复图标设置 window.addEventListener('focus', () => { if (bookmarkData && currentSelectedBookmark) { // 窗口重新获得焦点时,检查localStorage中的图标设置是否有更新 try { const savedIcons = JSON.parse(localStorage.getItem('bookmarkCustomIcons') || '{}'); const savedIconUrl = savedIcons[currentSelectedBookmark]; // 更新图标输入框的值 if (savedIconUrl !== undefined) { iconUrl.value = savedIconUrl; } else if (bookmarkMap[currentSelectedBookmark] && bookmarkMap[currentSelectedBookmark].icon) { iconUrl.value = bookmarkMap[currentSelectedBookmark].icon; } // 更新当前图标显示 updateCurrentIconDisplay(); } catch (e) { console.error('从localStorage恢复自定义图标失败:', e); } } });