// API 配置(从 config.js 读取,部署 Pages 时请设置后端 Worker 地址) const API_BASE = typeof window !== 'undefined' && window.API_BASE !== undefined ? window.API_BASE : ''; // 初始数据(示例) let sites = [ { id: '1', name: '百度', url: 'https://www.baidu.com', description: '全球最大的中文搜索引擎', category: '常用', tags: ['搜索', '中文'] }, { id: '2', name: '知乎', url: 'https://www.zhihu.com', description: '中文互联网高质量的问答社区', category: '社交', tags: ['问答', '知识'] }, { id: '3', name: 'GitHub', url: 'https://github.com', description: '全球最大的代码托管平台', category: '工作', tags: ['代码', '开发'] }, { id: '4', name: 'Bilibili', url: 'https://www.bilibili.com', description: '中国年轻世代高度聚集的文化社区', category: '娱乐', tags: ['视频', '弹幕'] }, { id: '5', name: '淘宝', url: 'https://www.taobao.com', description: '亚洲最大的购物网站', category: '购物', tags: ['电商', '购物'] } ]; let deferredInstallPrompt = null; let installBtn = null; let hasRefreshing = false; function isStandaloneMode() { return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true; } function createInstallButton() { if (installBtn) { return installBtn; } installBtn = document.createElement('button'); installBtn.className = 'install-btn'; installBtn.type = 'button'; installBtn.textContent = '📲 安装应用'; installBtn.style.cssText = ` position: fixed; bottom: 20px; right: 20px; background: linear-gradient(135deg, #10b981, #059669); color: #fff; border: none; padding: 12px 24px; border-radius: 50px; cursor: pointer; font-weight: 600; font-size: 0.95rem; box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3); display: none; z-index: 1000; transition: all 0.2s ease; `; installBtn.addEventListener('mouseenter', () => { installBtn.style.transform = 'translateY(-2px)'; installBtn.style.boxShadow = '0 6px 20px rgba(16, 185, 129, 0.4)'; }); installBtn.addEventListener('mouseleave', () => { installBtn.style.transform = 'translateY(0)'; installBtn.style.boxShadow = '0 4px 15px rgba(16, 185, 129, 0.3)'; }); installBtn.addEventListener('click', async () => { if (!deferredInstallPrompt) { return; } deferredInstallPrompt.prompt(); const choice = await deferredInstallPrompt.userChoice; if (choice.outcome === 'accepted') { showToast('已触发安装流程'); } deferredInstallPrompt = null; installBtn.style.display = 'none'; }); document.body.appendChild(installBtn); return installBtn; } function initInstallPrompt() { const button = createInstallButton(); if (isStandaloneMode()) { button.style.display = 'none'; return; } window.addEventListener('beforeinstallprompt', (event) => { event.preventDefault(); deferredInstallPrompt = event; button.style.display = 'block'; }); window.addEventListener('appinstalled', () => { deferredInstallPrompt = null; button.style.display = 'none'; showToast('应用安装成功'); }); } async function registerServiceWorker() { if (!('serviceWorker' in navigator)) { return; } try { const registration = await navigator.serviceWorker.register('/sw.js', { scope: '/' }); if (registration.waiting) { promptRefresh(registration.waiting); } registration.addEventListener('updatefound', () => { const newWorker = registration.installing; if (!newWorker) { return; } newWorker.addEventListener('statechange', () => { if (newWorker.state === 'installed' && navigator.serviceWorker.controller) { promptRefresh(newWorker); } }); }); navigator.serviceWorker.addEventListener('controllerchange', () => { if (hasRefreshing) { return; } hasRefreshing = true; window.location.reload(); }); } catch (error) { console.error('Service Worker 注册失败:', error); } } function promptRefresh(worker) { const shouldRefresh = window.confirm('发现新版本,是否立即刷新?'); if (shouldRefresh) { worker.postMessage('SKIP_WAITING'); } } // 加载网站数据 async function loadSites() { try { const response = await fetch(`${API_BASE}/api/sites`); if (response.ok) { sites = await response.json(); updateStats(); renderSites(); } } catch (error) { console.error('加载网站数据失败:', error); // 使用默认数据 updateStats(); renderSites(); } } // 更新统计信息 function updateStats() { const totalSites = sites.length; const categories = [...new Set(sites.map(site => site.category))]; const allTags = sites.flatMap(site => site.tags || []); const uniqueTags = [...new Set(allTags)]; document.getElementById('total-sites').textContent = totalSites; document.getElementById('total-categories').textContent = categories.length; document.getElementById('total-tags').textContent = uniqueTags.length; // 更新分类过滤器 updateCategoryFilters(); } // 更新分类过滤器 function updateCategoryFilters() { const categoryFilters = document.getElementById('category-filters'); const categories = ['all', ...new Set(sites.map(site => site.category))]; categoryFilters.innerHTML = ''; categories.forEach(category => { const filterBtn = document.createElement('div'); filterBtn.className = 'category-filter'; filterBtn.textContent = category === 'all' ? '全部' : category; filterBtn.dataset.category = category; if (category === 'all') { filterBtn.classList.add('active'); } filterBtn.addEventListener('click', () => { document.querySelectorAll('.category-filter').forEach(btn => { btn.classList.remove('active'); }); filterBtn.classList.add('active'); filterSites(); closeCategorySidebar(); }); categoryFilters.appendChild(filterBtn); }); } // 渲染网站 function renderSites(filteredSites = null) { const container = document.getElementById('categories-container'); const searchInput = document.getElementById('search-input').value.toLowerCase(); const activeCategory = document.querySelector('.category-filter.active').dataset.category; // 如果没有传入过滤后的网站,则使用全部网站 const sitesToRender = filteredSites || sites; // 如果有搜索关键词,进一步过滤 let finalSites = sitesToRender; if (searchInput) { finalSites = sitesToRender.filter(site => site.name.toLowerCase().includes(searchInput) || (site.description && site.description.toLowerCase().includes(searchInput)) || (site.tags && site.tags.some(tag => tag.toLowerCase().includes(searchInput))) ); } // 如果有分类过滤 if (activeCategory !== 'all') { finalSites = finalSites.filter(site => site.category === activeCategory); } // 按分类分组 const sitesByCategory = {}; finalSites.forEach(site => { if (!sitesByCategory[site.category]) { sitesByCategory[site.category] = []; } sitesByCategory[site.category].push(site); }); // 如果没有网站匹配 if (Object.keys(sitesByCategory).length === 0) { container.innerHTML = `
🔍

