- 52 JS files (backend + public/js): header with Copyright (C) 2026 Sea Network Technology Studio Author: CangLan <admin@sea-studio.top> + AGPLv3 notice - idempotent (skips if header present), all syntax-checked
114 lines
4.6 KiB
JavaScript
114 lines
4.6 KiB
JavaScript
/*
|
|
* 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 U = {
|
|
STATUS: { pending:'待处理', processing:'处理中', awaiting_info:'待补充', appealing:'申诉中', resolved:'已解决', rejected:'已驳回', closed:'已关闭' },
|
|
TYPE: { report:'举报', suggestion:'建议', appeal:'申诉', result_appeal:'结果申诉' },
|
|
ROLE: { owner:'服主', admin:'管理员', player:'玩家' },
|
|
PRIORITY: { low:'低', medium:'中', high:'高', urgent:'紧急' },
|
|
|
|
badge(s, t) {
|
|
if (t === 'status') return `<span class="badge bg-${s}">${this.STATUS[s]||s}</span>`;
|
|
if (t === 'type') return `<span class="badge bg-${s}">${this.TYPE[s]||s}</span>`;
|
|
if (t === 'role') return `<span class="badge bg-${s}">${this.ROLE[s]||s}</span>`;
|
|
if (t === 'priority') return `<span class="badge bg-${s}">${this.PRIORITY[s]||s}</span>`;
|
|
return s;
|
|
},
|
|
|
|
date(d) {
|
|
if (!d) return '-';
|
|
const dt = new Date(d);
|
|
if (isNaN(dt.getTime())) return d;
|
|
const p = n => String(n).padStart(2, '0');
|
|
return `${dt.getFullYear()}-${p(dt.getMonth()+1)}-${p(dt.getDate())} ${p(dt.getHours())}:${p(dt.getMinutes())}`;
|
|
},
|
|
|
|
esc(s) {
|
|
if (!s) return '';
|
|
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
|
},
|
|
|
|
escJs(s) {
|
|
if (!s) return '';
|
|
return String(s).replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/"/g,'"').replace(/\n/g,'\\n');
|
|
},
|
|
|
|
// ---- 动态来源(缓存, 注册/绑定身份/用户管理等处共用) ----
|
|
_sourcesCache: null,
|
|
async sources(force) {
|
|
if (force || !this._sourcesCache) {
|
|
try { this._sourcesCache = await API.get('/sources'); }
|
|
catch { this._sourcesCache = []; }
|
|
}
|
|
return this._sourcesCache;
|
|
},
|
|
// 生成下拉 HTML; includeEmpty 时带"不指定"选项
|
|
async sourcesOptions(selected, includeEmpty) {
|
|
const list = await this.sources();
|
|
const opts = (includeEmpty ? '<option value="">不指定</option>' : '') +
|
|
list.map(s => `<option value="${this.esc(s.code)}" ${s.code===selected?'selected':''}>${this.esc(s.label)}</option>`).join('');
|
|
return opts;
|
|
},
|
|
// code → 显示标签(找不到时显示原 code)
|
|
async sourceLabel(code) {
|
|
if (!code) return '-';
|
|
const list = await this.sources();
|
|
const hit = list.find(s => s.code === code);
|
|
return hit ? hit.label : code;
|
|
},
|
|
async sourceBadge(code) {
|
|
if (!code) return '-';
|
|
const list = await this.sources();
|
|
const hit = list.find(s => s.code === code);
|
|
return `<span class="badge bg-admin">${this.esc(hit ? hit.label : code)}</span>`;
|
|
},
|
|
|
|
showAlert(containerId, type, msg) {
|
|
const ct = document.getElementById(containerId || 'page-content');
|
|
if (!ct) return;
|
|
const el = document.createElement('div');
|
|
el.className = `alert alert-${type === 'success' ? 's' : type === 'error' ? 'e' : 'i'}`;
|
|
el.textContent = msg;
|
|
ct.prepend(el);
|
|
setTimeout(() => el.remove(), 6000);
|
|
},
|
|
|
|
loading(ct) { if (!ct) return; ct.innerHTML = '<div class="loading"><i class="fas fa-spinner"></i><p>加载中...</p></div>'; },
|
|
|
|
modal(title, html, w) {
|
|
const o = document.getElementById('modal-overlay');
|
|
if (!o) return;
|
|
document.getElementById('modal-title').textContent = title;
|
|
document.getElementById('modal-body').innerHTML = html;
|
|
o.querySelector('.modal-box').style.width = w || '';
|
|
o.classList.remove('hidden');
|
|
},
|
|
|
|
confirm(msg) {
|
|
return new Promise(resolve => {
|
|
this.modal('确认', `
|
|
<p>${this.esc(msg)}</p>
|
|
<div class="modal-f"><button class="btn btn-o btn-confirm-cancel">取消</button><button class="btn btn-p btn-confirm-ok">确认</button></div>
|
|
`);
|
|
document.querySelector('.btn-confirm-cancel').onclick = () => { App.closeModal(); resolve(false); };
|
|
document.querySelector('.btn-confirm-ok').onclick = () => { App.closeModal(); resolve(true); };
|
|
});
|
|
}
|
|
};
|