增加手机上传图片的功能
This commit is contained in:
@@ -37,7 +37,7 @@ body { background: #f5f7fa; }
|
||||
|
||||
<!-- 上传区域 -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<div class="section-title">📄 发票 PDF <span class="text-muted fw-normal" style="font-size:12px">(多选)</span></div>
|
||||
<div class="upload-zone" id="pdf-zone" onclick="document.getElementById('pdf-input').click()">
|
||||
<div class="icon">📁</div>
|
||||
@@ -46,7 +46,7 @@ body { background: #f5f7fa; }
|
||||
</div>
|
||||
<input type="file" id="pdf-input" accept=".pdf" multiple hidden onchange="handleFiles(this, 'pdf')">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-4">
|
||||
<div class="section-title">🖼️ 支付截图 <span class="text-muted fw-normal" style="font-size:12px">(多选)</span></div>
|
||||
<div class="upload-zone" id="img-zone" onclick="document.getElementById('img-input').click()">
|
||||
<div class="icon">🖼️</div>
|
||||
@@ -55,6 +55,11 @@ body { background: #f5f7fa; }
|
||||
</div>
|
||||
<input type="file" id="img-input" accept="image/*" multiple hidden onchange="handleFiles(this, 'img')">
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<div class="section-title">📱 手机扫码上传</div>
|
||||
<div id="qrcode" style="display:inline-block"></div>
|
||||
<p class="text-muted mt-2 mb-0" style="font-size:12px">扫码即可拍照上传</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CSV 快捷上传 -->
|
||||
@@ -126,6 +131,7 @@ body { background: #f5f7fa; }
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
|
||||
<script>
|
||||
let sessionId = null;
|
||||
const pdfFiles = [], imgFiles = [];
|
||||
@@ -357,6 +363,76 @@ function showResult(r) {
|
||||
${mdLink}
|
||||
`;
|
||||
}
|
||||
|
||||
// ---- 二维码 / 手机扫码上传 ----
|
||||
let qrGenerated = false;
|
||||
let syncTimer = null;
|
||||
|
||||
async function generateQr() {
|
||||
await ensureSession();
|
||||
const mobileUrl = window.location.origin + '/mobile/' + sessionId;
|
||||
const box = document.getElementById('qrcode');
|
||||
box.innerHTML = '';
|
||||
new QRCode(box, {
|
||||
text: mobileUrl,
|
||||
width: 120,
|
||||
height: 120,
|
||||
colorDark: '#333',
|
||||
colorLight: '#fff',
|
||||
});
|
||||
qrGenerated = true;
|
||||
}
|
||||
|
||||
// ---- 实时同步:轮询服务器文件列表,检测手机端上传的新图片 ----
|
||||
async function startSync() {
|
||||
if (syncTimer) return;
|
||||
await syncFiles(); // 立即执行一次
|
||||
syncTimer = setInterval(syncFiles, 3000);
|
||||
}
|
||||
|
||||
function stopSync() {
|
||||
if (syncTimer) { clearInterval(syncTimer); syncTimer = null; }
|
||||
}
|
||||
|
||||
async function syncFiles() {
|
||||
if (!sessionId) return;
|
||||
try {
|
||||
const r = await fetch(`/api/files/${sessionId}`);
|
||||
const d = await r.json();
|
||||
const serverNames = new Set(d.images || []);
|
||||
const localNames = new Set(imgFiles.map(f => f.name));
|
||||
|
||||
// 检测服务器上有但本地没有的图片(手机端上传的)
|
||||
for (const name of serverNames) {
|
||||
if (!localNames.has(name)) {
|
||||
// 从服务器下载图片并加入本地列表
|
||||
const resp = await fetch(`/api/download/${sessionId}/${encodeURIComponent(name)}`);
|
||||
const blob = await resp.blob();
|
||||
const file = new File([blob], name, { type: blob.type });
|
||||
imgFiles.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
// 检测本地有但服务器没有的图片(被手机端删除了)
|
||||
for (const f of [...imgFiles]) {
|
||||
if (!serverNames.has(f.name)) {
|
||||
const idx = imgFiles.indexOf(f);
|
||||
if (idx > -1) imgFiles.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
renderFileList('img');
|
||||
if (imgFiles.length) {
|
||||
document.getElementById('img-zone').classList.add('active');
|
||||
} else {
|
||||
document.getElementById('img-zone').classList.remove('active');
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
// ---- 页面加载时自动生成二维码并启动同步 ----
|
||||
generateQr();
|
||||
startSync();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
124
web/templates/mobile_upload.html
Normal file
124
web/templates/mobile_upload.html
Normal file
@@ -0,0 +1,124 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>支付截图上传</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
body { background: #f5f7fa; padding-bottom: 40px; }
|
||||
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff; padding: 20px 16px; text-align: center; }
|
||||
.upload-btn { width: 100%; padding: 18px; font-size: 16px; border-radius: 12px; margin-top: 16px; }
|
||||
.img-preview { width: 100px; height: 100px; object-fit: cover; border-radius: 8px; margin: 4px; }
|
||||
.file-card { background: #fff; border-radius: 8px; padding: 8px; margin-bottom: 8px; display: flex; align-items: center; gap: 10px; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
|
||||
.file-card img { width: 60px; height: 60px; object-fit: cover; border-radius: 6px; }
|
||||
.file-card .info { flex: 1; min-width: 0; }
|
||||
.file-card .name { font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.file-card .remove { color: #c00; cursor: pointer; padding: 4px 8px; }
|
||||
.status-msg { text-align: center; padding: 12px; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% if error %}
|
||||
<div class="header"><h5>错误</h5><p>{{ error }}</p></div>
|
||||
{% else %}
|
||||
|
||||
<div class="header">
|
||||
<h5>📸 支付截图上传</h5>
|
||||
<p class="mb-0 opacity-75" style="font-size:13px">拍照或从相册选择图片</p>
|
||||
</div>
|
||||
|
||||
<div class="container" style="max-width:480px">
|
||||
|
||||
<!-- 上传按钮 -->
|
||||
<input type="file" id="file-input" accept="image/*" multiple hidden onchange="handleSelect(this)">
|
||||
|
||||
<button class="btn btn-primary upload-btn" onclick="document.getElementById('file-input').click()">
|
||||
📷 选择图片 / 拍照
|
||||
</button>
|
||||
|
||||
<!-- 上传进度 -->
|
||||
<div id="upload-status" class="status-msg mt-3"></div>
|
||||
|
||||
<!-- 已上传图片列表 -->
|
||||
<h6 class="mt-3 mb-2">已上传 ({{ server_count | default(0) }})</h6>
|
||||
<div id="file-list"></div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
const sessionId = "{{ session_id }}";
|
||||
|
||||
// ---- 加载已有文件 ----
|
||||
async function loadFiles() {
|
||||
try {
|
||||
const r = await fetch(`/api/files/${sessionId}`);
|
||||
const d = await r.json();
|
||||
renderList(d.images || []);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
function renderList(images) {
|
||||
const box = document.getElementById('file-list');
|
||||
if (!images.length) {
|
||||
box.innerHTML = '<p class="text-muted text-center" style="font-size:13px">暂无图片</p>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = images.map(name => `
|
||||
<div class="file-card">
|
||||
<img src="/api/download/${sessionId}/${encodeURIComponent(name)}#t=image/png" alt="">
|
||||
<div class="info"><div class="name">${name}</div></div>
|
||||
<span class="remove" onclick="removeFile('${name}')">×</span>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// ---- 上传 ----
|
||||
async function handleSelect(input) {
|
||||
const files = Array.from(input.files);
|
||||
if (!files.length) return;
|
||||
|
||||
const status = document.getElementById('upload-status');
|
||||
let done = 0;
|
||||
status.innerHTML = `<div class="text-primary">上传中... 0/${files.length}</div>`;
|
||||
|
||||
for (const f of files) {
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append('file', f);
|
||||
await fetch(`/api/upload/${sessionId}`, { method: 'POST', body: fd });
|
||||
done++;
|
||||
status.innerHTML = `<div class="text-primary">上传中... ${done}/${files.length}</div>`;
|
||||
} catch (e) {
|
||||
status.innerHTML = `<div class="text-danger">${f.name} 上传失败</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (done === files.length) {
|
||||
status.innerHTML = `<div class="text-success">✅ ${done} 张图片上传成功</div>`;
|
||||
} else {
|
||||
status.innerHTML = `<div class="text-warning">${done}/${files.length} 张上传成功</div>`;
|
||||
}
|
||||
|
||||
input.value = '';
|
||||
loadFiles();
|
||||
}
|
||||
|
||||
// ---- 删除(前端视觉移除,实际保留在服务器)----
|
||||
function removeFile(name) {
|
||||
// 移动端暂不支持删除,提示用户回到 PC 端管理
|
||||
if (confirm('文件已上传到服务器。如需删除请回到电脑端操作。')) {
|
||||
loadFiles();
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 定时刷新列表(同步 PC 端新增的文件)----
|
||||
setInterval(loadFiles, 5000);
|
||||
loadFiles();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user