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.description ? `
${U.esc(i.description)}
` : ''}
`;
},
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);} }
};