/* * MC Report System * Copyright (C) 2026 Sea Network Technology Studio * Author: CangLan * * 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 . */ const Dashboard = { async render() { return '
'; }, async mount() { const ct = document.getElementById('page-content'); document.getElementById('page-title').textContent = '控制台'; document.getElementById('page-actions').innerHTML = ` `; try { const [stats, exports] = await Promise.all([ API.get('/tickets/stats'), Auth.isOwner() ? API.get('/export/dashboard') : null, ]); const bans = Auth.isAdmin() ? await API.get('/bans/active').catch(()=>[]) : []; const identities = await API.get('/auth/identities').catch(()=>[]); this.renderContent(ct, stats, exports, bans, identities); // 渲染完成后将来源 code 替换为动态标签 U.sources().then(() => { ct.querySelectorAll('[data-src]').forEach(el => { const code = el.dataset.src; const hit = (U._sourcesCache||[]).find(s => s.code === code); el.textContent = hit ? hit.label : (code || '-'); }); }).catch(()=>{}); } catch (e) { ct.innerHTML = `
${e.message}
`; } }, renderContent(ct, stats, exports, bans, identities) { const byStatus = stats.byStatus || {}; const banData = bans || []; const idents = identities || []; ct.innerHTML = `
${stats.total}
工单总数
${byStatus.pending || 0}
待处理
${byStatus.processing || 0}
处理中
${byStatus.resolved || 0}
已解决
${Auth.isAdmin() ? `
封禁列表 ${Auth.isOwner() ? ``:''}
${banData.length === 0 ? '暂无封禁记录' : `${banData.map(b=>``).join('')}
玩家类型原因时长时间
${U.esc(b.player_name)}${b.type==='ban'?'封禁':b.type==='mute'?'禁言':'其他'}${U.esc(b.reason||'')}${b.duration||'永久'}${U.date(b.created_at)}
`}
` : ''}
我的身份 ${idents.length < 2 ? `` : ''}
${idents.length === 0 ? '暂无身份,点击右上角绑定' : `${idents.map(it=>``).join('')}
来源游戏名绑定时间操作
${U.esc(it.source||'-')} ${U.esc(it.game_name)} ${U.date(it.created_at)} ${idents.length > 1 ? `` : '主身份'}
`}
${Auth.isOwner() && exports ? `
类型分布
${(exports.byType||[]).map(r => `
${U.TYPE[r.type]||r.type}${r.c} 条
`).join('')}
状态分布
${(exports.byStatus||[]).map(r => `
${U.STATUS[r.status]||r.status}${r.c} 条
`).join('')}
${exports.monthly?.length ? `
月度趋势
${exports.monthly.map(m=>``).join('')}
月份数量
${m.m}${m.c}
` : ''} ` : ''}

提示: 可在"工单列表"中查看和管理所有工单

`; }, showAddBan() { U.modal('添加封禁', `
`); U.sources().then(list => { const sel = document.getElementById('ban-source'); if (sel) sel.innerHTML = '' + list.map(s => ``).join(''); }).catch(()=>{}); document.getElementById('ban-form').onsubmit = async e => { e.preventDefault(); try { await API.post('/bans', { player_name: document.getElementById('ban-name').value, player_uid: document.getElementById('ban-uid').value, source: document.getElementById('ban-source').value, type: document.getElementById('ban-type').value, duration: document.getElementById('ban-dur').value, reason: document.getElementById('ban-reason').value }); App.closeModal(); this.mount(); } catch(ex) { alert(ex.message); } }; setTimeout(() => Dashboard.checkMinDuration(), 100); }, showAddIdentity() { U.modal('绑定身份', `
`); // 动态加载来源 U.sources().then(async list => { const sel = document.getElementById('ident-source'); sel.innerHTML = list.length ? list.map(s => ``).join('') : ''; }).catch(()=>{}); document.getElementById('ident-form').onsubmit = async e => { e.preventDefault(); try { await API.post('/auth/identities', { source: document.getElementById('ident-source').value, game_name: document.getElementById('ident-name').value, }); App.closeModal(); this.mount(); } catch (ex) { const el = document.getElementById('ident-err'); el.textContent = ex.message; el.classList.remove('hidden'); } }; }, async removeIdentity(id) { if (!await U.confirm('确认解绑该身份?')) return; try { await API.delete(`/auth/identities/${id}`); this.mount(); } catch (ex) { alert(ex.message); } }, async checkMinDuration() { const name = document.getElementById('ban-name')?.value; const uid = document.getElementById('ban-uid')?.value; const type = document.getElementById('ban-type')?.value; if (!name && !uid) return; try { const r = await API.get('/bans/min-duration?player_name='+name+'&player_uid='+uid+'&type='+type); const sel = document.getElementById('ban-dur'); if (r.minDays > 0) { sel.innerHTML = ``; } else { sel.innerHTML = ``; } } catch {} } };