Files
MC_Report/public/js/pages/polls.js

143 lines
8.8 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 PollsPage = {
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="PollsPage.showCreate()"><i class="fas fa-plus"></i> 创建投票</button><button class="btn btn-o btn-sm" onclick="PollsPage.showGroupMgr()"><i class="fas fa-cog"></i> 分组</button>'
: '';
await this.load();
},
async load() {
const ct = document.getElementById('page-content');
try {
const groups = await API.get('/polls/active');
const entries = Object.entries(groups);
if (entries.length === 0) {
ct.innerHTML = '<div class="empty"><i class="fas fa-poll"></i><p>暂无进行中的投票</p></div>';
return;
}
ct.innerHTML = entries.map(([group, polls]) => `
<div style="margin-bottom:24px">
<h3 style="font-size:16px;font-weight:700;margin-bottom:12px;color:var(--p)"><i class="fas fa-server"></i> ${U.esc(group)}</h3>
${polls.map(p => this.renderPoll(p)).join('')}
</div>
`).join('');
for (const [group, polls] of entries) for (const p of polls) this.bindVote(p);
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
},
renderPoll(p) {
const opts = p.options || [];
const timed = p.start_time || p.end_time;
return `<div class="card" id="poll-${p.id}">
<div class="card-h"><span>${U.esc(p.server_name)} �${U.esc(p.title)}</span>
<span style="display:flex;gap:8px;align-items:center">
${timed ? `<span class="ts tm">${p.start_time?'� '+U.date(p.start_time):''} ${p.end_time?'� '+U.date(p.end_time):''}</span>`:''}
<span class="ts tm">${p.total||0} �/span>
${Auth.isOwner() ? `<button class="btn btn-o btn-sm" onclick="PollsPage.showEdit(${p.id})"><i class="fas fa-edit"></i></button>` : ''}
</span>
</div>
<div class="card-b">
${p.description?`<p class="tm mb-4">${U.esc(p.description)}</p>`:''}
${p.voted ? `<div class="alert alert-s"><i class="fas fa-check-circle"></i> 已投�/div>` : ''}
<div style="display:flex;flex-direction:column;gap:8px">
${opts.map((o,i) => {
const v = p.voted ? 0 : (p.votes?.[i] || 0);
const pct = p.voted ? 0 : (p.total ? Math.round(v/p.total*100) : 0);
const my = p.my_vote === i;
return `<div class="poll-opt" style="position:relative;overflow:hidden;border:1px solid var(--g200);border-radius:var(--r);${p.voted?'cursor:default':'cursor:pointer'}" data-poll="${p.id}" data-idx="${i}">
${!p.voted ? `<div style="position:absolute;top:0;left:0;bottom:0;width:${pct}%;background:${my?'var(--pl)':'var(--g2)'};transition:width .3s;z-index:0"></div>` : ''}
<div style="position:relative;z-index:1;display:flex;justify-content:space-between;align-items:center;padding:10px 14px;font-size:13px">
<span>${my?'<i class="fas fa-check" style="color:var(--p)"></i> ':''}${U.esc(o)}</span>
${!p.voted ? `<span class="fb">${v}�${pct}%</span>` : ''}
</div>
</div>`;
}).join('')}
</div>
</div>
</div>`;
},
bindVote(p) {
if (p.voted) return;
const card = document.getElementById('poll-'+p.id);
if (!card) return;
card.querySelectorAll('.poll-opt').forEach(el => {
el.addEventListener('click', async () => {
try { await API.post(`/polls/${p.id}/vote`, { option_index: parseInt(el.dataset.idx) }); this.load(); } catch (ex) { alert(ex.message); }
});
});
},
showCreate() {
this.loadGroups().then(groups => {
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="poll-form">
<div class="grid-2"><div class="form-group"><label>分组/å­<C3A5>æœ<C3A6> *</label><select id="pl-gs"><option value="">请选择</option>${opts}</select></div>
<div class="form-group"><label>标题 *</label><input id="pl-title" required></div></div>
<div class="form-group"><label>æ<><C3A6>è¿°</label><textarea id="pl-desc" rows="2"></textarea></div>
<div class="grid-2"><div class="form-group"><label>开始时é—?å<>¯é€?</label><input type="datetime-local" id="pl-start"></div>
<div class="form-group"><label>结æ<E2809C>Ÿæ—¶é—´<>¯é€?</label><input type="datetime-local" id="pl-end"></div></div>
<div class="form-group"><label>选项 (æ¯<C3A6>行一个,至尸? *</label><textarea id="pl-opts" rows="4" placeholder="选项A&#10;选项B&#10;选项C" required></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('poll-form').onsubmit = async e => {
e.preventDefault();
const [group_name, server_name] = document.getElementById('pl-gs').value.split('::');
const opts = document.getElementById('pl-opts').value.split('\n').map(s=>s.trim()).filter(s=>s);
if (opts.length < 2) return alert('至少需�个选项');
try {
await API.post('/polls', { title: document.getElementById('pl-title').value, description: document.getElementById('pl-desc').value, options: opts, group_name, server_name, start_time: document.getElementById('pl-start').value||null, end_time: document.getElementById('pl-end').value||null });
App.closeModal(); this.load();
} catch (ex) { alert(ex.message); }
};
});
},
async loadGroups() { try { return await API.get('/polls/groups'); } catch { return []; } },
showGroupMgr() {
this.loadGroups().then(groups => {
const list = groups.map(g => `<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--g200)">
<span>${U.esc(g.group_name)} / ${U.esc(g.server_name)}</span>
<button class="btn btn-d btn-sm" onclick="PollsPage.delGroup(${g.id})"><i class="fas fa-trash"></i></button>
</div>`).join('');
U.modal('管ç<C2A1>†åˆ†ç»„', `
<form id="gm-form"><div class="grid-2"><div class="form-group"><label>分组å<E2809E>?/label><input id="gm-group" required></div><div class="form-group"><label>å­<C3A5>æœ<C3A6>å<EFBFBD>?/label><input id="gm-server" required></div></div>
<button type="submit" class="btn btn-p btn-sm mb-4"><i class="fas fa-plus"></i> 添加</button></form>
<div>${list||'<p class="tm">暂无</p>'}</div>
`);
document.getElementById('gm-form').onsubmit = async e => { e.preventDefault();
try { await API.post('/polls/groups', { group_name: document.getElementById('gm-group').value, server_name: document.getElementById('gm-server').value }); App.closeModal(); this.showGroupMgr(); } catch(ex){alert(ex.message);}
};
});
},
async delGroup(id) { try { await API.req('DELETE', '/polls/groups/'+id); App.closeModal(); this.showGroupMgr(); } catch(ex){alert(ex.message);} },
async showEdit(id) {
const p = await API.get('/polls/'+id);
document.getElementById('modal-title').textContent = '编辑投票';
document.getElementById('modal-body').innerHTML = `
<form id="pe-form">
<div class="form-group"><label>状æ€?/label><select id="pe-active"><option value="1" ${p.active?'selected':''}<>¯ç”¨</option><option value="0" ${!p.active?'selected':''}>关闭</option></select></div>
<div class="form-group"><label>标题</label><input id="pe-title" value="${U.esc(p.title)}"></div>
<div class="form-group"><label>选项 (æ¯<C3A6>行一ä¸?</label><textarea id="pe-opts" rows="4">${U.esc((JSON.parse(p.options||'[]')).join('\n'))}</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="PollsPage.deletePoll(${id})"><i class="fas fa-trash"></i></button><button type="submit" class="btn btn-p">ä¿<C3A4>å­˜</button></div>
</form>
`;
document.getElementById('modal-overlay').classList.remove('hidden');
document.getElementById('pe-form').onsubmit = async e => {
e.preventDefault();
const opts = document.getElementById('pe-opts').value.split('\n').map(s=>s.trim()).filter(s=>s);
try { await API.put('/polls/'+id, { title: document.getElementById('pe-title').value, active: document.getElementById('pe-active').value==='1', options: opts.length>=2?opts:undefined }); App.closeModal(); this.load(); } catch (ex) { alert(ex.message); }
};
},
async deletePoll(id) { if (!await U.confirm('确认删除�)) return; try { await API.req('DELETE', '/polls/'+id); App.closeModal(); this.load(); } catch(ex){alert(ex.message);} }
};