Files
MC_Report/public/js/pages/sources.js
canglan c5ceba7cf0 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
2026-08-22 20:00:12 +08:00

105 lines
5.2 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.
/*
* MC Report System
* Copyright (C) 2026 Sea Network Technology Studio
* Author: CangLan <admin@sea-studio.top>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const SourcesPage = {
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" onclick="SourcesPage.showAdd()"><i class="fas fa-plus"></i> 添加来源</button>';
await this.load();
},
async load() {
const ct = document.getElementById('page-content');
try {
// 管理视图: 全量含 enabled(可显示/切换启用停用)
const rows = await API.get('/sources?manage=1');
ct.innerHTML = rows.length === 0
? '<div class="empty"><i class="fas fa-tag"></i><p>暂无来源</p></div>'
: `<div class="card table-wrap"><table><thead><tr><th>code</th><th>名称</th><th>排序</th><th>状态</th><th>操作</th></tr></thead><tbody>${rows.map(s => `
<tr>
<td><code>${U.esc(s.code)}</code></td>
<td>${U.esc(s.label)}</td>
<td>${s.sort_order}</td>
<td>${s.enabled ? '<span class="badge bg-resolved">启用</span>' : '<span class="badge bg-rejected">停用</span>'}</td>
<td>
<button class="btn btn-o btn-sm" onclick="SourcesPage.showEdit('${U.escJs(s.code)}','${U.escJs(s.label)}',${s.enabled},${s.sort_order})"><i class="fas fa-edit"></i> 编辑</button>
<button class="btn btn-d btn-sm" onclick="SourcesPage.del('${U.escJs(s.code)}')"><i class="fas fa-trash"></i></button>
</td>
</tr>`).join('')}</tbody></table></div>
<p class="help" style="margin-top:8px">来源用于标识玩家身份归属(如: 网易端/皮肤站/正版/离线)。已被身份数据使用的来源不能删除, 只能停用。</p>`;
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
},
showAdd() {
U.modal('添加来源', `
<form id="src-form">
<div class="grid-2">
<div class="form-group"><label>code *</label><input id="src-code" required placeholder="如: official" pattern="[a-z0-9_]{1,20}"></div>
<div class="form-group"><label>名称 *</label><input id="src-label" required placeholder="如: 正版"></div>
</div>
<div class="form-group"><label>排序</label><input id="src-sort" type="number" value="0"></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>
`);
document.getElementById('src-form').onsubmit = async e => {
e.preventDefault();
try {
await API.post('/sources', {
code: document.getElementById('src-code').value,
label: document.getElementById('src-label').value,
sort_order: document.getElementById('src-sort').value,
});
App.closeModal(); this.load();
} catch(ex){alert(ex.message);}
};
},
showEdit(code, label, enabled, sortOrder) {
U.modal('编辑来源', `
<form id="src-edit-form">
<div class="form-group"><label>code</label><input value="${U.esc(code)}" disabled></div>
<div class="grid-2">
<div class="form-group"><label>名称 *</label><input id="se-label" required value="${U.esc(label)}"></div>
<div class="form-group"><label>排序</label><input id="se-sort" type="number" value="${sortOrder}"></div>
</div>
<div class="form-group"><label>状态</label><select id="se-enabled"><option value="1" ${enabled?'selected':''}>启用</option><option value="0" ${!enabled?'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>
`);
document.getElementById('src-edit-form').onsubmit = async e => {
e.preventDefault();
try {
await API.put(`/sources/${code}`, {
label: document.getElementById('se-label').value,
enabled: document.getElementById('se-enabled').value === '1',
sort_order: document.getElementById('se-sort').value,
});
App.closeModal(); this.load();
} catch(ex){alert(ex.message);}
};
},
async del(code) {
if (!await U.confirm(`确认删除来源 ${code}`)) return;
try { await API.delete(`/sources/${code}`); this.load(); } catch(ex){alert(ex.message);}
}
};