Files
MC_Report/public/js/pages/notifications.js
canglan 8d6c3c4a8d fix: notifications page crash + email code dev-mode false error
- notifications.js: EventOptions(current) referenced undefined 'Current'
  (case typo) -> ReferenceError crashed add/edit modal, page unusable
- auth.js send-verify-code: dev mode (SMTP down) deleted captcha row before
  returning code, so register always failed with '验证码无效或已过期'
  even with correct code. Keep row; it's deleted after successful verify.
2026-08-17 04:50:10 +08:00

93 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const NotificationsPage = {
async render() { return '<div class="loading"><i class="fas fa-spinner"></i></div>'; },
async mount() {
document.getElementById('page-title').textContent = '通知配置';
document.getElementById('page-actions').innerHTML = '<button class="btn btn-p btn-sm" data-action="NotificationsPage:showCreate"><i class="fas fa-plus"></i> 添加通知</button>';
await this.load();
},
async load() {
const ct = document.getElementById('page-content');
try {
const cfgs = await API.get('/notifications');
const typeLabel = { webhook:'Discord', email:'邮件通知', generic:'通用Webhook' };
ct.innerHTML = cfgs.length ? cfgs.map(c => `
<div class="card"><div class="card-h"><span><i class="fas fa-${c.type==='email'?'envelope':c.type==='generic'?'plug':'link'}"></i> ${U.esc(c.name)}</span><span>${c.active?`<span class="badge bg-resolved">启用</span>`:`<span class="badge bg-rejected">禁用</span>`}</span></div><div class="card-b">
<div class="detail-row"><span class="lbl">类型</span><span class="val">${typeLabel[c.type]||c.type}</span></div>
${c.type!=='email' && c.webhook_url?`<div class="detail-row"><span class="lbl">地址</span><span class="val ts" style="word-break:break-all">${U.esc(c.webhook_url)}</span></div>`:''}
<div class="detail-row"><span class="lbl">事件</span><span class="val">${EventsLabel(c.events)}</span></div>
<div class="mt-2 flex g2">
<button class="btn btn-o btn-sm" onclick="NotificationsPage.showEdit(${c.id},'${U.escJs(c.name)}','${c.type}','${U.escJs(c.webhook_url||'')}','${c.events}',${c.active})"><i class="fas fa-edit"></i> 编辑</button>
${c.type!=='email'?`<button class="btn btn-i btn-sm" onclick="NotificationsPage.test(${c.id})"><i class="fas fa-paper-plane"></i> 测试</button>`:''}
<button class="btn btn-d btn-sm" onclick="NotificationsPage.del(${c.id})"><i class="fas fa-trash"></i></button>
</div>
</div></div>
`).join('') : '<div class="empty"><i class="fas fa-bell-slash"></i><p>暂无通知配置</p></div>';
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
},
showCreate() {
U.modal('添加通知', `
<form id="nc-form">
<div class="form-group"><label>名称 *</label><input id="nc-name" placeholder="如Discord通知" required></div>
<div class="form-group"><label>类型 *</label><select id="nc-type" onchange="NotificationsPage.toggleFields()">
<option value="webhook">Discord Webhook</option>
<option value="email">邮件通知</option>
<option value="generic">通用Webhook(企微/QQ/飞书)</option>
</select></div>
<div class="form-group" id="nc-url-group"><label>Webhook URL</label><input id="nc-url" placeholder="https://..."></div>
<div class="form-group"><label>触发事件</label>
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:4px">${EventOptions('all')}</div>
<input type="hidden" id="nc-events" value="all">
</div>
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">取消</button><button type="submit" class="btn btn-p">创建</button></div>
</form>
`);
bindEventToggles();
document.getElementById('nc-form').onsubmit = async e => { e.preventDefault();
const type = document.getElementById('nc-type').value;
try { await API.post('/notifications',{name:document.getElementById('nc-name').value, type, webhook_url:document.getElementById('nc-url').value, events:document.getElementById('nc-events').value}); App.closeModal(); this.load(); } catch(ex){alert(ex.message);}
};
},
toggleFields() {
const type = document.getElementById('nc-type')?.value || document.getElementById('ne-type')?.value;
const urlGroup = document.querySelector('#nc-url-group') || document.querySelector('#ne-url-group');
if (urlGroup) urlGroup.style.display = type === 'email' ? 'none' : '';
},
showEdit(id, name, type, url, events, active) {
U.modal('编辑通知', `
<form id="ne-form"><div class="form-group"><label>名称</label><input id="ne-name" value="${name}"></div>
<div class="form-group"><label>类型</label><select id="ne-type" onchange="NotificationsPage.toggleFields()">
<option value="webhook" ${type==='webhook'?'selected':''}>Discord Webhook</option>
<option value="email" ${type==='email'?'selected':''}>邮件通知</option>
<option value="generic" ${type==='generic'?'selected':''}>通用Webhook</option>
</select></div>
<div class="form-group" id="ne-url-group" style="${type==='email'?'display:none':''}"><label>Webhook URL</label><input id="ne-url" value="${url}"></div>
<div class="form-group"><label>触发事件</label>
<div style="display:flex;flex-wrap:wrap;gap:8px;margin-top:4px">${EventOptions(events)}</div>
<input type="hidden" id="ne-events" value="${events}">
</div>
<div class="form-group"><label>状态</label><select id="ne-active"><option value="1" ${active?'selected':''}>启用</option><option value="0" ${!active?'selected':''}>禁用</option></select></div>
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">取消</button><button type="submit" class="btn btn-p">保存</button></div></form>
`);
bindEventToggles();
document.getElementById('ne-form').onsubmit = async e => { e.preventDefault();
const newType = document.getElementById('ne-type').value;
try { await API.put(`/notifications/${id}`,{name:document.getElementById('ne-name').value, webhook_url:document.getElementById('ne-url')?.value, events:document.getElementById('ne-events').value, active:document.getElementById('ne-active').value==='1', type:newType}); App.closeModal(); this.load(); } catch(ex){alert(ex.message);}
};
},
async test(id) { try { alert((await API.post(`/notifications/${id}/test`)).message); } catch(ex){alert(ex.message);} },
async del(id) { if(await U.confirm('确认删除?')){ try { await API.req('DELETE',`/notifications/${id}`); this.load(); } catch(ex){alert(ex.message);} } }
};
function EventOptions(current) { return ['all','ticket_created','ticket_claimed','ticket_transferred','ticket_updated'].map(e=>`<label style="cursor:pointer;font-size:12px"><input type="checkbox" value="${e}" class="ev-check" ${String(current||'').split(',').map(s=>s.trim()).includes(e)?'checked':''}> ${EventsLabelSingle(e)}</label>`).join(''); }
function EventsLabel(v) { return v==='all'?'全部事件':v.split(',').map(s=>s.trim()).map(EventsLabelSingle).join(', '); }
function EventsLabelSingle(e) { return {all:'全部',ticket_created:'工单创建',ticket_claimed:'工单认领',ticket_transferred:'工单转交',ticket_updated:'工单更新'}[e]||e; }
function bindEventToggles() {
document.querySelectorAll('.ev-check').forEach(cb=>{ cb.onchange=()=>{ const all=document.querySelectorAll('.ev-check'); const v=Array.from(all).filter(c=>c.checked).map(c=>c.value).join(','); const hid=cb.closest('form').querySelector('input[type="hidden"]'); if(hid)hid.value=v; }; });
}