import { applications, categories, filterApplications, validateApplications } from './apps.mjs'; validateApplications(applications); const icons = { grid: '', sparkles: '', clipboard: '', wallet: '', scan: '', text: '', shield: '', star: '', arrow: '', search: '' }; const svg = name => ``; const escapeHtml = value => String(value).replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char])); const storageKey = 'dieya-portal:favorites:v1'; function readFavorites() { try { const stored = JSON.parse(localStorage.getItem(storageKey) || '[]'); return Array.isArray(stored) ? stored.filter(id => applications.some(app => app.id === id)) : []; } catch { return []; } } const state = { query: '', category: 'all', environment: 'all', favorites: readFavorites() }; const grid = document.querySelector('#app-grid'); const search = document.querySelector('#search'); const nav = document.querySelector('#category-nav'); const filters = document.querySelector('#environment-filters'); document.querySelector('#search-icon').innerHTML = svg('search'); document.querySelector('#empty-icon').innerHTML = svg('search'); document.querySelector('#total-count').textContent = applications.length; const environmentLabels = [{ id: 'all', label: '全部' }, { id: 'production', label: '正式应用' }, { id: 'test', label: '测试应用' }]; let toastTimer; function renderNav() { nav.innerHTML = categories.map(category => { const count = category.id === 'favorites' ? state.favorites.length : category.id === 'all' ? applications.length : applications.filter(app => app.category === category.id).length; return ``; }).join(''); } function render() { renderNav(); filters.innerHTML = environmentLabels.map(item => ``).join(''); document.querySelector('#section-title').textContent = categories.find(category => category.id === state.category).name; document.querySelector('#section-description').textContent = state.category === 'favorites' ? '收藏你常用的应用,下次更快找到。' : '你的业务工具与 AI 助手,都在这里。'; const matches = filterApplications(applications, state); document.querySelector('#result-count').textContent = `共 ${matches.length} 个应用`; grid.innerHTML = matches.map(app => { const favorite = state.favorites.includes(app.id); const categoryName = categories.find(category => category.id === app.category).name; return `
${svg(app.icon)}

${escapeHtml(app.name)}

${escapeHtml(app.description)}

${escapeHtml(categoryName)}${app.environment === 'test' ? '测试' : '正式'}
${escapeHtml(new URL(app.url).hostname)}
`; }).join(''); document.querySelector('#empty-state').hidden = matches.length > 0; const emptyFavorites = state.category === 'favorites' && state.favorites.length === 0; document.querySelector('#empty-title').textContent = emptyFavorites ? '把常用应用放在这里' : '没有找到匹配的应用'; document.querySelector('#empty-description').textContent = emptyFavorites ? '点击应用卡片右上角的星标,即可加入我的常用。' : '试试其他关键词,或调整筛选条件。'; } nav.addEventListener('click', event => { const button = event.target.closest('[data-category]'); if (!button) return; state.category = button.dataset.category; render(); }); filters.addEventListener('click', event => { const button = event.target.closest('[data-environment]'); if (!button) return; state.environment = button.dataset.environment; render(); }); search.addEventListener('input', () => { state.query = search.value; render(); }); grid.addEventListener('click', event => { const button = event.target.closest('[data-favorite]'); if (!button) return; const id = button.dataset.favorite; const adding = !state.favorites.includes(id); state.favorites = adding ? [...state.favorites, id] : state.favorites.filter(value => value !== id); let persisted = true; try { localStorage.setItem(storageKey, JSON.stringify(state.favorites)); } catch { persisted = false; } render(); grid.querySelector(`[data-favorite="${id}"]`)?.focus({ preventScroll: true }); const toast = document.querySelector('#toast'); toast.textContent = `${adding ? '已加入我的常用' : '已取消常用'}${persisted ? '' : '(当前浏览器无法保存,仅本次有效)'}`; toast.classList.add('visible'); clearTimeout(toastTimer); toastTimer = setTimeout(() => toast.classList.remove('visible'), 2400); }); document.querySelector('#reset-filters').addEventListener('click', () => { state.category = 'all'; state.environment = 'all'; state.query = ''; search.value = ''; render(); }); document.addEventListener('keydown', event => { if (event.key === '/' && !event.ctrlKey && !event.metaKey && !event.altKey && !event.target.closest('input,textarea,[contenteditable="true"]')) { event.preventDefault(); search.focus(); } if (event.key === 'Escape' && document.activeElement === search) { search.value = ''; state.query = ''; render(); search.blur(); } }); window.addEventListener('storage', event => { if (event.key === storageKey || event.key === null) { state.favorites = readFavorites(); render(); } }); render();