import { applications, validateApplications } from './apps.mjs'; validateApplications(applications); const icons = { shield: '', scan: '', arrow: '', external: '' }; const svg = name => ``; const escapeHtml = value => String(value).replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char])); const grid = document.querySelector('#agent-grid'); const root = document.documentElement; const themeKey = 'dieya-portal:theme:v1'; const storedTheme = localStorage.getItem(themeKey); const preferredTheme = storedTheme || (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); function applyTheme(theme) { root.dataset.theme = theme; document.querySelectorAll('[data-theme]').forEach(button => { const active = button.dataset.theme === theme; button.classList.toggle('active', active); button.setAttribute('aria-pressed', String(active)); }); document.querySelector('meta[name="theme-color"]').setAttribute('content', theme === 'dark' ? '#070b17' : '#f5f8fc'); try { localStorage.setItem(themeKey, theme); } catch { /* 当前浏览器不支持持久化时仍可切换 */ } } function render() { grid.innerHTML = applications.map((app, index) => `
${svg(app.icon)}产品部

${escapeHtml(app.name)} 正式应用

${escapeHtml(app.description)}

就绪0${index + 1}
打开工作台 ${svg('arrow')}
`).join(''); document.querySelector('#empty-state').hidden = applications.length > 0; } function showView(view, updateHash = true) { const stats = view === 'stats'; document.querySelector('#home-view').hidden = stats; document.querySelector('#stats-view').hidden = !stats; document.querySelectorAll('[data-view]').forEach(button => { const active = button.dataset.view === view; button.classList.toggle('active', active); button.setAttribute('aria-pressed', String(active)); }); if (updateHash) history.replaceState(null, '', stats ? '#stats' : '#'); if (stats) loadStats(); } async function loadStats() { const error = document.querySelector('#stats-error'); error.hidden = true; try { const response = await fetch('/api/stats', { headers: { Accept: 'application/json' } }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const payload = await response.json(); const records = payload.items || []; document.querySelector('#stats-total').textContent = Number(payload.total_clicks || 0).toLocaleString('zh-CN'); document.querySelector('#stats-updated').textContent = payload.updated_at ? new Date(payload.updated_at).toLocaleString('zh-CN', { hour12: false }) : '暂无记录'; document.querySelector('#stats-table-body').innerHTML = records.map(item => `${escapeHtml(item.name)}${escapeHtml(item.url)}${Number(item.click_count).toLocaleString('zh-CN')}正式应用`).join(''); } catch { error.hidden = false; } } async function trackAndOpen(app) { const request = fetch(`/api/apps/${encodeURIComponent(app.id)}/click`, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({}), keepalive: true }).catch(() => undefined); // Give the same-origin request a chance to reach the API, then open the tool. await Promise.race([request, new Promise(resolve => setTimeout(resolve, 180))]); window.location.assign(app.url); } document.querySelectorAll('[data-theme]').forEach(button => button.addEventListener('click', () => applyTheme(button.dataset.theme))); document.querySelectorAll('[data-view]').forEach(button => button.addEventListener('click', () => showView(button.dataset.view))); document.querySelector('#refresh-stats').addEventListener('click', loadStats); window.addEventListener('hashchange', () => showView(location.hash === '#stats' ? 'stats' : 'home', false)); grid.addEventListener('click', event => { const link = event.target.closest('.agent-card__button'); if (!link) return; event.preventDefault(); const card = link.closest('.agent-card'); const app = applications.find(item => item.url === link.href); if (app && card) trackAndOpen(app); }); applyTheme(preferredTheme); render(); showView(location.hash === '#stats' ? 'stats' : 'home', false);