fix: data-action buttons, sources enabled, email notify, ticket detail single-page

- app.js: mount page objects via inline script (CSP blocks eval → data-action buttons dead)
- app.js: add tickets list entry to sidebar nav (admin could not find claimed tickets)
- sources: GET /sources?manage=1 returns enabled field for admin view
- mailer: sendNotifyEmail consumes email-type notification_configs (webhook_url = recipients)
- tickets: trigger notify on created/claimed/transferred/updated/status-change
- notifications: email type shows recipient field, validates email list
- ticket-detail: single-page chat flow (player/staff bubbles) + processing log timeline
- css: chat bubble styles
This commit is contained in:
2026-08-22 20:00:12 +08:00
parent e5a1d693f1
commit c5ceba7cf0
9 changed files with 120 additions and 37 deletions

View File

@@ -93,4 +93,25 @@ async function sendEmail(to, templateCode, vars) {
}
}
module.exports = { sendEmail, renderTemplate, getTemplate, getSmtpConfig, getSiteName, getSiteUrl };
module.exports = { sendEmail, sendNotifyEmail, renderTemplate, getTemplate, getSmtpConfig, getSiteName, getSiteUrl };
// ---- 事件通知(email 类型配置消费) ----
// 读取 notification_configs 中 type='email' 且 active=1 的配置, 事件匹配则向收件人发信
// email 类型配置的 webhook_url 字段复用为收件人邮箱(逗号分隔)
async function sendNotifyEmail(event, data) {
try {
const configs = await query("SELECT * FROM notification_configs WHERE type = 'email' AND active = 1");
for (const cfg of configs) {
const events = cfg.events || 'all';
if (events !== 'all' && !events.split(',').map(s => s.trim()).includes(event)) continue;
const recipients = String(cfg.webhook_url || '').split(',').map(s => s.trim()).filter(Boolean);
if (!recipients.length) continue;
const templateCode = { ticket_created: 'ticket_created', ticket_claimed: 'ticket_claimed', ticket_transferred: 'ticket_transferred', ticket_updated: 'ticket_updated' }[event] || 'ticket_updated';
for (const to of recipients) {
sendEmail(to, templateCode, data).catch(() => {});
}
}
} catch (err) {
console.error('[NotifyEmail]', err.message);
}
}