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

@@ -55,7 +55,7 @@ const NotificationsPage = {
<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" id="nc-url-group"><label id="nc-url-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">
@@ -68,12 +68,25 @@ const NotificationsPage = {
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);}
};
this.toggleFields();
},
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' : '';
const urlLabel = document.querySelector('#nc-url-label') || document.querySelector('#ne-url-label');
if (!urlGroup) return;
if (type === 'email') {
urlGroup.style.display = '';
urlLabel.textContent = '收件人邮箱 (逗号分隔)';
const inp = urlGroup.querySelector('input');
if (inp) { inp.placeholder = 'admin@example.com, staff@example.com'; inp.type = 'email'; }
} else {
urlGroup.style.display = '';
urlLabel.textContent = 'Webhook URL';
const inp = urlGroup.querySelector('input');
if (inp) { inp.placeholder = 'https://...'; inp.type = 'text'; }
}
},
showEdit(id, name, type, url, events, active) {
@@ -84,7 +97,7 @@ const NotificationsPage = {
<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" id="ne-url-group" style="${type==='email'?'display:none':''}"><label id="ne-url-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}">
@@ -93,6 +106,7 @@ const NotificationsPage = {
<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();
this.toggleFields();
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);}