// Prompt Generator — 全局 JS 模块 const API = { async request(method, path, body) { const opts = { method, headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', }; if (body !== undefined) { opts.body = JSON.stringify(body); } const resp = await fetch(path, opts); const data = await resp.json(); if (resp.status === 401) { Auth.showLoginModal(); throw new Error('未登录'); } if (data.code !== 0) { throw new Error(data.message || '请求失败'); } return data.data; }, get(path) { return this.request('GET', path); }, post(path, body) { return this.request('POST', path, body); }, put(path, body) { return this.request('PUT', path, body); }, del(path) { return this.request('DELETE', path); }, }; // 认证模块 const Auth = { modal: null, showLoginModal() { if (this.modal) return; const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); this.modal = overlay; const input = overlay.querySelector('#login-password'); const btn = overlay.querySelector('#login-btn'); const err = overlay.querySelector('#login-error'); const doLogin = async () => { try { btn.disabled = true; btn.textContent = '登录中...'; await API.post('/api/auth/login', { password: input.value }); overlay.remove(); this.modal = null; Toast.success('登录成功'); // Reload page data if (typeof onLogin === 'function') onLogin(); } catch (e) { err.textContent = e.message; err.style.display = 'block'; btn.disabled = false; btn.textContent = '登录'; } }; btn.onclick = doLogin; input.onkeydown = (e) => { if (e.key === 'Enter') doLogin(); }; }, async check() { try { await API.get('/api/auth/check'); return true; } catch { return false; } }, async logout() { try { await API.post('/api/auth/logout'); } catch {} location.reload(); } }; // Toast 提示 const Toast = { show(message, type = 'success', duration = 3000) { const el = document.createElement('div'); el.className = `toast toast-${type}`; el.textContent = message; document.body.appendChild(el); setTimeout(() => el.remove(), duration); }, success(msg) { this.show(msg, 'success'); }, error(msg) { this.show(msg, 'error', 5000); }, }; // 主题切换 const Theme = { init() { const saved = localStorage.getItem('theme'); if (saved) { document.documentElement.setAttribute('data-theme', saved); } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.setAttribute('data-theme', 'dark'); } }, toggle() { const current = document.documentElement.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('theme', next); }, isDark() { return document.documentElement.getAttribute('data-theme') === 'dark'; } }; // 草稿管理(localStorage) const Draft = { save(key, data) { localStorage.setItem(`draft_${key}`, JSON.stringify(data)); }, load(key) { const raw = localStorage.getItem(`draft_${key}`); if (!raw) return null; try { return JSON.parse(raw); } catch { return null; } }, clear(key) { localStorage.removeItem(`draft_${key}`); } }; // 防抖 function debounce(fn, delay = 1000) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), delay); }; } // 工具函数 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function truncateText(text, maxLen = 150) { if (!text) return ''; // 跳过 XML 标签内容,提取用户实际输入 const cleaned = text.replace(/<[a-zA-Z_-][^>]*>[\s\S]*?<\/[a-zA-Z_-][^>]*>/g, '').trim(); if (cleaned.length <= maxLen) return cleaned; return cleaned.substring(0, maxLen) + '...'; } function formatDate(dateStr) { if (!dateStr) return ''; const d = new Date(dateStr); const pad = n => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; } // 初始化 Theme.init(); // 导航栏渲染 function renderNavbar(activePage) { const pages = [ { id: 'builder', label: '构建器', href: '/' }, { id: 'dashboard', label: '复盘看板', href: '/dashboard' }, { id: 'settings', label: '设置', href: '/settings' }, ]; const nav = document.createElement('nav'); nav.className = 'navbar'; nav.innerHTML = ` `; document.body.insertBefore(nav, document.body.firstChild); }