优化布局和交互

This commit is contained in:
iqudoo
2026-06-14 12:03:05 +08:00
parent 55867ae12d
commit 4955b1f2e2
9 changed files with 454 additions and 15 deletions

View File

@@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react'
import { createPortal } from 'react-dom';
import { LabelTemplate, TemplateElement } from '../types';
import { mmToPx, MM_TO_PX, shouldRenderElement } from '../utils';
import type { ElementClipboardActions } from '../hooks/useElementClipboard';
import {
createDefaultTableElement,
getTableColWidths,
@@ -18,6 +19,7 @@ import { CanvasLabelImage } from './CanvasLabelImage';
import { ElementFloatPreview, renderElementFloatPreview } from './ElementFloatPreview';
import { renderLabelToDataUrl } from '../labelRenderer';
import { useAppDialog } from './AppDialog';
import { MobileMainToolButtons, type MobileToolButtonItem } from './MobileMainToolButtons';
import {
ZoomIn,
ZoomOut,
@@ -40,6 +42,10 @@ interface LabelDesignerProps {
variant?: 'desktop' | 'mobile';
/** 微调等场景下隐藏选区框与缩放手柄 */
suppressSelectionChrome?: boolean;
elementClipboard?: Pick<
ElementClipboardActions,
'copySelectedElements' | 'pasteClipboardElements' | 'canCopy' | 'canPaste' | 'clipboardCount'
>;
}
type ActiveTool = 'move' | 'text' | 'barcode' | 'qrcode' | 'table';
@@ -212,6 +218,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
onSelectTableCells,
variant = 'desktop',
suppressSelectionChrome = false,
elementClipboard,
}) => {
const { showConfirm } = useAppDialog();
const selectedElementId = selectedElementIds[0] || null;
@@ -494,6 +501,17 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
onSelectElements(template.elements.filter((el) => !el.locked).map((el) => el.id));
return;
}
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') {
e.preventDefault();
elementClipboard?.copySelectedElements();
return;
}
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'v') {
e.preventDefault();
elementClipboard?.pasteClipboardElements();
setActiveTool('move');
return;
}
if (e.key === 'Delete' || e.key === 'Backspace') {
e.preventDefault();
void deleteSelectedElements();
@@ -539,6 +557,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
addElement,
onSelectElements,
deleteSelectedElements,
elementClipboard,
]);
const clearInteractionCanvasState = useCallback(() => {
@@ -1430,6 +1449,28 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
{ id: 'qrcode', icon: <QrCode className="w-[18px] h-[18px]" />, label: '二维码' },
];
const mobileToolItems = useMemo<MobileToolButtonItem[]>(
() => [
...tools.map((tool) => ({
id: tool.id,
icon: tool.icon,
label: tool.label,
shortcut: tool.shortcut,
active: activeTool === tool.id,
onClick: () => handleToolClick(tool.id),
})),
{
id: 'image',
icon: <ImageIcon className="w-[18px] h-[18px]" />,
label: '图片',
active: false,
onClick: handleImageToolClick,
},
],
// eslint-disable-next-line react-hooks/exhaustive-deps -- tools 为静态配置,随 activeTool 更新即可
[activeTool]
);
const mainToolButtons = (
<>
{tools.map((tool) => (
@@ -1922,9 +1963,14 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
{isMobileLayout ? (
<div className="mobile-design-toolstrip">
<div className="mobile-design-toolstrip-tools">
{mainToolButtons}
</div>
<MobileMainToolButtons items={mobileToolItems} />
<input
ref={imageInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleImageFile}
/>
{zoomControls}
</div>
) : (
@@ -1952,7 +1998,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
)}
{activeTool === 'move' && (
<span className="text-[#888] hidden sm:inline">
| · Shift/Ctrl+ · Ctrl+A
| · Shift/Ctrl+ · Ctrl+A · Ctrl+C/V
</span>
)}
</>