优化修复
This commit is contained in:
@@ -16,7 +16,7 @@ import {
|
||||
} from '../tableUtils';
|
||||
import { useIsNarrowScreen } from '../hooks/useIsMobile';
|
||||
import { CanvasLabelImage } from './CanvasLabelImage';
|
||||
import { ElementFloatPreview, renderElementFloatPreview } from './ElementFloatPreview';
|
||||
import { ElementFloatPreview, getFloatPreviewPaddingMm, renderElementFloatPreview } from './ElementFloatPreview';
|
||||
import { renderLabelToDataUrl } from '../labelRenderer';
|
||||
import { useAppDialog } from './AppDialog';
|
||||
import { MobileMainToolButtons, type MobileToolButtonItem } from './MobileMainToolButtons';
|
||||
@@ -318,16 +318,19 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
selectedIdsRef.current = selectedElementIds;
|
||||
const templateElementsRef = useRef(template.elements);
|
||||
templateElementsRef.current = template.elements;
|
||||
const templateRef = useRef(template);
|
||||
templateRef.current = template;
|
||||
|
||||
const addImageElement = useCallback(
|
||||
(src: string = '') => {
|
||||
const current = templateRef.current;
|
||||
const id = `image_${Date.now()}`;
|
||||
const n = template.elements.length + 1;
|
||||
const n = current.elements.length + 1;
|
||||
const newElement: TemplateElement = {
|
||||
id,
|
||||
type: 'image',
|
||||
name: `图片 ${n}`,
|
||||
...centerElementOnLabel(template.width, template.height, 30, 30),
|
||||
...centerElementOnLabel(current.width, current.height, 30, 30),
|
||||
width: 30,
|
||||
height: 30,
|
||||
content: src,
|
||||
@@ -342,20 +345,21 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
imageFit: 'contain',
|
||||
};
|
||||
onChange({
|
||||
...template,
|
||||
elements: [...template.elements, newElement],
|
||||
...current,
|
||||
elements: [...current.elements, newElement],
|
||||
});
|
||||
onSelectElements([id]);
|
||||
setActiveTool('move');
|
||||
},
|
||||
[template, onChange, onSelectElements]
|
||||
[onChange, onSelectElements]
|
||||
);
|
||||
|
||||
const addElement = useCallback(
|
||||
(type: 'text' | 'barcode' | 'qrcode' | 'table') => {
|
||||
const current = templateRef.current;
|
||||
let newElement: TemplateElement;
|
||||
const id = `${type}_${Date.now()}`;
|
||||
const n = template.elements.length + 1;
|
||||
const n = current.elements.length + 1;
|
||||
|
||||
if (type === 'table') {
|
||||
newElement = createDefaultTableElement(id, n);
|
||||
@@ -421,24 +425,25 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
}
|
||||
|
||||
const centered = centerElementOnLabel(
|
||||
template.width,
|
||||
template.height,
|
||||
current.width,
|
||||
current.height,
|
||||
newElement.width,
|
||||
newElement.height
|
||||
);
|
||||
newElement = { ...newElement, ...centered };
|
||||
|
||||
onChange({
|
||||
...template,
|
||||
elements: [...template.elements, newElement],
|
||||
...current,
|
||||
elements: [...current.elements, newElement],
|
||||
});
|
||||
onSelectElements([id]);
|
||||
setActiveTool('move');
|
||||
},
|
||||
[template, onChange, onSelectElements]
|
||||
[onChange, onSelectElements]
|
||||
);
|
||||
|
||||
const deleteSelectedElements = useCallback(async () => {
|
||||
const current = templateRef.current;
|
||||
const ids =
|
||||
selectedElementIds.length > 0
|
||||
? selectedElementIds
|
||||
@@ -446,14 +451,14 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
? [selectedElementId]
|
||||
: [];
|
||||
const deletable = ids.filter((id) => {
|
||||
const el = template.elements.find((item) => item.id === id);
|
||||
const el = current.elements.find((item) => item.id === id);
|
||||
return el && !el.locked;
|
||||
});
|
||||
if (deletable.length === 0) return;
|
||||
|
||||
const message =
|
||||
deletable.length === 1
|
||||
? `确定删除「${template.elements.find((item) => item.id === deletable[0])?.name ?? '未命名'}」吗?此操作无法撤销。`
|
||||
? `确定删除「${current.elements.find((item) => item.id === deletable[0])?.name ?? '未命名'}」吗?此操作无法撤销。`
|
||||
: `确定删除选中的 ${deletable.length} 个元素吗?此操作无法撤销。`;
|
||||
|
||||
const confirmed = await showConfirm(message, {
|
||||
@@ -464,10 +469,10 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
if (!confirmed) return;
|
||||
|
||||
const idSet = new Set(deletable);
|
||||
const remaining = template.elements.filter((el) => !idSet.has(el.id));
|
||||
onChange({ ...template, elements: remaining });
|
||||
const remaining = current.elements.filter((el) => !idSet.has(el.id));
|
||||
onChange({ ...templateRef.current, elements: remaining });
|
||||
onSelectElements(selectedElementIds.filter((id) => remaining.some((el) => el.id === id)));
|
||||
}, [selectedElementIds, selectedElementId, template, onChange, onSelectElements, showConfirm]);
|
||||
}, [selectedElementIds, selectedElementId, onChange, onSelectElements, showConfirm]);
|
||||
|
||||
const handleToolClick = (tool: ActiveTool) => {
|
||||
if (tool === 'move') {
|
||||
@@ -505,7 +510,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'a') {
|
||||
e.preventDefault();
|
||||
setActiveTool('move');
|
||||
onSelectElements(template.elements.filter((el) => !el.locked).map((el) => el.id));
|
||||
onSelectElements(templateRef.current.elements.filter((el) => !el.locked).map((el) => el.id));
|
||||
return;
|
||||
}
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') {
|
||||
@@ -541,9 +546,10 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
|
||||
const idSet = new Set(moveIds);
|
||||
e.preventDefault();
|
||||
const current = templateRef.current;
|
||||
onChange({
|
||||
...template,
|
||||
elements: template.elements.map((el) => {
|
||||
...current,
|
||||
elements: current.elements.map((el) => {
|
||||
if (!idSet.has(el.id) || el.locked) return el;
|
||||
return {
|
||||
...el,
|
||||
@@ -1036,7 +1042,7 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
const shouldCommit =
|
||||
dragState.type === 'resize' || dragMovedRef.current;
|
||||
if (shouldCommit) {
|
||||
onChange({ ...template, elements: localElementsRef.current });
|
||||
onChange({ ...templateRef.current, elements: localElementsRef.current });
|
||||
}
|
||||
}
|
||||
localElementsRef.current = null;
|
||||
@@ -1474,8 +1480,8 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
onClick: handleImageToolClick,
|
||||
},
|
||||
],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- tools 为静态配置,随 activeTool 更新即可
|
||||
[activeTool]
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- tools 为静态配置
|
||||
[activeTool, handleToolClick, handleImageToolClick]
|
||||
);
|
||||
|
||||
const mainToolButtons = (
|
||||
@@ -1582,15 +1588,22 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
willChange: 'transform',
|
||||
}}
|
||||
>
|
||||
{dragFloatElements.map((el) => (
|
||||
{dragFloatElements.map((el) => {
|
||||
const padMm = getFloatPreviewPaddingMm(el);
|
||||
const padPx = mmToPx(padMm, displayZoom);
|
||||
const boxW = mmToPx(el.width, displayZoom);
|
||||
const boxH = mmToPx(el.height, displayZoom);
|
||||
return (
|
||||
<div
|
||||
key={el.id}
|
||||
className="absolute pointer-events-none"
|
||||
style={{
|
||||
left: `${mmToPx(el.x, displayZoom)}px`,
|
||||
top: `${mmToPx(el.y, displayZoom)}px`,
|
||||
width: `${mmToPx(el.width, displayZoom)}px`,
|
||||
height: `${mmToPx(el.height, displayZoom)}px`,
|
||||
width: `${boxW}px`,
|
||||
height: `${boxH}px`,
|
||||
transform: el.rotation ? `rotate(${el.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
@@ -1615,7 +1628,13 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
}
|
||||
}}
|
||||
alt=""
|
||||
className="absolute inset-0 w-full h-full pointer-events-none select-none opacity-0"
|
||||
className="absolute pointer-events-none select-none opacity-0"
|
||||
style={{
|
||||
left: `${-padPx}px`,
|
||||
top: `${-padPx}px`,
|
||||
width: `${boxW + padPx * 2}px`,
|
||||
height: `${boxH + padPx * 2}px`,
|
||||
}}
|
||||
onLoad={(e) => {
|
||||
const img = e.currentTarget;
|
||||
img.style.opacity = '1';
|
||||
@@ -1624,7 +1643,8 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{resizeFloatElements && resizeFloatElements.length > 0 && (
|
||||
@@ -1632,27 +1652,34 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
className="absolute inset-0 pointer-events-none"
|
||||
style={{ overflow: 'visible' }}
|
||||
>
|
||||
{resizeFloatElements.map((el) => (
|
||||
<React.Fragment key={el.id}>
|
||||
{resizeFloatElements.map((el) => {
|
||||
const left = mmToPx(el.x, displayZoom);
|
||||
const top = mmToPx(el.y, displayZoom);
|
||||
const width = mmToPx(el.width, displayZoom);
|
||||
const height = mmToPx(el.height, displayZoom);
|
||||
return (
|
||||
<div
|
||||
className="absolute pointer-events-none border border-dashed border-[#31a8ff]/40 resize-float-placeholder"
|
||||
key={el.id}
|
||||
className="absolute pointer-events-none"
|
||||
style={{
|
||||
left: `${mmToPx(el.x, displayZoom)}px`,
|
||||
top: `${mmToPx(el.y, displayZoom)}px`,
|
||||
width: `${mmToPx(el.width, displayZoom)}px`,
|
||||
height: `${mmToPx(el.height, displayZoom)}px`,
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
transform: el.rotation ? `rotate(${el.rotation}deg)` : undefined,
|
||||
transformOrigin: 'center center',
|
||||
}}
|
||||
/>
|
||||
<ElementFloatPreview
|
||||
element={el}
|
||||
renderScale={canvasRenderScale}
|
||||
displayZoom={displayZoom}
|
||||
variableDefaults={previewDefaults}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
>
|
||||
<ElementFloatPreview
|
||||
element={el}
|
||||
renderScale={canvasRenderScale}
|
||||
displayZoom={displayZoom}
|
||||
variableDefaults={previewDefaults}
|
||||
/>
|
||||
<div className="absolute inset-0 ps-resize-outline z-[2]" aria-hidden />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
@@ -1825,6 +1852,9 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
if (isBeingDragged) {
|
||||
return null;
|
||||
}
|
||||
if (isBeingResized && interactionCanvasReady && resizingPreviewElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const overlayElement =
|
||||
isBeingResized && resizingPreviewElement ? resizingPreviewElement : element;
|
||||
@@ -1884,6 +1914,10 @@ export const LabelDesigner: React.FC<LabelDesignerProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isBeingResized && (
|
||||
<div className="absolute inset-0 ps-resize-outline pointer-events-none z-[60]" aria-hidden />
|
||||
)}
|
||||
|
||||
{showResizeHandles && (
|
||||
<>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user