Files
label-designer/src/components/MobileExportView.tsx
2026-06-12 22:24:25 +08:00

217 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React from 'react';
import {
ChevronLeft,
Printer,
Loader2,
Database,
Link2,
Download,
} from 'lucide-react';
import {
ImportData,
LabelTemplate,
RecordRow,
ExportRangeConfig,
PaperConfig,
CutLineConfig,
} from '../types';
import { isVariableMappingConfigured } from '../utils';
import { DataImporter, downloadDataTemplate } from './DataImporter';
import { VariableMappingPanel } from './VariableMappingPanel';
import { MobileExportPropsPanel } from './MobileExportPropsPanel';
import { DataRangeFields } from './DataRangeFields';
interface MobileExportViewProps {
template: LabelTemplate;
templateName: string;
templateVariables: string[];
importData: ImportData | null;
onDataLoaded: (data: ImportData) => void;
onClear: () => void;
dataColumns: string[];
variableColumnMapping: Record<string, string>;
onChangeMapping: (mapping: Record<string, string>) => void;
paper: PaperConfig;
onChangePaper: (paper: PaperConfig) => void;
rows: RecordRow[];
draftExportRange: ExportRangeConfig;
onChangeDraftExportRange: (range: ExportRangeConfig) => void;
cutLine: CutLineConfig;
onChangeCutLine: (cutLine: CutLineConfig) => void;
selectedCount: number;
totalPages: number;
pdfGenerating: boolean;
onBack: () => void;
onExportPdf: () => void;
}
export const MobileExportView: React.FC<MobileExportViewProps> = ({
template,
templateName,
templateVariables,
importData,
onDataLoaded,
onClear,
dataColumns,
variableColumnMapping,
onChangeMapping,
paper,
onChangePaper,
rows,
draftExportRange,
onChangeDraftExportRange,
cutLine,
onChangeCutLine,
selectedCount,
totalPages,
pdfGenerating,
onBack,
onExportPdf,
}) => {
const hasData = rows.length > 0;
const mappedCount = templateVariables.filter((v) =>
isVariableMappingConfigured(variableColumnMapping, v, dataColumns)
).length;
const needsMapping = templateVariables.length > 0;
const mappingComplete = !needsMapping || (hasData && mappedCount === templateVariables.length);
const exportBlockedReason = !hasData
? '请先上传 Excel 数据'
: !mappingComplete
? `还有 ${templateVariables.length - mappedCount} 个变量未关联`
: selectedCount === 0
? '当前数据范围内无标签'
: null;
return (
<div className="mobile-export-view">
<header className="no-print bg-indigo-700 border-b border-indigo-900 shrink-0 shadow mobile-export-header px-4">
<div className="flex items-center w-full min-h-[44px]">
<button
type="button"
onClick={onBack}
className="mobile-export-back shrink-0"
aria-label="返回"
>
<ChevronLeft className="w-5 h-5" />
</button>
<p
className="flex-1 min-w-0 px-2 text-center text-sm font-bold tracking-wide text-slate-100 truncate"
title={templateName}
>
{templateName}
</p>
<div className="w-10 shrink-0" aria-hidden />
</div>
</header>
<main className="mobile-export-content">
<section className="mobile-export-card">
<div className="mobile-export-card-head">
<Database className="w-4 h-4 text-indigo-500 shrink-0" />
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<h2></h2>
<p>{hasData ? '已上传,可设置数据范围' : '上传 Excel 或 CSV 文件'}</p>
</div>
<button
type="button"
onClick={() => downloadDataTemplate(templateName, templateVariables)}
className="mobile-export-icon-btn"
aria-label="下载数据模板"
>
<Download className="w-5 h-5" />
</button>
</div>
</div>
</div>
<div className="mobile-export-card-body space-y-5">
<DataImporter
variant="mobile"
importData={importData}
onDataLoaded={onDataLoaded}
onClear={onClear}
templateName={templateName}
templateVariables={templateVariables}
/>
{hasData && (
<DataRangeFields
variant="mobile"
exportRange={draftExportRange}
onChangeExportRange={onChangeDraftExportRange}
rows={rows}
paper={paper}
template={template}
/>
)}
</div>
</section>
{hasData && (
<section className="mobile-export-card">
<div className="mobile-export-card-head">
<Link2 className="w-4 h-4 text-indigo-500 shrink-0" />
<div className="flex-1 min-w-0">
<h2></h2>
<p>
{needsMapping
? `已关联 ${mappedCount}/${templateVariables.length}`
: '此模板无需变量映射'}
</p>
</div>
</div>
<div className="mobile-export-card-body">
<VariableMappingPanel
variant="mobile"
variables={templateVariables}
columns={dataColumns}
mapping={variableColumnMapping}
onChangeMapping={onChangeMapping}
variableDefaults={template.variableDefaults}
/>
</div>
</section>
)}
<MobileExportPropsPanel
template={template}
paper={paper}
onChangePaper={onChangePaper}
rows={rows}
exportRange={draftExportRange}
cutLine={cutLine}
onChangeCutLine={onChangeCutLine}
locked={!hasData}
/>
</main>
<footer className="mobile-export-footer">
{exportBlockedReason && !pdfGenerating && (
<p className="mobile-export-footer-hint">{exportBlockedReason}</p>
)}
<button
type="button"
onClick={onExportPdf}
disabled={!!exportBlockedReason || pdfGenerating}
className="mobile-export-submit"
>
{pdfGenerating ? (
<>
<Loader2 className="w-5 h-5 animate-spin shrink-0" />
PDF
</>
) : (
<>
<Printer className="w-5 h-5 shrink-0" />
{exportBlockedReason
? '生成标签PDF文件'
: `生成标签PDF文件${selectedCount} 枚 · ${totalPages} 页)`}
</>
)}
</button>
</footer>
</div>
);
};