Files
Achievement_Inputing/elastic/templates/elastic/upload.html
2025-11-13 17:06:10 +08:00

229 lines
7.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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: 900px; margin: 6vh auto; background: #fff; border-radius: 10px; box-shadow: 0 6px 18px rgba(0,0,0,0.06); padding: 24px; }
.row { display: flex; gap: 16px; }
.col { flex: 1; }
textarea { width: 100%; min-height: 260px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; }
img { max-width: 100%; border: 1px solid #eee; border-radius: 6px; }
.btn { padding: 8px 12px; border: none; border-radius: 6px; cursor: pointer; }
.btn-primary { background: #1677ff; color: #fff; }
.btn-secondary { background: #f0f0f0; }
.muted { color: #666; font-size: 12px; }
.error { color: #d14343; }
.success { color: #179957; }
</style>
</head>
<body>
<div class="container">
<h2>图片上传与识别</h2>
<p class="muted">选择图片后上传,服务端调用大模型解析为可编辑的 JSON再确认入库。</p>
<form id="uploadForm" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" id="fileInput" name="file" accept="image/*" />
<button type="submit" class="btn btn-primary">上传并识别</button>
<span id="uploadMsg" class="muted"></span>
</form>
<div class="row" style="margin-top:16px;">
<div class="col">
<h4>图片预览</h4>
<img id="preview" alt="预览" />
</div>
<div class="col">
<h4>识别结果(可编辑)</h4>
<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>
</div>
<div id="kvForm" style="border:1px solid #eee; border-radius:6px; padding:8px; max-height:300px; overflow:auto;"></div>
<div style="margin-top:8px;">
<textarea id="resultBox" placeholder="识别结果JSON将显示在这里"></textarea>
</div>
</div>
</div>
<div style="margin-top:16px;">
<button id="confirmBtn" class="btn btn-primary" disabled>确认并入库</button>
<button id="clearBtn" class="btn btn-secondary" type="button">清空</button>
<span id="confirmMsg" class="muted"></span>
</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 uploadForm = document.getElementById('uploadForm');
const fileInput = document.getElementById('fileInput');
const preview = document.getElementById('preview');
const resultBox = document.getElementById('resultBox');
const uploadMsg = document.getElementById('uploadMsg');
const confirmBtn = document.getElementById('confirmBtn');
const clearBtn = document.getElementById('clearBtn');
const confirmMsg = document.getElementById('confirmMsg');
const kvForm = document.getElementById('kvForm');
const addFieldBtn = document.getElementById('addFieldBtn');
const syncFromTextBtn = document.getElementById('syncFromTextBtn');
let currentImageRel = '';
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 keyInput = document.createElement('input');
keyInput.type = 'text';
keyInput.placeholder = '字段名';
keyInput.value = k;
const valInput = document.createElement('input');
valInput.type = 'text';
valInput.placeholder = '字段值';
valInput.value = typeof v === 'object' ? JSON.stringify(v) : (v ?? '');
const delBtn = document.createElement('button');
delBtn.type = 'button';
delBtn.className = 'btn btn-secondary';
delBtn.textContent = '删除';
delBtn.onclick = () => { kvForm.removeChild(row); syncTextarea(); };
keyInput.oninput = syncTextarea;
valInput.oninput = syncTextarea;
row.appendChild(keyInput);
row.appendChild(valInput);
row.appendChild(delBtn);
return row;
}
function renderFormFromObject(obj) {
kvForm.innerHTML = '';
Object.keys(obj || {}).forEach(k => {
kvForm.appendChild(createRow(k, obj[k]));
});
if (!kvForm.children.length) kvForm.appendChild(createRow());
syncTextarea();
}
function objectFromForm() {
const obj = {};
Array.from(kvForm.children).forEach(row => {
const [kInput, vInput] = row.querySelectorAll('input');
const k = (kInput.value || '').trim();
if (!k) return;
const raw = vInput.value;
try {
obj[k] = JSON.parse(raw);
} catch (_) {
obj[k] = raw;
}
});
return obj;
}
function syncTextarea() {
const obj = objectFromForm();
resultBox.value = JSON.stringify(obj, null, 2);
}
addFieldBtn.addEventListener('click', () => {
kvForm.appendChild(createRow());
syncTextarea();
});
syncFromTextBtn.addEventListener('click', () => {
try {
const obj = JSON.parse(resultBox.value || '{}');
renderFormFromObject(obj);
} catch (e) {
uploadMsg.textContent = '文本区不是有效JSON';
uploadMsg.className = 'error';
}
});
uploadForm.addEventListener('submit', async (e) => {
e.preventDefault();
uploadMsg.textContent = '';
confirmMsg.textContent = '';
confirmBtn.disabled = true;
resultBox.value = '';
currentImageRel = '';
const file = fileInput.files[0];
if (!file) {
uploadMsg.textContent = '请选择图片文件';
uploadMsg.className = 'error';
return;
}
const formData = new FormData();
formData.append('file', file);
try {
const resp = await fetch('/elastic/upload/', {
method: 'POST',
credentials: 'same-origin',
headers: { 'X-CSRFToken': getCookie('csrftoken') || '' },
body: formData,
});
const data = await resp.json();
if (!resp.ok || data.status !== 'success') {
throw new Error(data.message || '上传识别失败');
}
uploadMsg.textContent = data.message || '识别成功';
uploadMsg.className = 'success';
preview.src = data.image_url;
renderFormFromObject(data.data || {});
currentImageRel = data.image;
confirmBtn.disabled = false;
} catch (e) {
uploadMsg.textContent = e.message || '发生错误';
uploadMsg.className = 'error';
}
});
confirmBtn.addEventListener('click', async () => {
confirmMsg.textContent = '';
try {
const edited = objectFromForm();
const resp = await fetch('/elastic/confirm/', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken') || ''
},
body: JSON.stringify({ data: edited, image: currentImageRel })
});
const data = await resp.json();
if (!resp.ok || data.status !== 'success') {
throw new Error(data.message || '录入失败');
}
confirmMsg.textContent = data.message || '录入成功';
confirmMsg.className = 'success';
} catch (e) {
confirmMsg.textContent = e.message || '发生错误';
confirmMsg.className = 'error';
}
});
clearBtn.addEventListener('click', () => {
fileInput.value = '';
preview.src = '';
resultBox.value = '';
kvForm.innerHTML = '';
uploadMsg.textContent = '';
confirmMsg.textContent = '';
confirmBtn.disabled = true;
});
</script>
</body>
</html>