Files
MC_Report/public/js/pages/features.js

189 lines
11 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const FeaturesPage = {
sort: 'time',
filterGroup: '',
filterServer: '',
filterStatus: '',
async render() { return '<div class="loading"><i class="fas fa-spinner"></i></div>'; },
async mount() {
document.getElementById('page-title').textContent = '更新内容';
document.getElementById('page-actions').innerHTML = Auth.isOwner()
? '<button class="btn btn-p btn-sm" onclick="FeaturesPage.showCreate()"><i class="fas fa-plus"></i> 添加</button>'
: '';
this.renderLayout();
await this.load();
},
renderLayout() {
const ct = document.getElementById('page-content');
ct.innerHTML = `<div style="display:flex;gap:10px;align-items:center;margin-bottom:16px;flex-wrap:wrap">
<select id="feat-group" onchange="FeaturesPage.setFilter()"><option value="">全部分组</option></select>
<select id="feat-server" onchange="FeaturesPage.setFilter()"><option value="">全部å­<C3A5>æœ<C3A6></option></select>
<select id="feat-status" onchange="FeaturesPage.setFilter()"><option value="all">全部状�/option><option value="pending">待定</option><option value="planned">计划�/option><option value="done">已完�/option></select>
<span style="flex:1"></span>
<button class="btn btn-o btn-sm ${this.sort==='time'?'btn-p':''}" id="sort-time" onclick="FeaturesPage.setSort('time')">æ—¶é—´</button>
<button class="btn btn-o btn-sm ${this.sort==='votes'?'btn-p':''}" id="sort-votes" onclick="FeaturesPage.setSort('votes')">热度</button>
</div>
<div id="feat-list"></div>`;
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 = '<option value="">全部分组</option>' + uniqueGroups.map(g => `<option value="${g}" ${this.filterGroup===g?'selected':''}>${g}</option>`).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 = '<option value="">全部å­<C3A5>æœ<C3A6></option>' + uniqueServers.map(s => `<option value="${s}" ${this.filterServer===s?'selected':''}>${s}</option>`).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 ? '<div class="empty"><i class="fas fa-inbox"></i><p>暂无内容</p></div>'
: items.map(i => this.renderItem(i)).join('');
for (const i of items) {
if (Auth.logged()) this.bindVote(i);
this.bindComment(i);
}
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
},
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 `<div class="card" id="feat-${i.id}">
<div class="card-h">
<span><span style="color:${sc};font-size:18px;margin-right:8px">${i.status==='done'?'\u2713':i.status==='planned'?'\u25C9':'\u25CB'}</span> ${U.esc(i.title)}</span>
<div style="display:flex;gap:8px;align-items:center">
${i.group_name ? `<span class="badge bg-player">${U.esc(i.group_name)}</span>` : ''}
${Auth.isOwner() ? `<button class="btn btn-o btn-sm" onclick="FeaturesPage.showEdit(${i.id},'${U.escJs(i.title)}','${U.escJs(i.description||'')}','${i.status}','${U.escJs(i.group_name||'')}','${U.escJs(i.server_name||'')}')"><i class="fas fa-edit"></i></button>` : ''}
</div>
</div>
<div class="card-b">
${i.description ? `<p style="white-space:pre-wrap;margin-bottom:12px">${U.esc(i.description)}</p>` : ''}
<div style="display:flex;align-items:center;gap:16px;margin-bottom:12px">
<button class="btn btn-${i.my_vote?'s':'o'} btn-sm feat-vote" data-id="${i.id}"><i class="fas fa-thumbs-up"></i> ${i.votes_count||0}</button>
<span class="ts tm"><i class="fas fa-comment"></i> ${i.comments_count||0} 讨论</span>
<span class="ts tm">${U.date(i.created_at)}</span>
</div>
<button class="btn btn-o btn-sm w-full" onclick="FeaturesPage.toggleDiscuss(${i.id})"><i class="fas fa-comments"></i> 展开讨论</button>
<div id="discuss-${i.id}" style="display:none;margin-top:12px;border-top:1px solid var(--g200);padding-top:12px">
<div id="comments-${i.id}" class="mb-4"></div>
<form id="comment-form-${i.id}" class="flex g2"><input id="comment-input-${i.id}" placeholder="添加评论..." style="flex:1;padding:8px 12px;border:1px solid var(--g200);border-radius:var(--r)"><button type="submit" class="btn btn-p btn-sm"><i class="fas fa-reply"></i></button></form>
</div>
</div>
</div>`;
},
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 ? '<p class="tm ts">暂无评论</p>'
: item.comments.map(c => `<div style="padding:6px 0;border-bottom:1px solid var(--g100)">
<span class="fb">${U.esc(c.username||'用户')}</span>
${c.role ? U.badge(c.role, 'role') : ''}
<span class="ts tm">${U.date(c.created_at)}</span>
<div style="margin-top:4px;white-space:pre-wrap">${U.esc(c.content)}</div>
</div>`).join('');
} catch { ct.innerHTML = '<p class="tm">加载失败</p>'; }
},
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 => `<option value="${g.group_name}::${g.server_name}">${g.group_name} / ${g.server_name}</option>`).join('');
U.modal('添加', `
<form id="feat-form">
<div class="grid-2"><div class="form-group"><label>标题 *</label><input id="ft-title" required></div>
<div class="form-group"><label>分组/å­<C3A5>æœ<C3A6></label><select id="ft-gs"><option value="::">æ—?/option>${opts}</select></div></div>
<div class="form-group"><label>æ<><C3A6>è¿°</label><textarea id="ft-desc" rows="3"></textarea></div>
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">å<>消</button><button type="submit" class="btn btn-p">添加</button></div>
</form>
`);
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 => `<option value="${g.group_name}::${g.server_name}" ${gn===g.group_name&&sn===g.server_name?'selected':''}>${g.group_name} / ${g.server_name}</option>`).join('');
U.modal('编辑', `
<form id="fe-edit">
<div class="form-group"><label>标题</label><input id="fe-title" value="${title}"></div>
<div class="grid-2"><div class="form-group"><label>状�/label><select id="fe-status"><option value="pending" ${status==='pending'?'selected':''}>待定</option><option value="planned" ${status==='planned'?'selected':''}>计划�/option><option value="done" ${status==='done'?'selected':''}>已完�/option></select></div>
<div class="form-group"><label>分组/å­<C3A5>æœ<C3A6></label><select id="fe-gs"><option value="::">æ—?/option>${opts}</select></div></div>
<div class="form-group"><label>æ<><C3A6>è¿°</label><textarea id="fe-desc" rows="3">${desc}</textarea></div>
<div class="modal-f"><button type="button" class="btn btn-o" data-action="App:closeModal">å<>消</button><button type="button" class="btn btn-d" onclick="FeaturesPage.del(${id})"><i class="fas fa-trash"></i></button><button type="submit" class="btn btn-p">ä¿<C3A4>å­˜</button></div>
</form>
`);
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);} }
};