refactor: remove polls+features, move to standalone mc-vote repo

This commit is contained in:
2026-07-14 03:11:26 +08:00
parent 3d7393817c
commit d61ad2109f
7 changed files with 0 additions and 331 deletions

View File

@@ -1,89 +0,0 @@
const FeaturesPage = {
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>'
: '';
await this.load();
},
async load() {
const ct = document.getElementById('page-content');
try {
const items = await API.get('/features');
ct.innerHTML = items.length === 0
? '<div class="empty"><i class="fas fa-lightbulb"></i><p>暂无更新项目</p></div>'
: items.map(i => this.renderItem(i)).join('');
for (const i of items) if (Auth.logged()) this.bindVote(i);
} catch (e) { ct.innerHTML = `<div class="alert alert-e">${e.message}</div>`; }
},
renderItem(i) {
const statusColor = 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>${U.esc(i.title)}</span>
<div style="display:flex;gap:8px;align-items:center">
<span style="font-size:11px;color:${statusColor}">● ${statusLabel}</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}')"><i class="fas fa-edit"></i></button>` : ''}
</div>
</div>
<div class="card-b">
${i.description ? `<p class="tm mb-4">${U.esc(i.description)}</p>` : ''}
<div style="display:flex;align-items:center;gap:12px">
<button class="btn btn-${i.my_vote?'s':'o'} btn-sm feat-vote" data-id="${i.id}">
<i class="fas fa-chevron-up"></i> ${i.votes_count || 0}
</button>
<span class="ts tm">${i.my_vote ? '已投票 (点击取消)' : '点击投票'}</span>
</div>
</div>
</div>`;
},
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); }
});
},
showCreate() {
U.modal('添加项目', `
<form id="feat-form">
<div class="form-group"><label>标题 *</label><input id="ft-title" required></div>
<div class="form-group"><label>描述</label><textarea id="ft-desc" rows="2"></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('feat-form').onsubmit = async e => { e.preventDefault();
try { await API.post('/features', { title: document.getElementById('ft-title').value, description: document.getElementById('ft-desc').value }); App.closeModal(); this.load(); } catch(ex){alert(ex.message);}
};
},
async showEdit(id, title, desc, status) {
U.modal('编辑', `
<form id="fe-edit">
<div class="form-group"><label>标题</label><input id="fe-title" value="${title}"></div>
<div class="form-group"><label>描述</label><textarea id="fe-desc" rows="2">${desc}</textarea></div>
<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="modal-f"><button type="button" class="btn btn-o" onclick="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">保存</button></div>
</form>
`);
document.getElementById('fe-edit').onsubmit = async e => { e.preventDefault();
try { await API.put('/features/'+id, { title: document.getElementById('fe-title').value, description: document.getElementById('fe-desc').value, status: document.getElementById('fe-status').value }); 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);}
}
};

View File

@@ -1,108 +0,0 @@
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>'
: '';
await this.load();
},
async load() {
const ct = document.getElementById('page-content');
try {
const polls = await API.get('/polls/active');
ct.innerHTML = polls.length === 0
? '<div class="empty"><i class="fas fa-poll"></i><p>暂无进行中的投票</p></div>'
: polls.map(p => this.renderPoll(p)).join('');
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 max = Math.max(1, ...Object.values(p.votes || {}));
const showResult = !p.voted;
return `<div class="card" id="poll-${p.id}">
<div class="card-h"><span>${U.esc(p.title)}</span><span class="ts tm">${p.total||0} 票</span>
${Auth.isOwner() ? `<span><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 && !showResult ? `<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 = showResult ? (p.votes?.[i] || 0) : 0;
const pct = showResult && 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}">
${showResult ? `<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>
${showResult ? `<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() {
U.modal('创建投票', `
<form id="poll-form">
<div class="form-group"><label>标题 *</label><input id="pl-title" required></div>
<div class="form-group"><label>描述</label><textarea id="pl-desc" rows="2"></textarea></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 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 }); App.closeModal(); this.load(); } catch (ex) { alert(ex.message); }
};
},
async showEdit(id) {
U.modal('编辑投票', '<div class="loading"><i class="fas fa-spinner"></i></div>');
const p = await API.get('/polls/' + id);
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('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);}
}
};