${esc(a.title)}
${esc(a.exhibitionName)} · ${date(a.startsAt)} · ${a.published?'已发布':'草稿'}
const state={token:localStorage.adminToken||'',data:null};
const $=(s)=>document.querySelector(s); const esc=(v='')=>String(v).replace(/[&<>"']/g,(c)=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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=>``).join('');$('#tagChecks').innerHTML=d.tags.map(t=>``).join('');
$('#activityList').innerHTML=d.activities.map(a=>` ${esc(a.exhibitionName)} · ${date(a.startsAt)} · ${a.published?'已发布':'草稿'}${esc(a.title)}
还没有活动
'; $('#exhibitionList').innerHTML=d.exhibitions.map(e=>`${esc(e.venue)} · ${date(e.startsAt)} 至 ${date(e.endsAt)}
还没有会展
'; $('#groupList').innerHTML=d.groups.map(g=>`${esc(g.submitterName||'未知用户')} · ${esc(g.note)} · ${g.status}
还没有群资料
';} $('#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();