问卷总览与管理

本页面仅用于内部数据查看与导出,请勿外泄受访者信息。
'); else out = out + cfg; out = out.replace(/const\s+APP\s*=\s*['"][^'"]+['"]/g, "const APP='"+APP+"'"); out = out.replace(/const\s+TID\s*=\s*['"][^'"]+['"]/g, "const TID='"+UP_TABLE_ID+"'"); // 自动写入「填写语言」隐藏域,放到 form 内(无 form 则放 body) const hidden = `\n`; if(/]*>/i.test(out)) out = out.replace(/]*>/i, m => m + hidden); else if(/]*>/i.test(out)) out = out.replace(/]*>/i, m => m + hidden); return out; } function prepareDownloads(){ if(!UP_TABLE_ID){ alert('请先创建数据表'); return; } if(!UP_FILES.length){ alert('无HTML内容'); return; } const box = document.getElementById('upDowns'); box.style.display = 'block'; box.innerHTML = ''; UP_FILES.forEach(f => { const injected = injectInto(f.html, f.lang); const blob = new Blob([injected], {type:'text/html;charset=utf-8'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = f.fileName; a.textContent = '下载 ' + f.fileName + '(' + f.lang + ')'; box.appendChild(a); box.appendChild(document.createElement('br')); }); const res = document.getElementById('upResult'); res.style.display = 'block'; res.innerHTML += `
已为 ${UP_FILES.length} 个语言版注入同一表ID与「填写语言」,请逐个点击下载,放入项目目录后重新部署。`; } async function addToMaster(){ if(!UP_TABLE_ID){ alert('请先创建数据表'); return; } const name = document.getElementById('upName').value.trim(); const cat = document.getElementById('upCat').value.trim(); const group = document.getElementById('upGroup').value.trim(); if(!name || !cat || !group){ alert('请填写问卷名称、分类、所属栏目'); return; } if(!UP_FILES.length){ alert('请先上传 HTML'); return; } const langs = [...new Set(UP_FILES.map(f=>f.lang))].join(';'); const links = UP_FILES.map(f => `https://guinea-visit-questionnaire.pages.dev/${encodeURIComponent(f.fileName)}`).join(';'); const fields = { '问卷名称': name, '语言': langs, '分类': cat, '数据表ID': UP_TABLE_ID, '已下发': false, '邀请人数': 0, '问卷链接': links, '创建时间': new Date().toISOString().slice(0,10), '备注': group }; const existing = findExistingSurvey(group, cat); try { const res = document.getElementById('upResult'); res.style.display = 'block'; if(existing){ // 更新已有记录(走本项目 LOCAL_PROXY,batch_update 已验证可用) const url = `${LOCAL_PROXY}/open-apis/bitable/v1/apps/${APP}/tables/${MASTER_TID}/records/batch_update`; const r = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ records:[{ record_id:existing.record_id, fields }] }) }); const j = await r.json(); if(j.code !== 0) throw new Error(j.msg || ('code '+j.code)); res.innerHTML += `
已更新管理表中的「${esc(name)}」。语言:${esc(langs)}。`; } else { // 新增记录(走本项目 LOCAL_PROXY,中文单条 create 已验证可用) const url = `${LOCAL_PROXY}/open-apis/bitable/v1/apps/${APP}/tables/${MASTER_TID}/records`; const r = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ fields }) }); const j = await r.json(); if(j.code !== 0) throw new Error(j.msg || ('code '+j.code)); res.innerHTML += `
已登记到管理表。语言:${esc(langs)};下发链接见「问卷链接」列。`; } showOverview(); } catch(e){ alert('登记失败:' + e.message); console.error(e); } } // ---- 新建活动(自动生成三个小分类)---- function openActModal(){ document.getElementById('actName').value = ''; document.getElementById('actModal').style.display = 'flex'; document.getElementById('actName').focus(); } function closeActModal(){ document.getElementById('actModal').style.display = 'none'; } async function addActivity(){ const name = document.getElementById('actName').value.trim(); if(!name){ alert('请输入活动名称'); return; } const cats = ['参访前调查', '参访后调查', '效果评估']; const records = cats.map(cat => ({ fields:{ 问卷名称: name + ' · ' + cat, 语言: '中文 / 法语', 分类: cat, 数据表ID: '', 已下发: false, 邀请人数: 0, 问卷链接: '', 创建时间: new Date().toISOString().slice(0,10), 备注: '' }})); const btn = document.getElementById('actConfirm'); btn.textContent = '创建中…'; try { const url = `${LOCAL_PROXY}/open-apis/bitable/v1/apps/${APP}/tables/${MASTER_TID}/records/batch_create`; const r = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ records }) }); const j = await r.json(); if(j.code !== 0) throw new Error(j.msg || ('code '+j.code)); closeActModal(); showOverview(); } catch(e){ alert('创建失败:' + e.message); console.error(e); } finally { btn.textContent = '确定创建'; } } // ---- 删除单个问卷分类 ---- async function delSurvey(rid){ if(!confirm('确定删除该问卷分类吗?\n(仅删除后台登记项,数据表内已回收的问卷不会删除)')) return; try { const url = `${LOCAL_PROXY}/open-apis/bitable/v1/apps/${APP}/tables/${MASTER_TID}/records/batch_delete`; const r = await fetch(url, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ records:[rid] }) }); const j = await r.json(); if(j.code !== 0) throw new Error(j.msg || ('code '+j.code)); showOverview(); } catch(e){ alert('删除失败:' + e.message); console.error(e); } } document.getElementById('refresh').addEventListener('click', () => { if(currentView === 'overview') showOverview(); else load(); }); document.getElementById('export').addEventListener('click', exportXLSX); document.getElementById('langFilter').addEventListener('change', onLangChange); document.getElementById('btnNewActivity').addEventListener('click', openActModal); document.getElementById('actConfirm').addEventListener('click', addActivity); document.getElementById('actCancel').addEventListener('click', closeActModal); (function(){ const layout = document.querySelector('.admin-layout'); const toggle = document.getElementById('navToggle'); toggle.addEventListener('click', () => { layout.classList.toggle('collapsed'); toggle.textContent = layout.classList.contains('collapsed') ? '▶' : '◀'; toggle.title = layout.classList.contains('collapsed') ? '展开侧边栏' : '收起侧边栏'; }); })(); renderNav(); bindUpload(); showOverview();