diff --git a/App.jsx b/App.jsx index 9049fb4..31a0555 100644 --- a/App.jsx +++ b/App.jsx @@ -2087,7 +2087,7 @@ function MainApp({ username, onLogout }) { const [regionFilter, setRegionFilter] = useState('all'); const [attachmentNote, setAttachmentNote] = useState(''); const [attachmentCategory, setAttachmentCategory] = useState(FILE_CATEGORIES[0]); - const [attachmentFile, setAttachmentFile] = useState(null); + const [attachmentFiles, setAttachmentFiles] = useState([]); const [uploading, setUploading] = useState(false); const [fileInputKey, setFileInputKey] = useState(0); const [fileDragActive, setFileDragActive] = useState(false); @@ -2138,7 +2138,7 @@ function MainApp({ username, onLogout }) { const [supplierDraft, setSupplierDraft] = useState(null); const [supplierSearch, setSupplierSearch] = useState(''); const [supplierAttachmentNote, setSupplierAttachmentNote] = useState(''); - const [supplierAttachmentFile, setSupplierAttachmentFile] = useState(null); + const [supplierAttachmentFiles, setSupplierAttachmentFiles] = useState([]); const [supplierUploading, setSupplierUploading] = useState(false); const [supplierFileInputKey, setSupplierFileInputKey] = useState(0); const [supplierFileDragActive, setSupplierFileDragActive] = useState(false); @@ -2471,33 +2471,36 @@ function MainApp({ username, onLogout }) { function handleFileDrop(e) { e.preventDefault(); setFileDragActive(false); - const f = e.dataTransfer.files && e.dataTransfer.files[0]; - if (f) setAttachmentFile(f); + const files = e.dataTransfer.files && Array.from(e.dataTransfer.files); + if (files && files.length) setAttachmentFiles(files); } async function handleUploadAttachment(customerId, folderId) { - if (!attachmentFile) { notify('请先选择文件'); return; } + if (!attachmentFiles.length) { notify('请先选择文件'); return; } setUploading(true); + const failed = []; try { - const res = await uploadFile(customerId, attachmentFile); - setCustomers(prev => prev.map(c => { - if (c.id !== customerId) return c; - const entry = { - fileName: res.fileName, - originalName: res.originalName, - category: attachmentCategory, - note: attachmentNote.trim(), - date: todayStr(), - folderId: folderId || null, - }; - return { ...c, attachments: [entry, ...(c.attachments || [])] }; - })); - setAttachmentFile(null); + for (const file of attachmentFiles) { + try { + const res = await uploadFile(customerId, file); + const entry = { + fileName: res.fileName, + originalName: res.originalName, + category: attachmentCategory, + note: attachmentNote.trim(), + date: todayStr(), + folderId: folderId || null, + }; + setCustomers(prev => prev.map(c => (c.id === customerId ? { ...c, attachments: [entry, ...(c.attachments || [])] } : c))); + } catch (e) { + failed.push(`${file.name}:${e.message}`); + } + } + setAttachmentFiles([]); setAttachmentNote(''); setAttachmentCategory(FILE_CATEGORIES[0]); setFileInputKey(k => k + 1); - notify('已上传'); - } catch (e) { - notify('上传失败,请重试'); + if (failed.length === 0) notify('已上传'); + else notify(`${attachmentFiles.length - failed.length}个成功,失败:${failed.join(';')}`); } finally { setUploading(false); } @@ -2617,25 +2620,28 @@ function MainApp({ username, onLogout }) { function handleSupplierFileDrop(e) { e.preventDefault(); setSupplierFileDragActive(false); - const f = e.dataTransfer.files && e.dataTransfer.files[0]; - if (f) setSupplierAttachmentFile(f); + const files = e.dataTransfer.files && Array.from(e.dataTransfer.files); + if (files && files.length) setSupplierAttachmentFiles(files); } async function handleUploadSupplierAttachment(supplierId) { - if (!supplierAttachmentFile) { notify('请先选择文件'); return; } + if (!supplierAttachmentFiles.length) { notify('请先选择文件'); return; } setSupplierUploading(true); + const failed = []; try { - const res = await uploadFile(supplierId, supplierAttachmentFile); - setSuppliers(prev => prev.map(s => { - if (s.id !== supplierId) return s; - const entry = { fileName: res.fileName, originalName: res.originalName, note: supplierAttachmentNote.trim(), date: todayStr() }; - return { ...s, attachments: [entry, ...(s.attachments || [])] }; - })); - setSupplierAttachmentFile(null); + for (const file of supplierAttachmentFiles) { + try { + const res = await uploadFile(supplierId, file); + const entry = { fileName: res.fileName, originalName: res.originalName, note: supplierAttachmentNote.trim(), date: todayStr() }; + setSuppliers(prev => prev.map(s => (s.id === supplierId ? { ...s, attachments: [entry, ...(s.attachments || [])] } : s))); + } catch (e) { + failed.push(`${file.name}:${e.message}`); + } + } + setSupplierAttachmentFiles([]); setSupplierAttachmentNote(''); setSupplierFileInputKey(k => k + 1); - notify('已上传'); - } catch (e) { - notify('上传失败,请重试'); + if (failed.length === 0) notify('已上传'); + else notify(`${supplierAttachmentFiles.length - failed.length}个成功,失败:${failed.join(';')}`); } finally { setSupplierUploading(false); } @@ -3344,8 +3350,8 @@ function MainApp({ username, onLogout }) { setAttachmentNote={setAttachmentNote} attachmentCategory={attachmentCategory} setAttachmentCategory={setAttachmentCategory} - attachmentFileName={attachmentFile ? attachmentFile.name : ''} - onPickAttachment={(f) => setAttachmentFile(f)} + attachmentFileNames={attachmentFiles.map(f => f.name)} + onPickAttachment={(files) => setAttachmentFiles(files)} uploading={uploading} fileInputKey={fileInputKey} onUploadAttachment={(folderId) => handleUploadAttachment(selected.id, folderId)} @@ -3707,8 +3713,8 @@ function MainApp({ username, onLogout }) { onDelete={() => deleteSupplier(selectedSupplier.id)} attachmentNote={supplierAttachmentNote} setAttachmentNote={setSupplierAttachmentNote} - attachmentFileName={supplierAttachmentFile ? supplierAttachmentFile.name : ''} - onPickAttachment={(f) => setSupplierAttachmentFile(f)} + attachmentFileNames={supplierAttachmentFiles.map(f => f.name)} + onPickAttachment={(files) => setSupplierAttachmentFiles(files)} uploading={supplierUploading} fileInputKey={supplierFileInputKey} onUploadAttachment={() => handleUploadSupplierAttachment(selectedSupplier.id)} @@ -4171,10 +4177,11 @@ function SupplierForm({ draft, setDraft, onSave, onCancel }) { function SupplierDetail({ supplier: s, onEdit, onDelete, - attachmentNote, setAttachmentNote, attachmentFileName, onPickAttachment, uploading, fileInputKey, + attachmentNote, setAttachmentNote, attachmentFileNames, onPickAttachment, uploading, fileInputKey, onUploadAttachment, onDeleteAttachment, fileDragActive, onFileDragOver, onFileDragLeave, onFileDrop, }) { const attachments = s.attachments || []; + const [previewFile, setPreviewFile] = useState(null); // { url, name } return (
@@ -4197,19 +4204,19 @@ function SupplierDetail({ {s.notes &&
备注
{s.notes}
}
- 厂家资料(PDF / 图片等) + 厂家资料(PDF / 图片等,支持多选)
{a.date}
- {a.originalName} + setPreviewFile({ url: `/uploads/${s.id}/${a.fileName}`, name: a.originalName })} + style={{ color: 'var(--blue)', cursor: 'pointer' }} + >{a.originalName} {a.note && — {a.note}}
+
))}
+ {previewFile && setPreviewFile(null)} />}
); } @@ -4350,6 +4362,40 @@ function ContactInfo({ icon, label, value, active, onToggle }) { ); } +function isPreviewableImage(name) { return /\.(jpe?g|png|gif|webp|svg|bmp)$/i.test(name || ''); } +function isPreviewablePdf(name) { return /\.pdf$/i.test(name || ''); } + +function FilePreviewModal({ url, name, onClose }) { + const isImage = isPreviewableImage(name); + const isPdf = isPreviewablePdf(name); + const wide = isImage || isPdf; + return ( +
+
e.stopPropagation()}> +
+
{name}
+
+ + +
+
+
+ {isImage ? ( + {name} + ) : isPdf ? ( +