/* * 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 FeaturesPage = { sort: 'time', filterGroup: '', filterServer: '', filterStatus: '', async render() { return '
'; }, async mount() { document.getElementById('page-title').textContent = '更新内容'; document.getElementById('page-actions').innerHTML = Auth.isOwner() ? '' : ''; this.renderLayout(); await this.load(); }, renderLayout() { const ct = document.getElementById('page-content'); ct.innerHTML = `
`; this.loadGroupOptions(); }, async loadGroupOptions() { try { const groups = await API.get('/polls/groups'); const groupSel = document.getElementById('feat-group'); const serverSel = document.getElementById('feat-server'); const uniqueGroups = [...new Set(groups.map(g => g.group_name))]; groupSel.innerHTML = '' + uniqueGroups.map(g => ``).join(''); const servers = this.filterGroup ? groups.filter(g => g.group_name === this.filterGroup) : groups; const uniqueServers = [...new Set(servers.map(g => g.server_name))]; serverSel.innerHTML = '' + uniqueServers.map(s => ``).join(''); } catch {} }, setFilter() { this.filterGroup = document.getElementById('feat-group').value; this.filterServer = document.getElementById('feat-server').value; this.filterStatus = document.getElementById('feat-status').value; this.loadGroupOptions(); this.load(); }, setSort(s) { this.sort = s; document.getElementById('sort-time').className = 'btn btn-o btn-sm ' + (s==='time'?'btn-p':''); document.getElementById('sort-votes').className = 'btn btn-o btn-sm ' + (s==='votes'?'btn-p':''); this.load(); }, async load() { const ct = document.getElementById('feat-list'); U.loading(ct); try { const params = new URLSearchParams({ sort: this.sort, group_name: this.filterGroup, server_name: this.filterServer, status: this.filterStatus }); const items = await API.get('/features?' + params); ct.innerHTML = items.length === 0 ? '

暂无内容

' : items.map(i => this.renderItem(i)).join(''); for (const i of items) { if (Auth.logged()) this.bindVote(i); } } catch (e) { ct.innerHTML = `
${e.message}
`; } }, renderItem(i) { const sc = i.status==='done'?'var(--s)':i.status==='planned'?'var(--p)':'var(--g5)'; const statusLabel = i.status==='done'?'已完成':i.status==='planned'?'计划中':'待定'; return `
${i.status==='done'?'\u2713':i.status==='planned'?'\u25C9':'\u25CB'} ${U.esc(i.title)}
${i.group_name ? `${U.esc(i.group_name)}` : ''} ${Auth.isOwner() ? `` : ''}
${i.description ? `

${U.esc(i.description)}

` : ''}
${i.comments_count||0} 讨论 ${U.date(i.created_at)}
`; }, toggleDiscuss(id) { const el = document.getElementById('discuss-'+id); if (el.style.display === 'none') { el.style.display = 'block'; this.loadComments(id); } else { el.style.display = 'none'; } }, async loadComments(id) { const ct = document.getElementById('comments-'+id); try { const item = await API.get('/features/'+id); ct.innerHTML = item.comments.length === 0 ? '

暂无评论

' : item.comments.map(c => `
${U.esc(c.username||'用户')} ${c.role ? U.badge(c.role, 'role') : ''} ${U.date(c.created_at)}
${U.esc(c.content)}
`).join(''); } catch { ct.innerHTML = '

加载失败

'; } }, bindComment(i) { const form = document.getElementById('comment-form-'+i.id); if (!form) return; form.onsubmit = async e => { e.preventDefault(); const input = document.getElementById('comment-input-'+i.id); const content = input.value.trim(); if (!content) return; try { await API.post(`/features/${i.id}/comment`, { content }); input.value = ''; this.loadComments(i.id); this.load(); } catch(ex){alert(ex.message);} }; }, bindVote(i) { const card = document.getElementById('feat-'+i.id); if (!card) return; const btn = card.querySelector('.feat-vote'); if (!btn) return; btn.addEventListener('click', async () => { try { await API.post(`/features/${i.id}/vote`); this.load(); } catch (ex) { alert(ex.message); } }); }, async loadGroupsForForm() { try { return await API.get('/polls/groups'); } catch { return []; } }, async showCreate() { const groups = await this.loadGroupsForForm(); const opts = groups.map(g => ``).join(''); U.modal('添加', `
`); document.getElementById('feat-form').onsubmit = async e => { e.preventDefault(); const gs = document.getElementById('ft-gs').value; const [group_name, server_name] = gs === '::' ? ['',''] : gs.split('::'); try { await API.post('/features', { title: document.getElementById('ft-title').value, description: document.getElementById('ft-desc').value, group_name, server_name }); App.closeModal(); this.load(); } catch(ex){alert(ex.message);} }; }, async showEdit(id, title, desc, status, gn, sn) { const groups = await this.loadGroupsForForm(); const opts = groups.map(g => ``).join(''); U.modal('编辑', `
`); document.getElementById('fe-edit').onsubmit = async e => { e.preventDefault(); const gs = document.getElementById('fe-gs').value; const [group_name, server_name] = gs === '::' ? ['',''] : gs.split('::'); try { await API.put('/features/'+id, { title: document.getElementById('fe-title').value, description: document.getElementById('fe-desc').value, status: document.getElementById('fe-status').value, group_name, server_name }); App.closeModal(); this.load(); } catch(ex){alert(ex.message);} }; }, async del(id) { if (!await U.confirm('确认删除?')) return; try { await API.req('DELETE', '/features/'+id); App.closeModal(); this.load(); } catch(ex){alert(ex.message);} } };