Files
2026-09-21 03:38:09 +08:00

19 lines
4.9 KiB
JavaScript

const state={token:localStorage.adminToken||'',data:null};
const $=(s)=>document.querySelector(s); const esc=(v='')=>String(v).replace(/[&<>"']/g,(c)=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
function toast(message){const el=$('#toast');el.textContent=message;el.style.display='block';setTimeout(()=>el.style.display='none',2200)}
async function api(path,options={}){const response=await fetch(`/api/admin${path}`,{...options,headers:{'Content-Type':'application/json','x-admin-token':state.token,...options.headers}});const data=response.status===204?null:await response.json();if(!response.ok)throw new Error(data.error||'请求失败');return data}
async function uploadImages(files){if(!files.length)return[];if(files.length>9)throw new Error('最多上传 9 张图片');const body=new FormData();Array.from(files).forEach(file=>body.append('files',file));const response=await fetch('/api/uploads/admin',{method:'POST',headers:{'x-admin-token':state.token},body});const data=await response.json();if(!response.ok)throw new Error(data.error||'图片上传失败');return data.files.map(x=>x.url)}
function date(v){return new Date(v).toLocaleString('zh-CN',{month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'})}
async function load(){try{state.data=await api('/dashboard');localStorage.adminToken=state.token;$('#login').hidden=true;$('#app').hidden=false;render()}catch(e){$('#login').hidden=false;$('#app').hidden=true;toast(e.message)}}
function render(){const d=state.data;$('#exhibitionCount').textContent=d.exhibitions.length;$('#activityCount').textContent=d.activities.length;$('#pendingCount').textContent=d.groups.filter(g=>g.status==='pending').length;
$('#exhibitionSelect').innerHTML=d.exhibitions.map(x=>`<option value="${x.id}">${esc(x.name)}</option>`).join('');$('#tagChecks').innerHTML=d.tags.map(t=>`<label><input type="checkbox" name="tagIds" value="${t.id}">${esc(t.name)}</label>`).join('');
$('#activityList').innerHTML=d.activities.map(a=>`<article class="row"><div><h3>${esc(a.title)}</h3><p>${esc(a.exhibitionName)} · ${date(a.startsAt)} · ${a.published?'已发布':'草稿'}</p></div><button data-publish="${a.id}" data-value="${!a.published}">${a.published?'下架':'发布'}</button></article>`).join('')||'<p>还没有活动</p>';
$('#exhibitionList').innerHTML=d.exhibitions.map(e=>`<article class="row"><div><h3>${esc(e.name)}</h3><p>${esc(e.venue)} · ${date(e.startsAt)} 至 ${date(e.endsAt)}</p></div></article>`).join('')||'<p>还没有会展</p>';
$('#groupList').innerHTML=d.groups.map(g=>`<article class="row"><div class="group-main"><img class="qr" src="${esc(g.qrImageUrl)}"><div><h3>${esc(g.name)} · ${esc(g.activityTitle)}</h3><p>${esc(g.submitterName||'未知用户')} · ${esc(g.note)} · ${g.status}</p></div></div>${g.status==='pending'?`<div class="row-actions"><button data-review="${g.id}" data-status="approved">通过</button><button class="reject" data-review="${g.id}" data-status="rejected">拒绝</button></div>`:''}</article>`).join('')||'<p>还没有群资料</p>';}
$('#loginForm').onsubmit=(e)=>{e.preventDefault();state.token=$('#token').value;load()};$('#logout').onclick=()=>{localStorage.removeItem('adminToken');location.reload()};
document.addEventListener('click',async(e)=>{const tab=e.target.dataset.tab;if(tab){document.querySelectorAll('nav button').forEach(x=>x.classList.toggle('active',x===e.target));document.querySelectorAll('.panel').forEach(x=>x.hidden=x.id!==tab)}if(e.target.dataset.open)document.getElementById(e.target.dataset.open).showModal();if(e.target.classList.contains('close'))e.target.closest('dialog').close();
try{if(e.target.dataset.publish){await api(`/activities/${e.target.dataset.publish}/publish`,{method:'PATCH',body:JSON.stringify({published:e.target.dataset.value==='true'})});await load()}if(e.target.dataset.review){await api(`/groups/${e.target.dataset.review}/review`,{method:'PATCH',body:JSON.stringify({status:e.target.dataset.status})});await load()}}catch(err){toast(err.message)}});
$('#exhibitionForm').onsubmit=async(e)=>{e.preventDefault();const data=Object.fromEntries(new FormData(e.target));try{await api('/exhibitions',{method:'POST',body:JSON.stringify(data)});e.target.closest('dialog').close();e.target.reset();await load();toast('会展已创建')}catch(err){toast(err.message)}};
$('#activityForm').onsubmit=async(e)=>{e.preventDefault();const form=new FormData(e.target);const data=Object.fromEntries(form);data.published=form.has('published');data.tagIds=form.getAll('tagIds');const typed=(data.imageUrls||'').split('\n').map(x=>x.trim()).filter(Boolean);data.endsAt=data.endsAt||null;try{const uploaded=await uploadImages(e.target.elements.images.files);data.imageUrls=uploaded.concat(typed).slice(0,9);delete data.images;await api('/activities',{method:'POST',body:JSON.stringify(data)});e.target.closest('dialog').close();e.target.reset();await load();toast('活动已创建')}catch(err){toast(err.message)}};
if(state.token)load();