Initial daily news digest web

This commit is contained in:
wuyanwanwu
2026-08-09 17:43:21 +08:00
commit 8e735ece88
17 changed files with 1230 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
const $ = (selector) => document.querySelector(selector);
const escapeHtml = (value) => String(value ?? '').replace(/[&<>'"]/g, (char) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' }[char]));
let modules = [];
let editingId = null;
function token() { return sessionStorage.getItem('daily_digest_admin_token') || ''; }
function headers(json = false) { const value = { 'X-Admin-Token': token() }; if (json) value['Content-Type'] = 'application/json'; return value; }
function message(selector, text, type = '') { const el = $(selector); el.textContent = text; el.className = `form-message ${type}`; }
async function api(path, options = {}) {
const response = await fetch(path, { ...options, headers: { ...headers(Boolean(options.body)), ...(options.headers || {}) } });
const payload = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(payload.error || `请求失败 (${response.status})`);
return payload;
}
function renderModuleOptions(selected = []) {
$('#moduleOptions').innerHTML = modules.map((module) => `<label class="module-option"><input type="checkbox" value="${escapeHtml(module.module_key)}" ${selected.includes(module.module_key) ? 'checked' : ''}><span>${escapeHtml(module.module_name)}</span></label>`).join('');
}
function selectedModules() { return [...document.querySelectorAll('#moduleOptions input:checked')].map((input) => input.value); }
function renderRecipients(recipients) {
$('#recipientCount').textContent = recipients.length;
if (!recipients.length) { $('#recipientList').innerHTML = '<div class="empty-state"><strong>还没有收件邮箱</strong><span>在左侧添加第一个日报收件人。</span></div>'; return; }
const names = Object.fromEntries(modules.map((module) => [module.module_key, module.module_name]));
$('#recipientList').innerHTML = recipients.map((recipient) => `<div class="recipient-item">
<div><div class="recipient-email">${escapeHtml(recipient.email)} ${recipient.enabled ? '' : '<span class="tag">已停用</span>'}</div><div class="recipient-sub">${escapeHtml(recipient.display_name || '未设置名称')} · 每天 ${escapeHtml(String(recipient.send_time).slice(0, 5))} · ${escapeHtml(recipient.timezone)}</div><div class="chips">${recipient.send_all_modules ? '<span class="chip">全部模块</span>' : (recipient.modules || []).map((key) => `<span class="chip">${escapeHtml(names[key] || key)}</span>`).join('')}</div></div>
<div class="recipient-actions"><button class="icon-button" type="button" data-edit="${recipient.id}" title="编辑">✎</button><button class="icon-button delete" type="button" data-delete="${recipient.id}" title="删除">×</button></div>
</div>`).join('');
document.querySelectorAll('[data-edit]').forEach((button) => button.addEventListener('click', () => editRecipient(recipients.find((item) => item.id === Number(button.dataset.edit)))));
document.querySelectorAll('[data-delete]').forEach((button) => button.addEventListener('click', () => removeRecipient(Number(button.dataset.delete))));
}
async function loadRecipients() {
try { renderRecipients((await api('/api/recipients')).recipients); }
catch (error) { $('#recipientList').innerHTML = `<div class="empty-state"><strong>无法读取邮箱设置</strong><span>${escapeHtml(error.message)}</span></div>`; }
}
function editRecipient(recipient) {
editingId = recipient.id; $('#formTitle').textContent = '编辑收件邮箱'; $('#submitLabel').textContent = '保存修改'; $('#cancelEdit').classList.remove('hidden');
$('#recipientId').value = recipient.id; $('#emailInput').value = recipient.email; $('#emailInput').disabled = true; $('#nameInput').value = recipient.display_name || ''; $('#timeInput').value = String(recipient.send_time).slice(0, 5); $('#timezoneInput').value = recipient.timezone || 'Asia/Shanghai'; $('#allModulesInput').checked = recipient.send_all_modules; renderModuleOptions(recipient.modules || []); window.scrollTo({ top: 0, behavior: 'smooth' });
}
function resetForm() { editingId = null; $('#formTitle').textContent = '添加收件邮箱'; $('#submitLabel').textContent = '保存邮箱设置'; $('#cancelEdit').classList.add('hidden'); $('#recipientForm').reset(); $('#emailInput').disabled = false; $('#timeInput').value = '07:30'; $('#timezoneInput').value = 'Asia/Shanghai'; renderModuleOptions([]); message('#formMessage', ''); }
async function removeRecipient(id) {
if (!window.confirm('确定删除这个收件邮箱吗?历史发送记录会保留,但以后不会再发送。')) return;
try { await api(`/api/recipients/${id}`, { method: 'DELETE' }); message('#tokenMessage', '邮箱已删除', 'success'); await loadRecipients(); }
catch (error) { message('#tokenMessage', error.message, 'error'); }
}
$('#saveToken').addEventListener('click', async () => { const value = $('#tokenInput').value.trim(); if (!value) return message('#tokenMessage', '请输入管理令牌', 'error'); sessionStorage.setItem('daily_digest_admin_token', value); message('#tokenMessage', '令牌已保存,正在验证…'); await loadRecipients(); });
$('#tokenInput').value = token();
$('#cancelEdit').addEventListener('click', resetForm);
$('#allModulesInput').addEventListener('change', (event) => { document.querySelectorAll('#moduleOptions input').forEach((input) => { input.disabled = event.target.checked; }); });
$('#recipientForm').addEventListener('submit', async (event) => {
event.preventDefault();
const body = { email: $('#emailInput').value.trim(), display_name: $('#nameInput').value.trim(), send_time: $('#timeInput').value, timezone: $('#timezoneInput').value.trim(), send_all_modules: $('#allModulesInput').checked, modules: selectedModules() };
try { await api(editingId ? `/api/recipients/${editingId}` : '/api/recipients', { method: editingId ? 'PUT' : 'POST', body: JSON.stringify(body) }); message('#formMessage', editingId ? '邮箱设置已更新' : '邮箱已添加', 'success'); resetForm(); await loadRecipients(); }
catch (error) { message('#formMessage', error.message, 'error'); }
});
(async function init() { try { modules = (await fetch('/api/modules').then((response) => response.json())).modules || []; renderModuleOptions(); await loadRecipients(); } catch (error) { message('#tokenMessage', error.message, 'error'); } })();