版本更新:
1、已实现多图识别并入库 2、增加图片上传时删除图片功能 3、改用模型glm-4.6v预计5月份到期 4、已对环境txt做更改
This commit is contained in:
@@ -328,7 +328,7 @@ function renderTable(data) {
|
||||
row.innerHTML = `
|
||||
<td style="max-width:140px; word-break:break-all; font-size: 12px;">${item._id || item.id || ''}</td>
|
||||
<td>
|
||||
${item.image_url ? `<img src="${item.image_url}" onerror="this.src=''; this.alt='图片加载失败'" class="clickable-image" data-image="${item.image_url}" />` : '无图片'}
|
||||
<div style="display:flex;gap:6px;flex-wrap:wrap;">${buildImageCell(item)}</div>
|
||||
</td>
|
||||
<td>
|
||||
<pre style="white-space:pre-wrap; word-wrap:break-word; max-height: 100px; overflow-y: auto; font-size: 12px; margin: 0;">${escapeHtml(displayData)}</pre>
|
||||
@@ -343,6 +343,12 @@ function renderTable(data) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildImageCell(item) {
|
||||
const urls = Array.isArray(item.image_urls) ? item.image_urls : (item.image_url ? [item.image_url] : []);
|
||||
if (!urls.length) return '无图片';
|
||||
return urls.map(u => `<img src="${u}" onerror="this.src=''; this.alt='图片加载失败'" class="clickable-image" data-image="${u}" />`).join('');
|
||||
}
|
||||
|
||||
// 转义HTML以防止XSS
|
||||
function escapeHtml(unsafe) {
|
||||
return unsafe
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
.preview-box {flex: 1; text-align: center; }
|
||||
.preview-box h3 {margin-top: 0;color: #334155; }
|
||||
.preview-box img { max-width: 100%;max-height: 300px;border: 1px solid #e2e8f0;border-radius: 8px;object-fit: contain;}
|
||||
.preview-list {display: grid;grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));gap: 12px;}
|
||||
.preview-item {position: relative;}
|
||||
.preview-item img {width: 100%;max-height: 220px;border: 1px solid #e2e8f0;border-radius: 8px;object-fit: contain;}
|
||||
.preview-remove {position: absolute;top: 6px;right: 6px;border: none;border-radius: 999px;background: rgba(15,23,42,0.8);color: #fff;width: 24px;height: 24px;cursor: pointer;display: flex;align-items: center;justify-content: center;font-size: 14px;line-height: 1;}
|
||||
.result-box {flex: 1;}
|
||||
.result-box h3 { margin-top: 0; color: #334155;}
|
||||
.form-controls { display: flex;gap: 8px;margin-bottom: 12px;flex-wrap: wrap;}
|
||||
@@ -89,7 +93,8 @@
|
||||
<p>点击下方按钮选择图片,或拖拽图片到此区域</p>
|
||||
<form id="uploadForm" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="file" id="fileInput" name="file" accept="image/*" required />
|
||||
<input type="file" id="fileInput" name="file" accept="image/*" multiple />
|
||||
<span id="fileHint" class="muted"></span>
|
||||
<br>
|
||||
<button type="submit" class="btn btn-primary">上传并识别</button>
|
||||
</form>
|
||||
@@ -103,7 +108,7 @@
|
||||
<div class="preview-container">
|
||||
<div class="preview-box">
|
||||
<h3>图片预览</h3>
|
||||
<img id="preview" alt="预览" />
|
||||
<div id="previewList" class="preview-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="result-box">
|
||||
@@ -134,7 +139,8 @@ function getCookie(name) {
|
||||
|
||||
const uploadForm = document.getElementById('uploadForm');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const preview = document.getElementById('preview');
|
||||
const fileHint = document.getElementById('fileHint');
|
||||
const previewList = document.getElementById('previewList');
|
||||
const resultBox = document.getElementById('resultBox');
|
||||
const uploadMsg = document.getElementById('uploadMsg');
|
||||
const confirmBtn = document.getElementById('confirmBtn');
|
||||
@@ -148,7 +154,8 @@ const progressWrap = document.getElementById('progressWrap');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
let currentImageRel = '';
|
||||
let currentImageRel = [];
|
||||
let selectedFiles = [];
|
||||
|
||||
function setProgress(p, text){
|
||||
const v = Math.max(0, Math.min(100, Math.round(p||0)));
|
||||
@@ -221,22 +228,64 @@ function handleDrop(e) {
|
||||
const dt = e.dataTransfer;
|
||||
const files = dt.files;
|
||||
if (files.length) {
|
||||
fileInput.files = files;
|
||||
const event = new Event('change', { bubbles: true });
|
||||
fileInput.dispatchEvent(event);
|
||||
addFiles(files);
|
||||
}
|
||||
}
|
||||
|
||||
// 文件选择后预览
|
||||
fileInput.addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
preview.src = e.target.result;
|
||||
function setPreviewList(urls) {
|
||||
previewList.innerHTML = '';
|
||||
(urls || []).forEach((url, index) => {
|
||||
if (!url) return;
|
||||
const item = document.createElement('div');
|
||||
item.className = 'preview-item';
|
||||
item.dataset.index = String(index);
|
||||
const img = document.createElement('img');
|
||||
img.src = url;
|
||||
img.alt = '预览';
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'preview-remove';
|
||||
btn.textContent = '×';
|
||||
btn.onclick = () => {
|
||||
const idx = Number(item.dataset.index);
|
||||
if (!Number.isNaN(idx)) {
|
||||
selectedFiles.splice(idx, 1);
|
||||
const urls = selectedFiles.map(f => URL.createObjectURL(f));
|
||||
setPreviewList(urls);
|
||||
updateFileHint();
|
||||
setTimeout(() => urls.forEach(u => URL.revokeObjectURL(u)), 0);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
item.appendChild(img);
|
||||
item.appendChild(btn);
|
||||
previewList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function updateFileHint() {
|
||||
const count = selectedFiles.length;
|
||||
fileHint.textContent = count ? `已选择 ${count} 张` : '未选择文件';
|
||||
}
|
||||
|
||||
function addFiles(files) {
|
||||
const incoming = Array.from(files || []).filter(f => f && f.type.startsWith('image/'));
|
||||
const existingKeys = new Set(selectedFiles.map(f => `${f.name}|${f.size}|${f.lastModified}`));
|
||||
incoming.forEach(f => {
|
||||
const key = `${f.name}|${f.size}|${f.lastModified}`;
|
||||
if (!existingKeys.has(key)) {
|
||||
existingKeys.add(key);
|
||||
selectedFiles.push(f);
|
||||
}
|
||||
});
|
||||
const urls = selectedFiles.map(f => URL.createObjectURL(f));
|
||||
setPreviewList(urls);
|
||||
updateFileHint();
|
||||
setTimeout(() => urls.forEach(u => URL.revokeObjectURL(u)), 0);
|
||||
}
|
||||
|
||||
fileInput.addEventListener('change', function(e) {
|
||||
addFiles(e.target.files || []);
|
||||
fileInput.value = '';
|
||||
});
|
||||
|
||||
function createRow(k = '', v = '') {
|
||||
@@ -329,10 +378,10 @@ uploadForm.addEventListener('submit', async (e) => {
|
||||
confirmMsg.textContent = '';
|
||||
confirmBtn.disabled = true;
|
||||
resultBox.value = '';
|
||||
currentImageRel = '';
|
||||
currentImageRel = [];
|
||||
|
||||
const file = fileInput.files[0];
|
||||
if (!file) {
|
||||
const files = Array.from(selectedFiles || []).filter(f => f && f.type.startsWith('image/'));
|
||||
if (!files.length) {
|
||||
uploadMsg.textContent = '请选择图片文件';
|
||||
uploadMsg.className = 'status-message error';
|
||||
uploadMsg.style.display = 'block';
|
||||
@@ -341,17 +390,21 @@ uploadForm.addEventListener('submit', async (e) => {
|
||||
|
||||
showProgress();
|
||||
setProgress(5, '转换为JPG');
|
||||
let jpegFile = file;
|
||||
try {
|
||||
jpegFile = await convertToJpeg(file);
|
||||
setProgress(50, '转换为JPG');
|
||||
preview.src = URL.createObjectURL(jpegFile);
|
||||
} catch (_) {
|
||||
jpegFile = file;
|
||||
setProgress(50, '转换为JPG');
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('file', jpegFile);
|
||||
const converted = [];
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
let jpegFile = file;
|
||||
try {
|
||||
jpegFile = await convertToJpeg(file);
|
||||
} catch (_) {
|
||||
jpegFile = file;
|
||||
}
|
||||
converted.push(jpegFile);
|
||||
const pct = 5 + Math.round(((i + 1) / files.length) * 45);
|
||||
setProgress(pct, '转换为JPG');
|
||||
}
|
||||
converted.forEach(f => formData.append('file', f));
|
||||
|
||||
try {
|
||||
let prog = 50;
|
||||
@@ -375,9 +428,10 @@ uploadForm.addEventListener('submit', async (e) => {
|
||||
uploadMsg.textContent = data.message || '识别成功';
|
||||
uploadMsg.className = 'status-message success';
|
||||
uploadMsg.style.display = 'block';
|
||||
preview.src = data.image_url;
|
||||
const urls = data.image_urls || (data.image_url ? [data.image_url] : []);
|
||||
setPreviewList(urls);
|
||||
renderFormFromObject(data.data || {});
|
||||
currentImageRel = data.image;
|
||||
currentImageRel = data.images || (data.image ? [data.image] : []);
|
||||
confirmBtn.disabled = false;
|
||||
setTimeout(hideProgress, 800);
|
||||
} catch (e) {
|
||||
@@ -415,15 +469,20 @@ confirmBtn.addEventListener('click', async () => {
|
||||
|
||||
clearBtn.addEventListener('click', () => {
|
||||
fileInput.value = '';
|
||||
preview.src = '';
|
||||
previewList.innerHTML = '';
|
||||
resultBox.value = '';
|
||||
kvForm.innerHTML = '';
|
||||
kvForm.appendChild(createRow()); // 保留一个空行
|
||||
uploadMsg.textContent = '';
|
||||
confirmMsg.textContent = '';
|
||||
confirmBtn.disabled = true;
|
||||
currentImageRel = [];
|
||||
selectedFiles = [];
|
||||
updateFileHint();
|
||||
});
|
||||
|
||||
updateFileHint();
|
||||
|
||||
// 退出登录处理
|
||||
document.getElementById('logoutBtn').addEventListener('click', async () => {
|
||||
const msg = document.getElementById('logoutMsg');
|
||||
|
||||
Reference in New Issue
Block a user