const FeaturesPage = { sort: 'time', filterGroup: '', filterServer: '', 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.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 }); 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 sl = i.status==='done'?'已完成':i.status==='planned'?'计划中':'待定'; return `
${U.esc(i.title)} ${i.group_name ? `— ${U.esc(i.group_name)}/${U.esc(i.server_name)}` : ''}
● ${sl} ${Auth.isOwner() ? `` : ''}
${i.description ? `

${U.esc(i.description)}

` : ''}
${i.my_vote ? '已投票' : '投票'}
`; }, bindVote(i) { const card = document.getElementById('feat-'+i.id); if (!card) return; card.querySelector('.feat-vote').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);} } };