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')}
${svg('external')}
`).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);