没有找到匹配的网站

尝试调整搜索关键词或分类筛选条件

`; return; } // 渲染分类区块 container.innerHTML = ''; Object.keys(sitesByCategory).sort().forEach(category => { const categorySection = document.createElement('div'); categorySection.className = 'category-section'; categorySection.innerHTML = `

${category}

${sitesByCategory[category].map(site => createSiteCard(site)).join('')}
`; container.appendChild(categorySection); }); // 添加点击事件 document.querySelectorAll('.site-card').forEach(card => { card.addEventListener('click', (e) => { if (!e.target.closest('.site-icon') && !e.target.closest('img')) { window.open(card.dataset.url, '_blank'); } }); }); } // 通过 Worker 代理获取 favicon function getFaviconUrl(domain) { return `${API_BASE}/api/favicon?domain=${domain}`; } // 生成favicon HTML,使用 Worker 代理 function generateFaviconHtml(domain, firstLetter) { const faviconUrl = getFaviconUrl(domain); // Worker 会自动尝试多个源,失败时显示占位符 const onerrorCode = `this.parentElement.innerHTML='
${firstLetter}
';`; return { src: faviconUrl, onerror: onerrorCode }; } // 创建网站卡片HTML function createSiteCard(site) { // 从URL提取域名用于获取favicon const domain = new URL(site.url).hostname.replace('www.', ''); // 生成网站名称首字母作为备用图标 const firstLetter = site.name.charAt(0).toUpperCase(); // 获取favicon配置 const faviconConfig = generateFaviconHtml(domain, firstLetter); // 处理标签 const tagsHtml = site.tags && site.tags.length > 0 ? site.tags.map(tag => `${tag}`).join('') : ''; // 处理标签文本(用于 title) const tagsText = site.tags && site.tags.length > 0 ? site.tags.join('、') : ''; const clickCount = typeof site.clicks === 'number' ? site.clicks : 0; return ` ${clickCount}
${site.name}图标
${site.name}
${site.description || ''}
${tagsHtml}
`; } // 显示提示消息 function showToast(message, type = 'success') { const toast = document.getElementById('toast'); toast.className = `toast ${type} show`; document.querySelector('.toast-message').textContent = message; setTimeout(() => { toast.classList.remove('show'); }, 3000); } // 过滤网站(搜索和分类) function filterSites() { renderSites(); } // 显示提示消息 function showToast(message, type = 'success') { const toast = document.getElementById('toast'); toast.className = `toast ${type} show`; document.querySelector('.toast-message').textContent = message; setTimeout(() => { toast.classList.remove('show'); }, 3000); } // 过滤网站(搜索和分类) function filterSites() { renderSites(); } // 网页搜索处理 function performWebSearch(query, engine) { const encodedQuery = encodeURIComponent(query); let searchUrl = ''; switch (engine) { case 'google': searchUrl = `https://www.google.com/search?q=${encodedQuery}`; break; case 'baidu': searchUrl = `https://www.baidu.com/s?wd=${encodedQuery}`; break; case 'bing': searchUrl = `https://www.bing.com/search?q=${encodedQuery}`; break; case 'duckduckgo': searchUrl = `https://duckduckgo.com/?q=${encodedQuery}`; break; case 'yandex': searchUrl = `https://yandex.com/search/?text=${encodedQuery}`; break; default: searchUrl = `https://www.google.com/search?q=${encodedQuery}`; } window.open(searchUrl, '_blank'); } function openCategorySidebar() { document.body.classList.add('category-sidebar-open'); const backdrop = document.getElementById('category-sidebar-backdrop'); if (backdrop) backdrop.setAttribute('aria-hidden', 'false'); } function closeCategorySidebar() { document.body.classList.remove('category-sidebar-open'); const backdrop = document.getElementById('category-sidebar-backdrop'); if (backdrop) backdrop.setAttribute('aria-hidden', 'true'); } function initCategorySidebar() { const toggle = document.getElementById('category-sidebar-toggle'); const closeBtn = document.getElementById('category-sidebar-close'); const backdrop = document.getElementById('category-sidebar-backdrop'); if (toggle) toggle.addEventListener('click', openCategorySidebar); if (closeBtn) closeBtn.addEventListener('click', closeCategorySidebar); if (backdrop) backdrop.addEventListener('click', closeCategorySidebar); } // 初始化 document.addEventListener('DOMContentLoaded', async () => { initInstallPrompt(); initCategorySidebar(); // 点击卡片时上报访问次数(不阻止跳转) const container = document.getElementById('categories-container'); if (container) { container.addEventListener('click', (e) => { const card = e.target.closest('.site-card'); if (!card) return; const id = card.dataset.siteId; if (id) { const base = typeof window !== 'undefined' && window.API_BASE !== undefined ? window.API_BASE : ''; fetch(base + '/api/sites/' + id + '/click', { method: 'POST', keepalive: true }).catch(() => {}); } }); } // 注册 PWA Service Worker await registerServiceWorker(); // 加载网站数据 await loadSites(); // 搜索输入 document.getElementById('search-input').addEventListener('input', filterSites); // 网页搜索表单提交 const webSearchForm = document.getElementById('web-search-form'); if (webSearchForm) { webSearchForm.addEventListener('submit', (e) => { e.preventDefault(); const query = document.getElementById('web-search-input').value.trim(); const engine = document.getElementById('search-engine').value; if (query) { performWebSearch(query, engine); document.getElementById('web-search-input').value = ''; } }); } });