优化
This commit is contained in:
327
src/App.tsx
327
src/App.tsx
@@ -26,7 +26,6 @@ import { useAppDialog } from './components/AppDialog';
|
||||
import {
|
||||
FileDown,
|
||||
Code,
|
||||
Wand2,
|
||||
Loader2,
|
||||
Plus,
|
||||
Download,
|
||||
@@ -34,6 +33,7 @@ import {
|
||||
Trash2,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import logoUrl from './assets/logo.svg';
|
||||
|
||||
const TEMPLATE_PREVIEW_MAX_W = 220;
|
||||
const TEMPLATE_PREVIEW_MAX_H = 132;
|
||||
@@ -72,7 +72,7 @@ export default function App() {
|
||||
return parsed.map((t: LabelTemplate) => normalizeTemplate(t));
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
return [];
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ export default function App() {
|
||||
if (Array.isArray(parsed) && parsed.some((t: LabelTemplate) => t.id === savedId)) {
|
||||
return savedId;
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
return '';
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ export default function App() {
|
||||
try {
|
||||
const stripped = updatedList.map(stripTemplateForStorage);
|
||||
localStorage.setItem('label_templates_list_v2', JSON.stringify(stripped));
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
};
|
||||
|
||||
// Modify active template elements
|
||||
@@ -169,7 +169,7 @@ export default function App() {
|
||||
setSelectedElementIds([]);
|
||||
try {
|
||||
localStorage.setItem('selected_label_template_id_v2', id);
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
|
||||
setActiveRowIndex(0);
|
||||
};
|
||||
@@ -358,7 +358,7 @@ export default function App() {
|
||||
setSelectedTemplateId('');
|
||||
try {
|
||||
localStorage.removeItem('selected_label_template_id_v2');
|
||||
} catch (e) {}
|
||||
} catch (e) { }
|
||||
setWorkflowStep('template-select');
|
||||
}
|
||||
}
|
||||
@@ -604,13 +604,12 @@ export default function App() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`min-h-screen flex flex-col font-sans ${
|
||||
workflowStep === 'template-design' || (workflowStep === 'print-view' && !isMobile)
|
||||
className={`min-h-screen flex flex-col font-sans ${workflowStep === 'template-design' || (workflowStep === 'print-view' && !isMobile)
|
||||
? 'bg-[#323232] text-[#e8e8e8] h-screen overflow-hidden'
|
||||
: workflowStep === 'print-view' && isMobile
|
||||
? 'bg-slate-100 text-slate-800 h-screen overflow-hidden'
|
||||
: 'bg-slate-50 text-slate-800'
|
||||
}`}
|
||||
? 'bg-slate-100 text-slate-800 h-screen overflow-hidden'
|
||||
: 'bg-slate-50 text-slate-800'
|
||||
}`}
|
||||
>
|
||||
{/*
|
||||
=========================================
|
||||
@@ -618,61 +617,56 @@ export default function App() {
|
||||
=========================================
|
||||
*/}
|
||||
{!(isMobile && workflowStep === 'print-view') && (
|
||||
<header
|
||||
className={`no-print bg-slate-950 border-b border-slate-900 shrink-0 shadow flex flex-wrap items-center justify-between gap-4 mobile-template-header ${
|
||||
workflowStep === 'template-design' || workflowStep === 'print-view' ? 'px-4 py-2' : 'px-4 py-3 md:px-6 md:py-4'
|
||||
}`}
|
||||
>
|
||||
{/* Title logo and slogan description */}
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<div className="p-2 md:p-2.5 bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 rounded-xl shadow-inner shrink-0">
|
||||
<Wand2 className="w-5 h-5" />
|
||||
<header
|
||||
className={`no-print bg-slate-950 border-b border-slate-900 shrink-0 shadow flex flex-wrap items-center justify-between gap-4 mobile-template-header ${workflowStep === 'template-design' || workflowStep === 'print-view' ? 'px-4 py-2' : 'px-4 py-3 md:px-6 md:py-4'
|
||||
}`}
|
||||
>
|
||||
{/* Title logo and slogan description */}
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<img src={logoUrl} alt="" className="w-10 h-10" aria-hidden />
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-sm font-bold tracking-wider flex items-center gap-2 text-slate-100 uppercase">
|
||||
<span className="truncate">批量标签排版印刷生成器</span>
|
||||
</h1>
|
||||
{workflowStep !== 'template-design' && workflowStep !== 'print-view' && (
|
||||
<p className="text-[11px] text-slate-400">
|
||||
所见即所得,上传Excel批量排版生成标签
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-sm font-bold tracking-wider flex items-center gap-2 text-slate-100 uppercase">
|
||||
<span className="truncate">{isMobile ? '批量标签排版印刷生成器' : '批量标签排版印刷生成器'}</span>
|
||||
<span className="text-[9.5px] bg-indigo-600 text-white font-extrabold px-1.5 py-0.5 rounded shadow-sm tracking-normal shrink-0">PDF</span>
|
||||
</h1>
|
||||
{workflowStep !== 'template-design' && workflowStep !== 'print-view' && !isMobile && (
|
||||
<p className="text-[11px] text-slate-400">
|
||||
数据所见即所得:可视化添加变量、绑定 Excel 多表、快速二元素对齐排料、高保真物理 A4 无损裁切
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{workflowStep === 'template-select' && hasTemplates && !isMobile && (
|
||||
<div className="flex flex-wrap items-center gap-2 shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddTmplForm(true)}
|
||||
className="inline-flex items-center gap-1.5 px-4 py-2 bg-indigo-600 text-white rounded-xl text-xs font-semibold whitespace-nowrap transition hover:bg-indigo-700 shadow-sm cursor-pointer"
|
||||
>
|
||||
<Plus className="w-4 h-4 shrink-0" />
|
||||
新建模板
|
||||
</button>
|
||||
<label className="inline-flex items-center gap-1.5 px-4 py-2 bg-white/10 hover:bg-white/15 border border-slate-700 text-slate-100 rounded-xl text-xs font-semibold whitespace-nowrap transition shadow-sm cursor-pointer">
|
||||
<Upload className="w-4 h-4 shrink-0" />
|
||||
导入 JSON 模板
|
||||
<input
|
||||
type="file"
|
||||
accept=".json"
|
||||
onChange={handleImportTemplate}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
{workflowStep === 'template-select' && hasTemplates && !isMobile && (
|
||||
<div className="flex flex-wrap items-center gap-2 shrink-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowAddTmplForm(true)}
|
||||
className="inline-flex items-center gap-1.5 px-4 py-2 bg-indigo-600 text-white rounded-xl text-xs font-semibold whitespace-nowrap transition hover:bg-indigo-700 shadow-sm cursor-pointer"
|
||||
>
|
||||
<Plus className="w-4 h-4 shrink-0" />
|
||||
新建模板
|
||||
</button>
|
||||
<label className="inline-flex items-center gap-1.5 px-4 py-2 bg-white/10 hover:bg-white/15 border border-slate-700 text-slate-100 rounded-xl text-xs font-semibold whitespace-nowrap transition shadow-sm cursor-pointer">
|
||||
<Upload className="w-4 h-4 shrink-0" />
|
||||
导入 JSON 模板
|
||||
<input
|
||||
type="file"
|
||||
accept=".json"
|
||||
onChange={handleImportTemplate}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
|
||||
{/* Primary Workspace scroll container */}
|
||||
<main
|
||||
className={`no-print flex-1 flex flex-col w-full ${
|
||||
workflowStep === 'template-design' || workflowStep === 'print-view'
|
||||
className={`no-print flex-1 flex flex-col w-full ${workflowStep === 'template-design' || workflowStep === 'print-view'
|
||||
? 'overflow-hidden min-h-0'
|
||||
: 'overflow-auto p-4 md:p-6 max-w-[1720px] mx-auto gap-6'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
|
||||
{/* ========================================================
|
||||
@@ -691,16 +685,16 @@ export default function App() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center text-center px-6 py-16 md:py-24">
|
||||
<div className="w-16 h-16 rounded-2xl bg-indigo-50 text-indigo-500 flex items-center justify-center mb-5 border border-indigo-100">
|
||||
<Wand2 className="w-8 h-8" />
|
||||
</div>
|
||||
{/* <div className="w-16 h-16 rounded-2xl bg-indigo-50 flex items-center justify-center mb-5 border border-indigo-100">
|
||||
<img src={logoUrl} alt="" className="w-12 h-12" aria-hidden />
|
||||
</div> */}
|
||||
<h2 className="text-lg font-bold text-gray-900">还没有标签模板</h2>
|
||||
<p className="text-sm text-gray-500 mt-2 max-w-md leading-relaxed">
|
||||
{isMobile
|
||||
? '请导入已有模板,或电脑端进行设计。'
|
||||
? '导入已有模板,或在电脑端设计标签模板'
|
||||
: '创建模板开始可视化设计,或导入已有的 JSON 模板文件。'}
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 mt-8">
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 mt-3">
|
||||
{!isMobile && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -726,123 +720,121 @@ export default function App() {
|
||||
)}
|
||||
|
||||
{hasTemplates && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{templates.map((tmpl) => {
|
||||
const isActive = tmpl.id === selectedTemplateId;
|
||||
const { displayWidthPx, displayHeightPx } = fitTemplatePreviewSize(
|
||||
tmpl.width,
|
||||
tmpl.height
|
||||
);
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{templates.map((tmpl) => {
|
||||
const isActive = tmpl.id === selectedTemplateId;
|
||||
const { displayWidthPx, displayHeightPx } = fitTemplatePreviewSize(
|
||||
tmpl.width,
|
||||
tmpl.height
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={tmpl.id}
|
||||
onClick={() => handleSelectTemplate(tmpl.id!)}
|
||||
className={`bg-white border rounded-2xl p-5 shadow-xs transition-all flex flex-col cursor-pointer relative overflow-hidden group select-none ${
|
||||
isActive
|
||||
? 'ring-2 ring-indigo-500 border-indigo-500 bg-indigo-50/5'
|
||||
: 'border-gray-200 hover:shadow-md hover:border-indigo-200'
|
||||
}`}
|
||||
>
|
||||
{isActive && (
|
||||
<div
|
||||
className="absolute top-0 right-0 py-1.5 px-3 text-[10px] font-bold rounded-bl-xl text-white"
|
||||
style={{ backgroundColor: '#6366f1' }}
|
||||
>
|
||||
当前选中
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-1 pr-12 mb-3">
|
||||
<span className="text-[10px] font-mono font-extrabold text-indigo-700 bg-indigo-50 px-2 py-0.5 rounded-lg border border-indigo-100">
|
||||
{tmpl.width} × {tmpl.height} mm
|
||||
</span>
|
||||
<h3 className="text-sm font-bold text-gray-900 mt-1 leading-snug">{tmpl.name}</h3>
|
||||
<p className="text-[10px] text-gray-400">{tmpl.elements.length} 个元素</p>
|
||||
</div>
|
||||
|
||||
{/* Preview */}
|
||||
return (
|
||||
<div
|
||||
className="rounded-xl bg-gray-50 p-3 overflow-hidden flex items-center justify-center"
|
||||
style={{
|
||||
minHeight: `${TEMPLATE_PREVIEW_MAX_H + 24}px`,
|
||||
}}
|
||||
key={tmpl.id}
|
||||
onClick={() => handleSelectTemplate(tmpl.id!)}
|
||||
className={`bg-white border rounded-2xl p-5 shadow-xs transition-all flex flex-col cursor-pointer relative overflow-hidden group select-none ${isActive
|
||||
? 'ring-2 ring-indigo-500 border-indigo-500 bg-indigo-50/5'
|
||||
: 'border-gray-200 hover:shadow-md hover:border-indigo-200'
|
||||
}`}
|
||||
>
|
||||
{isActive && (
|
||||
<div
|
||||
className="absolute top-0 right-0 py-1.5 px-3 text-[10px] font-bold rounded-bl-xl text-white"
|
||||
style={{ backgroundColor: '#6366f1' }}
|
||||
>
|
||||
当前选中
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-1 pr-12 mb-3">
|
||||
<span className="text-[10px] font-mono font-extrabold text-indigo-700 bg-indigo-50 px-2 py-0.5 rounded-lg border border-indigo-100">
|
||||
{tmpl.width} × {tmpl.height} mm
|
||||
</span>
|
||||
<h3 className="text-sm font-bold text-gray-900 mt-1 leading-snug">{tmpl.name}</h3>
|
||||
<p className="text-[10px] text-gray-400">{tmpl.elements.length} 个元素</p>
|
||||
</div>
|
||||
|
||||
{/* Preview */}
|
||||
<div
|
||||
className="bg-white shadow-sm shrink-0 overflow-hidden"
|
||||
className="rounded-xl bg-gray-50 p-3 overflow-hidden flex items-center justify-center"
|
||||
style={{
|
||||
width: `${displayWidthPx}px`,
|
||||
height: `${displayHeightPx}px`,
|
||||
minHeight: `${TEMPLATE_PREVIEW_MAX_H + 24}px`,
|
||||
}}
|
||||
>
|
||||
<CanvasLabelImage
|
||||
widthMm={tmpl.width}
|
||||
heightMm={tmpl.height}
|
||||
elements={tmpl.elements}
|
||||
rowData={null}
|
||||
rowIndex={0}
|
||||
showBorder={true}
|
||||
variableDefaults={tmpl.variableDefaults ?? null}
|
||||
/>
|
||||
<div
|
||||
className="bg-white shadow-sm shrink-0 overflow-hidden"
|
||||
style={{
|
||||
width: `${displayWidthPx}px`,
|
||||
height: `${displayHeightPx}px`,
|
||||
}}
|
||||
>
|
||||
<CanvasLabelImage
|
||||
widthMm={tmpl.width}
|
||||
heightMm={tmpl.height}
|
||||
elements={tmpl.elements}
|
||||
rowData={null}
|
||||
rowIndex={0}
|
||||
showBorder={true}
|
||||
variableDefaults={tmpl.variableDefaults ?? null}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className={`flex flex-wrap items-center gap-2 mt-4 text-xs ${isMobile ? 'mobile-template-actions' : ''}`}>
|
||||
{!isMobile && (
|
||||
{/* Actions */}
|
||||
<div className={`flex flex-wrap items-center gap-2 mt-4 text-xs ${isMobile ? 'mobile-template-actions' : ''}`}>
|
||||
{!isMobile && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSelectTemplate(tmpl.id!);
|
||||
setWorkflowStep('template-design');
|
||||
}}
|
||||
className="shrink-0 whitespace-nowrap inline-flex items-center gap-1 px-3 py-1.5 bg-gray-50 text-indigo-700 border border-gray-100 hover:border-indigo-300 hover:bg-indigo-50 rounded-lg text-[11px] font-bold transition-colors cursor-pointer"
|
||||
>
|
||||
<Code className="w-3.5 h-3.5 shrink-0" />
|
||||
设计
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSelectTemplate(tmpl.id!);
|
||||
setWorkflowStep('template-design');
|
||||
openPrintView(tmpl.id!);
|
||||
}}
|
||||
className="shrink-0 whitespace-nowrap inline-flex items-center gap-1 px-3 py-1.5 bg-gray-50 text-indigo-700 border border-gray-100 hover:border-indigo-300 hover:bg-indigo-50 rounded-lg text-[11px] font-bold transition-colors cursor-pointer"
|
||||
className={`shrink-0 whitespace-nowrap inline-flex items-center gap-1 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-[11px] font-bold transition shadow-xs cursor-pointer ${isMobile ? 'mobile-primary-btn flex-1' : ''
|
||||
}`}
|
||||
>
|
||||
<Code className="w-3.5 h-3.5 shrink-0" />
|
||||
设计
|
||||
<FileDown className="w-3.5 h-3.5 shrink-0" />
|
||||
{isMobile ? '上传数据并导出 PDF' : '排版导出'}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
openPrintView(tmpl.id!);
|
||||
}}
|
||||
className={`shrink-0 whitespace-nowrap inline-flex items-center gap-1 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-[11px] font-bold transition shadow-xs cursor-pointer ${
|
||||
isMobile ? 'mobile-primary-btn flex-1' : ''
|
||||
}`}
|
||||
>
|
||||
<FileDown className="w-3.5 h-3.5 shrink-0" />
|
||||
{isMobile ? '上传数据并导出 PDF' : '排版导出'}
|
||||
</button>
|
||||
{!isMobile && (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleExportTemplate(tmpl);
|
||||
}}
|
||||
className="shrink-0 p-1.5 bg-gray-50 text-gray-500 hover:bg-gray-100 border border-gray-100 hover:border-gray-300 rounded-lg inline-flex items-center justify-center transition cursor-pointer"
|
||||
title="导出 JSON"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteTemplate(tmpl.id!);
|
||||
}}
|
||||
className="shrink-0 p-1.5 bg-gray-50 text-rose-500 hover:text-white hover:bg-rose-600 border border-gray-100 rounded-lg inline-flex items-center justify-center transition cursor-pointer"
|
||||
title="删除模板"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleExportTemplate(tmpl);
|
||||
}}
|
||||
className="shrink-0 p-1.5 bg-gray-50 text-gray-500 hover:bg-gray-100 border border-gray-100 hover:border-gray-300 rounded-lg inline-flex items-center justify-center transition cursor-pointer"
|
||||
title="导出 JSON"
|
||||
>
|
||||
<Download className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteTemplate(tmpl.id!);
|
||||
}}
|
||||
className="shrink-0 p-1.5 bg-gray-50 text-rose-500 hover:text-white hover:bg-rose-600 border border-gray-100 rounded-lg inline-flex items-center justify-center transition cursor-pointer"
|
||||
title="删除模板"
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -917,7 +909,7 @@ export default function App() {
|
||||
selectedElementIds={selectedElementIds}
|
||||
onSelectElements={setSelectedElementIds}
|
||||
activeTab="element"
|
||||
onChangeTab={() => {}}
|
||||
onChangeTab={() => { }}
|
||||
paper={paper}
|
||||
/>
|
||||
</div>
|
||||
@@ -993,11 +985,10 @@ export default function App() {
|
||||
disabled={
|
||||
!appliedExportData || appliedLayoutResult.selectedCount === 0 || pdfGenerating
|
||||
}
|
||||
className={`inline-flex items-center gap-1.5 px-3 py-1 text-[11px] font-semibold transition cursor-pointer ${
|
||||
appliedExportData && appliedLayoutResult.selectedCount > 0 && !pdfGenerating
|
||||
className={`inline-flex items-center gap-1.5 px-3 py-1 text-[11px] font-semibold transition cursor-pointer ${appliedExportData && appliedLayoutResult.selectedCount > 0 && !pdfGenerating
|
||||
? 'bg-[#31a8ff] hover:bg-[#2890d8] text-white'
|
||||
: 'bg-[#444] text-[#666] cursor-not-allowed'
|
||||
}`}
|
||||
}`}
|
||||
>
|
||||
{pdfGenerating ? (
|
||||
<Loader2 className="w-3 h-3 shrink-0 animate-spin" />
|
||||
|
||||
Reference in New Issue
Block a user