Update App.jsx via upload script - 2026-07-22 16:46:58
This commit is contained in:
parent
6af8cbb529
commit
8506e50107
124
App.jsx
124
App.jsx
|
|
@ -5,7 +5,7 @@ import {
|
||||||
Building2, Globe, Phone, Save, RefreshCw, LayoutDashboard, Users,
|
Building2, Globe, Phone, Save, RefreshCw, LayoutDashboard, Users,
|
||||||
Package, Layers, Zap, Tag, Inbox, Paperclip,
|
Package, Layers, Zap, Tag, Inbox, Paperclip,
|
||||||
Menu, LogOut, GripVertical, BookOpen, FileText, FolderTree, Lock, Crown, Sparkles, Printer, Image as ImageIcon,
|
Menu, LogOut, GripVertical, BookOpen, FileText, FolderTree, Lock, Crown, Sparkles, Printer, Image as ImageIcon,
|
||||||
ShieldCheck, Settings2, Boxes
|
ShieldCheck, Settings2, Boxes, Download
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
storageGet, storageSet, uploadFile, deleteFile,
|
storageGet, storageSet, uploadFile, deleteFile,
|
||||||
|
|
@ -835,6 +835,30 @@ const Styles = () => (
|
||||||
.cert-card-brand-text { font-size: 13px; font-weight: 700; cursor: pointer; word-break: break-word; }
|
.cert-card-brand-text { font-size: 13px; font-weight: 700; cursor: pointer; word-break: break-word; }
|
||||||
.cert-card-brand-text.empty { font-weight: 400; color: var(--ink-soft); }
|
.cert-card-brand-text.empty { font-weight: 400; color: var(--ink-soft); }
|
||||||
.cert-card-brand input { text-align: center; font-weight: 700; }
|
.cert-card-brand input { text-align: center; font-weight: 700; }
|
||||||
|
|
||||||
|
/* ---------- catalog docs (PDF gallery: upload progress, actions, dnd reorder) ---------- */
|
||||||
|
.catalog-doc-actions { position: absolute; top: 6px; right: 6px; display: flex; gap: 4px; }
|
||||||
|
.catalog-doc-actions .btn { padding: 5px 6px; background: rgba(255,255,255,0.92); border-color: var(--line); }
|
||||||
|
.catalog-doc-link {
|
||||||
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
|
||||||
|
width: 100%; height: 100%; padding: 18px; text-align: center; color: inherit; text-decoration: none;
|
||||||
|
}
|
||||||
|
.catalog-doc-filename { font-size: 11px; color: var(--ink-soft); word-break: break-word; }
|
||||||
|
.catalog-doc-uploading {
|
||||||
|
display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
|
||||||
|
width: 100%; height: 100%; color: var(--ink-soft); font-size: 12px; padding: 18px; text-align: center;
|
||||||
|
}
|
||||||
|
.catalog-doc-spinner {
|
||||||
|
width: 26px; height: 26px; border-radius: 50%; border: 3px solid var(--line); border-top-color: var(--copper);
|
||||||
|
animation: catalog-doc-spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes catalog-doc-spin { to { transform: rotate(360deg); } }
|
||||||
|
.catalog-dropzone {
|
||||||
|
border-radius: 12px; transition: background 0.15s, outline 0.15s; outline: 2px dashed transparent; outline-offset: 6px;
|
||||||
|
}
|
||||||
|
.catalog-dropzone.drag-active { outline-color: var(--blue); background: #F0F6FA; }
|
||||||
|
.catalog-doc-card { cursor: grab; }
|
||||||
|
.catalog-doc-card.dragging { opacity: 0.5; }
|
||||||
.field-label-row { display: flex; align-items: center; justify-content: space-between; }
|
.field-label-row { display: flex; align-items: center; justify-content: space-between; }
|
||||||
.field-manage-link {
|
.field-manage-link {
|
||||||
font-size: 11.5px; font-weight: 600; color: var(--blue); cursor: pointer;
|
font-size: 11.5px; font-weight: 600; color: var(--blue); cursor: pointer;
|
||||||
|
|
@ -1161,7 +1185,8 @@ function MainApp({ username, onLogout }) {
|
||||||
const [packingDraft, setPackingDraft] = useState(null);
|
const [packingDraft, setPackingDraft] = useState(null);
|
||||||
const [packingPdfExporting, setPackingPdfExporting] = useState(false);
|
const [packingPdfExporting, setPackingPdfExporting] = useState(false);
|
||||||
const [catalogDocs, setCatalogDocs] = useState([]);
|
const [catalogDocs, setCatalogDocs] = useState([]);
|
||||||
const [catalogUploading, setCatalogUploading] = useState(false);
|
const [catalogDragActive, setCatalogDragActive] = useState(false);
|
||||||
|
const [dragCatalogDocIdx, setDragCatalogDocIdx] = useState(null);
|
||||||
const [editingCatalogId, setEditingCatalogId] = useState(null);
|
const [editingCatalogId, setEditingCatalogId] = useState(null);
|
||||||
const [catalogTitleDraft, setCatalogTitleDraft] = useState('');
|
const [catalogTitleDraft, setCatalogTitleDraft] = useState('');
|
||||||
|
|
||||||
|
|
@ -1186,7 +1211,7 @@ function MainApp({ username, onLogout }) {
|
||||||
setSellerProfiles(data.sellerProfiles);
|
setSellerProfiles(data.sellerProfiles);
|
||||||
setCertifications(data.certifications);
|
setCertifications(data.certifications);
|
||||||
setPackingLists(data.packingLists);
|
setPackingLists(data.packingLists);
|
||||||
setCatalogDocs(data.catalogDocs);
|
setCatalogDocs((data.catalogDocs || []).filter(d => d.fileName));
|
||||||
setLoaded(true);
|
setLoaded(true);
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -1258,23 +1283,34 @@ function MainApp({ username, onLogout }) {
|
||||||
}
|
}
|
||||||
async function handleAddCatalogFile(file) {
|
async function handleAddCatalogFile(file) {
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
setCatalogUploading(true);
|
const tempId = 'catdoc_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
||||||
|
// 先插入一张"上传中"的占位卡片,让用户能实时看到这个文件正在传,传完再替换成真实数据
|
||||||
|
setCatalogDocs(prev => [{ id: tempId, title: '', fileName: '', originalName: file.name, uploading: true }, ...prev]);
|
||||||
try {
|
try {
|
||||||
const id = 'catdoc_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
|
const res = await uploadFile(tempId, file);
|
||||||
const res = await uploadFile(id, file);
|
setCatalogDocs(prev => prev.map(d => (d.id === tempId ? { ...d, fileName: res.fileName, originalName: res.originalName, uploading: false } : d)));
|
||||||
const entry = { id, title: '', fileName: res.fileName, originalName: res.originalName };
|
setEditingCatalogId(tempId);
|
||||||
setCatalogDocs(prev => [entry, ...prev]);
|
|
||||||
setEditingCatalogId(id);
|
|
||||||
setCatalogTitleDraft('');
|
setCatalogTitleDraft('');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notify('上传失败,请重试');
|
notify(`《${file.name}》上传失败,请重试`);
|
||||||
} finally {
|
setCatalogDocs(prev => prev.filter(d => d.id !== tempId));
|
||||||
setCatalogUploading(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleCatalogFilesSelected(files) {
|
function handleCatalogFilesSelected(files) {
|
||||||
Array.from(files || []).forEach(f => handleAddCatalogFile(f));
|
Array.from(files || []).forEach(f => handleAddCatalogFile(f));
|
||||||
}
|
}
|
||||||
|
function handleCatalogDragOver(e) { e.preventDefault(); setCatalogDragActive(true); }
|
||||||
|
function handleCatalogDragLeave(e) {
|
||||||
|
if (e.currentTarget.contains(e.relatedTarget)) return;
|
||||||
|
setCatalogDragActive(false);
|
||||||
|
}
|
||||||
|
function handleCatalogDrop(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
setCatalogDragActive(false);
|
||||||
|
const files = Array.from((e.dataTransfer && e.dataTransfer.files) || []).filter(f => f.type === 'application/pdf' || /\.pdf$/i.test(f.name));
|
||||||
|
if (files.length === 0) { notify('只能上传PDF文件'); return; }
|
||||||
|
handleCatalogFilesSelected(files);
|
||||||
|
}
|
||||||
function startEditCatalogTitle(doc) {
|
function startEditCatalogTitle(doc) {
|
||||||
setEditingCatalogId(doc.id);
|
setEditingCatalogId(doc.id);
|
||||||
setCatalogTitleDraft(doc.title || '');
|
setCatalogTitleDraft(doc.title || '');
|
||||||
|
|
@ -1290,6 +1326,11 @@ function MainApp({ username, onLogout }) {
|
||||||
setCatalogDocs(prev => prev.filter(d => d.id !== doc.id));
|
setCatalogDocs(prev => prev.filter(d => d.id !== doc.id));
|
||||||
if (editingCatalogId === doc.id) setEditingCatalogId(null);
|
if (editingCatalogId === doc.id) setEditingCatalogId(null);
|
||||||
}
|
}
|
||||||
|
function handleCatalogDocDrop(dropIdx) {
|
||||||
|
if (dragCatalogDocIdx === null || dragCatalogDocIdx === dropIdx) { setDragCatalogDocIdx(null); return; }
|
||||||
|
setCatalogDocs(prev => reorderArray(prev, dragCatalogDocIdx, dropIdx));
|
||||||
|
setDragCatalogDocIdx(null);
|
||||||
|
}
|
||||||
function handleGroupDrop(category, dropIdx) {
|
function handleGroupDrop(category, dropIdx) {
|
||||||
if (!dragGroupState || dragGroupState.category !== category || dragGroupState.index === dropIdx) { setDragGroupState(null); return; }
|
if (!dragGroupState || dragGroupState.category !== category || dragGroupState.index === dropIdx) { setDragGroupState(null); return; }
|
||||||
setProducts(prev => reorderWithinCategory(prev, category, dragGroupState.index, dropIdx));
|
setProducts(prev => reorderWithinCategory(prev, category, dragGroupState.index, dropIdx));
|
||||||
|
|
@ -2128,9 +2169,12 @@ function MainApp({ username, onLogout }) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{view === 'catalog' && (
|
{view === 'catalog' && (
|
||||||
<>
|
<div
|
||||||
|
className={`catalog-dropzone ${catalogDragActive ? 'drag-active' : ''}`}
|
||||||
|
onDragOver={handleCatalogDragOver} onDragLeave={handleCatalogDragLeave} onDrop={handleCatalogDrop}
|
||||||
|
>
|
||||||
<div className="cert-view-head">
|
<div className="cert-view-head">
|
||||||
<label className="btn btn-primary cert-add-btn" title={catalogUploading ? '上传中…' : '上传产品目录PDF'}>
|
<label className="btn btn-primary cert-add-btn" title="上传产品目录PDF(也可以把PDF直接拖到本页任意位置)">
|
||||||
<input
|
<input
|
||||||
type="file" accept="application/pdf" multiple style={{ display: 'none' }}
|
type="file" accept="application/pdf" multiple style={{ display: 'none' }}
|
||||||
onChange={e => { handleCatalogFilesSelected(e.target.files); e.target.value = ''; }}
|
onChange={e => { handleCatalogFilesSelected(e.target.files); e.target.value = ''; }}
|
||||||
|
|
@ -2139,22 +2183,42 @@ function MainApp({ username, onLogout }) {
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{catalogDocs.length === 0 ? (
|
{catalogDocs.length === 0 ? (
|
||||||
<div className="panel"><div className="empty-state"><BookOpen size={28} />暂无产品目录,点击右上角"+"上传第一份PDF样本</div></div>
|
<div className="panel"><div className="empty-state"><BookOpen size={28} />暂无产品目录,点击右上角"+"或把PDF拖到这里上传第一份样本</div></div>
|
||||||
) : (
|
) : (
|
||||||
<div className="card-grid">
|
<div className="card-grid">
|
||||||
{catalogDocs.map(doc => (
|
{catalogDocs.map((doc, idx) => (
|
||||||
<div key={doc.id} className="cert-card">
|
<div
|
||||||
|
key={doc.id}
|
||||||
|
className={`cert-card catalog-doc-card ${dragCatalogDocIdx === idx ? 'dragging' : ''}`}
|
||||||
|
draggable={!doc.uploading}
|
||||||
|
onDragStart={(e) => { e.stopPropagation(); setDragCatalogDocIdx(idx); }}
|
||||||
|
onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); }}
|
||||||
|
onDrop={(e) => { e.stopPropagation(); handleCatalogDocDrop(idx); }}
|
||||||
|
onDragEnd={() => setDragCatalogDocIdx(null)}
|
||||||
|
>
|
||||||
<div className="cert-card-img-wrap">
|
<div className="cert-card-img-wrap">
|
||||||
<a
|
{doc.uploading ? (
|
||||||
href={`/uploads/${doc.id}/${doc.fileName}`} target="_blank" rel="noreferrer"
|
<div className="catalog-doc-uploading">
|
||||||
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 10, padding: 18, textAlign: 'center', color: 'inherit', textDecoration: 'none', width: '100%', height: '100%' }}
|
<div className="catalog-doc-spinner" />
|
||||||
>
|
<div>正在上传…</div>
|
||||||
<FileText size={40} color="var(--copper)" />
|
<div className="catalog-doc-filename">{doc.originalName}</div>
|
||||||
<div style={{ fontSize: 11, color: 'var(--ink-soft)', wordBreak: 'break-word' }}>{doc.originalName}</div>
|
</div>
|
||||||
</a>
|
) : (
|
||||||
<button className="btn btn-sm btn-ghost btn-danger cert-card-delete" onClick={() => deleteCatalogDoc(doc)}>
|
<>
|
||||||
<Trash2 size={12} />
|
<a href={`/uploads/${doc.id}/${doc.fileName}`} target="_blank" rel="noreferrer" className="catalog-doc-link">
|
||||||
</button>
|
<FileText size={40} color="var(--copper)" />
|
||||||
|
<div className="catalog-doc-filename">{doc.originalName}</div>
|
||||||
|
</a>
|
||||||
|
<div className="catalog-doc-actions">
|
||||||
|
<a className="btn btn-sm btn-ghost" title="下载" href={`/uploads/${doc.id}/${doc.fileName}`} download={doc.originalName}>
|
||||||
|
<Download size={12} />
|
||||||
|
</a>
|
||||||
|
<button className="btn btn-sm btn-ghost btn-danger" title="删除" onClick={() => deleteCatalogDoc(doc)}>
|
||||||
|
<Trash2 size={12} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="cert-card-brand">
|
<div className="cert-card-brand">
|
||||||
{editingCatalogId === doc.id ? (
|
{editingCatalogId === doc.id ? (
|
||||||
|
|
@ -2169,9 +2233,9 @@ function MainApp({ username, onLogout }) {
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className={`cert-card-brand-text ${doc.title ? '' : 'empty'}`}
|
className={`cert-card-brand-text ${doc.title ? '' : 'empty'}`}
|
||||||
onClick={() => startEditCatalogTitle(doc)}
|
onClick={() => !doc.uploading && startEditCatalogTitle(doc)}
|
||||||
>
|
>
|
||||||
{doc.title || '点击填写目录名称'}
|
{doc.uploading ? '上传完成后可填写' : (doc.title || '点击填写目录名称')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -2179,7 +2243,7 @@ function MainApp({ username, onLogout }) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{view === 'certs' && (
|
{view === 'certs' && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue