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

143 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
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>分组/子服 *</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>描述</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>结束时间(可选)</label><input type="datetime-local" id="pl-end"></div></div>
<div class="form-group"><label>选项 (每行一个至少2个) *</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" onclick="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('至少需要2个选项');
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('管理分组', `
<form id="gm-form"><div class="grid-2"><div class="form-group"><label>分组名</label><input id="gm-group" required></div><div class="form-group"><label>子服名</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>选项 (每行一个)</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" onclick="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">保存</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);} }
};