移动端适配

This commit is contained in:
iqudoo
2026-06-13 02:23:11 +08:00
parent f2689520bd
commit 6c314f9808
10 changed files with 1058 additions and 533 deletions

View File

@@ -19,6 +19,7 @@ interface LabelDesignerProps {
onChange: (updated: LabelTemplate) => void;
selectedElementIds: string[];
onSelectElements: (ids: string[]) => void;
variant?: 'desktop' | 'mobile';
}
type ActiveTool = 'move' | 'text' | 'barcode' | 'qrcode';
@@ -134,9 +135,11 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
onChange,
selectedElementIds,
onSelectElements,
variant = 'desktop',
}) => {
const selectedElementId = selectedElementIds[0] || null;
const isNarrow = useIsNarrowScreen();
const isMobileLayout = variant === 'mobile';
const isNarrow = useIsNarrowScreen() || isMobileLayout;
const [zoom, setZoom] = useState<number>(MODERATE_ZOOM_MAX);
const [activeTool, setActiveTool] = useState<ActiveTool>('move');
const canvasAreaRef = useRef<HTMLDivElement | null>(null);
@@ -744,7 +747,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
const fitToView = () => {
const area = canvasAreaRef.current;
if (!area) return;
const pad = 80;
const pad = isMobileLayout ? 32 : 80;
const availW = area.clientWidth - pad;
const availH = area.clientHeight - pad;
const docW = mmToPx(template.width, 1);
@@ -753,6 +756,12 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
setZoom(Math.max(0.5, Math.round(fitZoom * 100) / 100));
};
useEffect(() => {
if (!isMobileLayout) return;
const timer = window.setTimeout(() => fitToView(), 0);
return () => window.clearTimeout(timer);
}, [isMobileLayout, template.width, template.height]);
const selectedElemObj = (localElements || template.elements).find(
(el) => el.id === selectedElementId
);
@@ -777,44 +786,85 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
{ id: 'qrcode', icon: <QrCode className="w-[18px] h-[18px]" />, label: '二维码' },
];
return (
<div className="flex flex-1 min-w-0 min-h-0">
<div className="ps-toolbar flex flex-col">
{tools.map((tool) => (
<button
key={tool.id}
className={`ps-tool-btn ${activeTool === tool.id ? 'active' : ''}`}
onClick={() => handleToolClick(tool.id)}
title={`${tool.label}${tool.shortcut ? ` (${tool.shortcut})` : ''}`}
>
{tool.icon}
</button>
))}
const mainToolButtons = (
<>
{tools.map((tool) => (
<button
className="ps-tool-btn"
onClick={handleImageToolClick}
title="图片工具"
key={tool.id}
className={`ps-tool-btn ${activeTool === tool.id ? 'active' : ''} ${isMobileLayout ? 'mobile-design-tool-btn' : ''}`}
onClick={() => handleToolClick(tool.id)}
title={`${tool.label}${tool.shortcut ? ` (${tool.shortcut})` : ''}`}
>
<ImageIcon className="w-[18px] h-[18px]" />
{tool.icon}
</button>
))}
<button
className={`ps-tool-btn ${isMobileLayout ? 'mobile-design-tool-btn' : ''}`}
onClick={handleImageToolClick}
title="图片工具"
>
<ImageIcon className="w-[18px] h-[18px]" />
</button>
<input
ref={imageInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleImageFile}
/>
</>
);
const deleteToolButton =
selectedElementIds.length > 0 ? (
<button
className={`ps-tool-btn hover:!bg-red-900/40 hover:!text-red-400 ${isMobileLayout ? 'mobile-design-tool-btn' : ''}`}
onClick={deleteSelectedElements}
title="删除选中元素"
>
<Trash2 className="w-[18px] h-[18px]" />
</button>
) : null;
const zoomControls = (
<div className="flex items-center gap-1.5 shrink-0">
<div className="ps-zoom-control">
<button className="ps-zoom-btn" onClick={() => setZoom(Math.max(0.5, zoom - 0.25))} title="缩小">
<ZoomOut className="w-3 h-3" />
</button>
<span className="px-1.5 text-[10px] font-mono text-[#ccc] min-w-[42px] text-center select-none">
{Math.round(zoom * 100)}%
</span>
<button className="ps-zoom-btn" onClick={() => setZoom(Math.min(5, zoom + 0.25))} title="放大">
<ZoomIn className="w-3 h-3" />
</button>
<input
ref={imageInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleImageFile}
/>
<div className="flex-1" />
{selectedElementIds.length > 0 && (
<button
className="ps-tool-btn hover:!bg-red-900/40 hover:!text-red-400"
onClick={deleteSelectedElements}
title="删除选中元素 (Del)"
>
<Trash2 className="w-[18px] h-[18px]" />
</button>
)}
</div>
<button
type="button"
onClick={fitToView}
className="text-[10px] text-[#aaa] hover:text-white px-1.5 py-0.5 border border-[#4a4a4a] hover:border-[#666] transition cursor-pointer"
title="适合窗口"
>
</button>
</div>
);
return (
<div
className={
isMobileLayout
? 'mobile-design-canvas-shell flex flex-col flex-1 min-w-0 min-h-0'
: 'flex flex-1 min-w-0 min-h-0'
}
>
{!isMobileLayout && (
<div className="ps-toolbar flex flex-col">
{mainToolButtons}
<div className="flex-1" />
{deleteToolButton}
</div>
)}
<div className="flex flex-col flex-1 min-w-0 min-h-0">
<div
@@ -839,7 +889,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
height: `${mmToPx(template.height, zoom)}px`,
}}
>
<div className="absolute -top-5 left-0 text-[10px] font-mono text-[#aaa]">
{/* <div className="absolute -top-5 left-0 text-[10px] font-mono text-[#aaa]">
{template.width} mm
</div>
<div
@@ -847,7 +897,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}
>
{template.height} mm
</div>
</div> */}
{/* 整页统一渲染:半透明背景按图层层级与下方元素真实合成 */}
<div className="absolute inset-0 overflow-hidden pointer-events-none z-0">
@@ -962,71 +1012,63 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
</div>
</div>
<div className={`ps-status-bar ${isNarrow ? 'ps-status-bar-compact' : ''}`}>
<div className="flex items-center gap-2 min-w-0 truncate">
{isNarrow ? (
<>
{isMobileLayout ? (
<div className="mobile-design-toolstrip">
<div className="mobile-design-toolstrip-tools">
{mainToolButtons}
{deleteToolButton}
</div>
{zoomControls}
</div>
) : (
<div className={`ps-status-bar ${isNarrow ? 'ps-status-bar-compact' : ''}`}>
<div className="flex items-center gap-2 min-w-0 truncate">
{isNarrow ? (
<span className="truncate">
{template.width}×{template.height}
{selectedElemObj ? ` · ${selectedElemObj.name}` : ''}
{selectedElementIds.length > 1 ? ` · ${selectedElementIds.length}` : ''}
</span>
</>
) : (
<>
<span>
: {template.width} × {template.height} mm
) : (
<>
<span>
: {template.width} × {template.height} mm
</span>
{selectedElemObj && (
<span className="text-[#31a8ff]">
| {selectedElemObj.name} X:{selectedElemObj.x} Y:{selectedElemObj.y} W:
{selectedElemObj.width} H:{selectedElemObj.height} mm
</span>
)}
{selectedElementIds.length > 1 && (
<span className="text-emerald-400">| {selectedElementIds.length} </span>
)}
{activeTool === 'move' && (
<span className="text-[#888] hidden sm:inline">
| · Shift/Ctrl+ · Ctrl+A
</span>
)}
</>
)}
</div>
<div className="flex items-center gap-2 shrink-0">
{!isNarrow && (
<span className="hidden sm:inline text-[10px]">
使 · · Ctrl+
</span>
{selectedElemObj && (
<span className="text-[#31a8ff]">
| {selectedElemObj.name} X:{selectedElemObj.x} Y:{selectedElemObj.y} W:
{selectedElemObj.width} H:{selectedElemObj.height} mm
</span>
)}
{selectedElementIds.length > 1 && (
<span className="text-emerald-400">| {selectedElementIds.length} </span>
)}
{activeTool === 'move' && (
<span className="text-[#888] hidden sm:inline">
| · Shift/Ctrl+ · Ctrl+A
</span>
)}
</>
)}
</div>
<div className="flex items-center gap-2 shrink-0">
{!isNarrow && (
<span className="hidden sm:inline text-[10px]">
使 · · Ctrl+
</span>
)}
<div className="ps-zoom-control">
<button className="ps-zoom-btn" onClick={() => setZoom(Math.max(0.5, zoom - 0.25))} title="缩小">
<ZoomOut className="w-3 h-3" />
</button>
<span className="px-1.5 text-[10px] font-mono text-[#ccc] min-w-[42px] text-center select-none">
{Math.round(zoom * 100)}%
</span>
<button className="ps-zoom-btn" onClick={() => setZoom(Math.min(5, zoom + 0.25))} title="放大">
<ZoomIn className="w-3 h-3" />
)}
{zoomControls}
<button
type="button"
onClick={applyModerateZoom}
className="text-[10px] text-[#aaa] hover:text-white px-1.5 py-0.5 border border-[#4a4a4a] hover:border-[#666] transition cursor-pointer"
title="适中缩放"
>
</button>
</div>
<button
onClick={applyModerateZoom}
className="text-[10px] text-[#aaa] hover:text-white px-1.5 py-0.5 border border-[#4a4a4a] hover:border-[#666] transition cursor-pointer"
title="适中缩放"
>
</button>
<button
onClick={fitToView}
className="text-[10px] text-[#aaa] hover:text-white px-1.5 py-0.5 border border-[#4a4a4a] hover:border-[#666] transition cursor-pointer"
title="适合窗口"
>
</button>
</div>
</div>
)}
</div>
</div>
);