新增“数据编辑”

This commit is contained in:
2025-11-13 17:06:10 +08:00
parent 30999e1de4
commit cf57f981c0
3 changed files with 407 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>数据管理</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background:#fafafa; }
.container { max-width: 1100px; margin: 6vh auto; background:#fff; border-radius:10px; box-shadow:0 6px 18px rgba(0,0,0,0.06); padding:20px; }
table { width:100%; border-collapse: collapse; }
th, td { border-bottom:1px solid #eee; padding:8px; text-align:left; vertical-align: top; }
img { max-width: 120px; border:1px solid #eee; border-radius:6px; }
.btn { padding:6px 10px; border:none; border-radius:6px; cursor:pointer; }
.btn-primary { background:#1677ff; color:#fff; }
.btn-danger { background:#ff4d4f; color:#fff; }
.btn-secondary { background:#f0f0f0; }
.muted { color:#666; font-size:12px; }
.modal { position: fixed; inset: 0; display: none; background: rgba(0,0,0,0.4); align-items: center; justify-content: center; }
.modal .dialog { width: 720px; max-width: 92vw; background:#fff; border-radius:10px; padding:16px; }
textarea { width:100%; min-height: 240px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size:14px; }
#kvForm { border:1px solid #eee; border-radius:6px; padding:8px; max-height:300px; overflow:auto; }
</style>
</head>
<body>
<div class="container">
<h2>数据管理</h2>
<p class="muted">仅管理员可见。可查看、编辑、删除所有记录。</p>
<table>
<thead>
<tr>
<th>ID</th>
<th>图片</th>
<th>数据</th>
<th>作者</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for it in items %}
<tr data-id="{{ it._id }}" data-writer="{{ it.writer_id }}" data-image="{{ it.image }}">
<td style="max-width:140px; word-break:break-all;">{{ it._id }}</td>
<td>
{% if it.image %}
<img src="/media/{{ it.image }}" onerror="this.src='';" />
<div class="muted">/media/{{ it.image }}</div>
{% endif %}
</td>
<td>
<pre style="white-space:pre-wrap; word-wrap:break-word;">{{ it.data|safe }}</pre>
</td>
<td>{{ it.writer_id }}</td>
<td>
<button class="btn btn-primary" onclick="openEdit('{{ it._id }}')">编辑</button>
<button class="btn btn-danger" onclick="doDelete('{{ it._id }}')">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div id="modal" class="modal">
<div class="dialog">
<h3>编辑</h3>
<div style="display:flex; gap:8px; align-items:center; margin-bottom:8px;">
<button id="addFieldBtn" class="btn btn-secondary" type="button">添加字段</button>
<button id="syncFromTextBtn" class="btn btn-secondary" type="button">从文本区刷新表单</button>
<span id="editMsg" class="muted"></span>
</div>
<div id="kvForm"></div>
<div style="margin-top:8px;">
<textarea id="resultBox" placeholder="JSON"></textarea>
</div>
<div style="margin-top:12px; display:flex; gap:8px;">
<button class="btn btn-primary" onclick="saveEdit()">保存</button>
<button class="btn" onclick="closeModal()">取消</button>
</div>
</div>
</div>
</div>
<script>
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
const modal = document.getElementById('modal');
const kvForm = document.getElementById('kvForm');
const resultBox = document.getElementById('resultBox');
const editMsg = document.getElementById('editMsg');
const addFieldBtn = document.getElementById('addFieldBtn');
const syncFromTextBtn = document.getElementById('syncFromTextBtn');
let currentId = '';
let currentWriter = '';
let currentImage = '';
function createRow(k = '', v = '') {
const row = document.createElement('div');
row.style.display = 'grid';
row.style.gridTemplateColumns = '1fr 1fr auto';
row.style.gap = '8px';
row.style.marginBottom = '6px';
const kI = document.createElement('input'); kI.type='text'; kI.placeholder='字段名'; kI.value=k;
const vI = document.createElement('input'); vI.type='text'; vI.placeholder='字段值'; vI.value = typeof v==='object'? JSON.stringify(v): (v??'');
const del = document.createElement('button'); del.type='button'; del.className='btn'; del.textContent='删除'; del.onclick=()=>{ kvForm.removeChild(row); syncTextarea(); };
kI.oninput = syncTextarea; vI.oninput = syncTextarea;
row.appendChild(kI); row.appendChild(vI); row.appendChild(del);
return row;
}
function renderForm(obj){
kvForm.innerHTML='';
Object.keys(obj||{}).forEach(k=> kvForm.appendChild(createRow(k, obj[k])));
if (!kvForm.children.length) kvForm.appendChild(createRow());
syncTextarea();
}
function formToObject(){
const o={};
Array.from(kvForm.children).forEach(row=>{
const [kI,vI] = row.querySelectorAll('input');
const k=(kI.value||'').trim(); if(!k) return;
const raw=vI.value; try{ o[k]=JSON.parse(raw);}catch(_){ o[k]=raw; }
});
return o;
}
function syncTextarea(){ resultBox.value = JSON.stringify(formToObject(), null, 2); }
addFieldBtn.onclick = ()=>{ kvForm.appendChild(createRow()); syncTextarea(); };
syncFromTextBtn.onclick = ()=>{ try{ renderForm(JSON.parse(resultBox.value||'{}')); }catch(e){ editMsg.textContent='JSON无效'; } };
function openEdit(id){
const tr = document.querySelector(`tr[data-id="${id}"]`);
currentId = id;
currentWriter = tr?.getAttribute('data-writer') || '';
currentImage = tr?.getAttribute('data-image') || '';
fetch(`/elastic/data/${id}/`, { credentials:'same-origin' })
.then(r=>r.json()).then(d=>{
if(d.status!=='success') throw new Error('获取失败');
const rec=d.data||{};
const dataStr = rec.data || '{}';
let obj={}; try{ obj = typeof dataStr==='string'? JSON.parse(dataStr): (dataStr||{});}catch(_){ obj={}; }
renderForm(obj);
modal.style.display='flex';
}).catch(e=>{ alert(e.message||'发生错误'); });
}
function closeModal(){ modal.style.display='none'; currentId=''; }
function saveEdit(){
const body = {
writer_id: currentWriter,
data: JSON.stringify(formToObject()),
image: currentImage,
};
fetch(`/elastic/data/${currentId}/update/`, {
method:'PUT', credentials:'same-origin',
headers:{ 'Content-Type':'application/json', 'X-CSRFToken': getCookie('csrftoken')||'' },
body: JSON.stringify(body),
}).then(r=>r.json()).then(d=>{
if(d.status!=='success') throw new Error('保存失败');
location.reload();
}).catch(e=>{ editMsg.textContent = e.message||'发生错误'; });
}
function doDelete(id){
if(!confirm('确认删除该记录?')) return;
fetch(`/elastic/data/${id}/delete/`, {
method:'DELETE', credentials:'same-origin',
headers:{ 'X-CSRFToken': getCookie('csrftoken')||'' }
}).then(r=>r.json()).then(d=>{
if(d.status!=='success') throw new Error('删除失败');
location.reload();
}).catch(e=> alert(e.message||'发生错误'));
}
</script>
</body>
</html>