109 lines
5.3 KiB
JavaScript
109 lines
5.3 KiB
JavaScript
const TicketsPage = {
|
||
tab: 'all',
|
||
filters: {},
|
||
|
||
async render() { return '<div class="loading"><i class="fas fa-spinner"></i></div>'; },
|
||
|
||
async mount() {
|
||
document.getElementById('page-title').textContent = 'å·¥å<C2A5>•列表';
|
||
document.getElementById('page-actions').innerHTML = `
|
||
<button class="btn btn-p btn-sm" onclick="App.navigate('tickets','create?type=report')"><i class="fas fa-flag"></i> 举报</button>
|
||
<button class="btn btn-s btn-sm" onclick="App.navigate('tickets','create?type=suggestion')"><i class="fas fa-lightbulb"></i> 建议</button>
|
||
`;
|
||
this.renderLayout();
|
||
await this.loadTab(this.tab);
|
||
},
|
||
|
||
renderLayout() {
|
||
const ct = document.getElementById('page-content');
|
||
const isStaff = Auth.isAdmin();
|
||
ct.innerHTML = `
|
||
<div class="tab-nav" id="ticket-tabs">
|
||
<button data-tab="all" class="active">全部</button>
|
||
<button data-tab="pending">待认�/button>
|
||
<button data-tab="claimed">已接å<C2A5>?/button>
|
||
${Auth.user().role === 'player' ? '<button data-tab="mine">我的工å<C2A5>•</button>' : ''}
|
||
</div>
|
||
<div class="filter-bar">
|
||
<select id="ft-type" onchange="TicketsPage.applyFilters()"><option value="">全部类型</option>${Object.entries(U.TYPE).map(([k,v])=>`<option value="${k}">${v}</option>`).join('')}</select>
|
||
<select id="ft-status" onchange="TicketsPage.applyFilters()"><option value="">全部状�/option>${Object.entries(U.STATUS).map(([k,v])=>`<option value="${k}">${v}</option>`).join('')}</select>
|
||
<select id="ft-priority" onchange="TicketsPage.applyFilters()"><option value="">全部优先�/option>${Object.entries(U.PRIORITY).map(([k,v])=>`<option value="${k}">${v}</option>`).join('')}</select>
|
||
<input id="ft-search" placeholder="æ<>œç´¢..." onkeyup="if(event.key==='Enter')TicketsPage.applyFilters()">
|
||
<button class="btn btn-o btn-sm" onclick="TicketsPage.applyFilters()"><i class="fas fa-search"></i></button>
|
||
<button class="btn btn-o btn-sm" onclick="TicketsPage.resetFilters()"><i class="fas fa-undo"></i></button>
|
||
</div>
|
||
<div id="ticket-list"></div>
|
||
`;
|
||
|
||
document.querySelectorAll('#ticket-tabs button').forEach(btn => {
|
||
btn.onclick = () => {
|
||
document.querySelectorAll('#ticket-tabs button').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
this.tab = btn.dataset.tab;
|
||
this.loadTab(this.tab);
|
||
};
|
||
});
|
||
},
|
||
|
||
async loadTab(tab) {
|
||
const list = document.getElementById('ticket-list');
|
||
U.loading(list);
|
||
try {
|
||
const params = new URLSearchParams();
|
||
if (this.filters.type) params.set('type', this.filters.type);
|
||
if (this.filters.status) params.set('status', this.filters.status);
|
||
if (this.filters.priority) params.set('priority', this.filters.priority);
|
||
if (this.filters.search) params.set('search', this.filters.search);
|
||
|
||
if (tab === 'pending') params.set('claimed', '0');
|
||
else if (tab === 'claimed') params.set('claimed', '1');
|
||
else if (tab === 'mine') params.set('status', '');
|
||
|
||
const qs = params.toString();
|
||
const tickets = await API.get('/tickets' + (qs ? '?' + qs : ''));
|
||
|
||
if (tab === 'mine') {
|
||
this.renderList(list, tickets);
|
||
return;
|
||
}
|
||
this.renderList(list, tickets);
|
||
} catch (e) { list.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
|
||
},
|
||
|
||
renderList(ct, tickets) {
|
||
ct.innerHTML = tickets.length === 0
|
||
? `<div class="empty"><i class="fas fa-inbox"></i><p>æš‚æ— å·¥å<C2A5>•</p></div>`
|
||
: `<div class="card table-wrap"><table><thead><tr>
|
||
<th>#</th><th>类型</th><th>æ ‡é¢˜</th><th>æ<><C3A6>交è€?/th><th>对象</th><th>状æ€?/th><th>优先çº?/th><th>è´Ÿè´£äº?/th><th>æ—¶é—´</th><th>æ“<C3A6>作</th>
|
||
</tr></thead><tbody>${tickets.map(t => `
|
||
<tr>
|
||
<td>${t.id}</td>
|
||
<td>${U.badge(t.type,'type')}</td>
|
||
<td><a href="#" onclick="App.navigate('ticket',${t.id});return false" style="color:var(--p);text-decoration:none;font-weight:600">${U.esc(t.title)}</a></td>
|
||
<td>${U.esc(t.reporter_game_name)}<br><span class="tm ts">UID:${U.esc(t.reporter_game_uid)}</span></td>
|
||
<td>${t.target_game_name?`${U.esc(t.target_game_name)}${t.target_game_uid?`<br><span class="tm ts">UID:${U.esc(t.target_game_uid)}</span>`:''}`:'-'}</td>
|
||
<td>${U.badge(t.status,'status')}</td>
|
||
<td>${U.badge(t.priority,'priority')}</td>
|
||
<td>${t.assignee_name?`<span class="badge bg-admin">${U.esc(t.assignee_name)}</span>`:`<span class="tm">待认�/span>`}</td>
|
||
<td class="ts">${U.date(t.updated_at)}</td>
|
||
<td><button class="btn btn-o btn-sm" onclick="App.navigate('ticket',${t.id})"><i class="fas fa-eye"></i></button></td>
|
||
</tr>`).join('')}</tbody></table></div>`;
|
||
},
|
||
|
||
applyFilters() {
|
||
this.filters = {
|
||
type: document.getElementById('ft-type').value,
|
||
status: document.getElementById('ft-status').value,
|
||
priority: document.getElementById('ft-priority').value,
|
||
search: document.getElementById('ft-search').value,
|
||
};
|
||
this.loadTab(this.tab);
|
||
},
|
||
|
||
resetFilters() {
|
||
this.filters = {};
|
||
document.querySelectorAll('.filter-bar select, .filter-bar input').forEach(el => { if (el.tagName === 'SELECT' || el.tagName === 'INPUT') el.value = ''; });
|
||
this.loadTab(this.tab);
|
||
}
|
||
};
|