From 4955b1f2e2c0503832063469a04c6f0b0cb5ecab Mon Sep 17 00:00:00 2001 From: iqudoo Date: Sun, 14 Jun 2026 12:03:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B8=83=E5=B1=80=E5=92=8C?= =?UTF-8?q?=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 24 ++++- src/components/AppDialog.tsx | 60 ++++++++++- src/components/LabelDesigner.tsx | 54 +++++++++- src/components/MobileDesignView.tsx | 5 + src/components/MobileMainToolButtons.tsx | 123 +++++++++++++++++++++++ src/components/PropSidebar.tsx | 28 ++++++ src/hooks/useElementClipboard.ts | 82 +++++++++++++++ src/index.css | 73 ++++++++++++-- src/utils.ts | 20 ++++ 9 files changed, 454 insertions(+), 15 deletions(-) create mode 100644 src/components/MobileMainToolButtons.tsx create mode 100644 src/hooks/useElementClipboard.ts diff --git a/src/App.tsx b/src/App.tsx index b3b5d3a..b7422e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,6 +35,7 @@ import { } from './labelPrint'; import { useIsMobile } from './hooks/useIsMobile'; import { useUndoRedo } from './hooks/useUndoRedo'; +import { useElementClipboard } from './hooks/useElementClipboard'; import { useAppDialog } from './components/AppDialog'; import { AnchoredMenu } from './components/AnchoredMenu'; import { @@ -78,7 +79,7 @@ function fitTemplatePreviewSize(widthMm: number, heightMm: number) { } export default function App() { - const { showAlert, showConfirm } = useAppDialog(); + const { showAlert, showConfirm, showToast } = useAppDialog(); // --- 1. Templates Storage & Initial States --- const [templates, setTemplates] = useState(() => { @@ -249,6 +250,22 @@ export default function App() { [activeTemplate] ); + const handleCopyElementsSuccess = useCallback( + (count: number) => { + showToast(`已复制 ${count} 个元素`, { variant: 'success' }); + }, + [showToast] + ); + + const elementClipboard = useElementClipboard({ + template: activeTemplate ?? EMPTY_TEMPLATE_PLACEHOLDER, + onChange: handleDesignTemplateChange, + selectedElementIds, + onSelectElements: handleSelectElements, + onSelectTableCells: setSelectedTableCells, + onCopySuccess: handleCopyElementsSuccess, + }); + useEffect(() => { if (workflowStep !== 'template-design' || !activeTemplate?.id) return; const needReset = @@ -1343,6 +1360,7 @@ export default function App() { onRedo={handleDesignRedo} onBack={() => setWorkflowStep('template-select')} onExport={() => openPrintView()} + elementClipboard={elementClipboard} /> )} @@ -1385,7 +1403,7 @@ export default function App() { - Shift/Ctrl+点击多选 + Shift/Ctrl+点击多选 · Ctrl+C/V 复制粘贴 + ); + + return ( +
+ {visibleItems.map((item) => renderToolButton(item))} + {overflowItems.length > 0 && ( + <> + + setMoreOpen(false)} + anchorRef={moreButtonRef} + className="mobile-toolstrip-overflow-menu" + > + {overflowItems.map((item) => ( + + ))} + + + )} +
+ ); +}; diff --git a/src/components/PropSidebar.tsx b/src/components/PropSidebar.tsx index 37a7a4d..40418fd 100644 --- a/src/components/PropSidebar.tsx +++ b/src/components/PropSidebar.tsx @@ -25,6 +25,7 @@ import { FieldSelect } from './PsSelect'; import { CommitInput } from './CommitInput'; import { useAppDialog } from './AppDialog'; import { useIsNarrowScreen } from '../hooks/useIsMobile'; +import type { ElementClipboardActions } from '../hooks/useElementClipboard'; import { TablePropertyFields, TableCellContentFields, @@ -53,6 +54,8 @@ import { Move, Layers, Trash2, + Copy, + ClipboardPaste, AlignHorizontalJustifyStart, AlignHorizontalJustifyCenter, AlignHorizontalJustifyEnd, @@ -180,6 +183,7 @@ interface PropSidebarProps { variant?: 'desktop' | 'mobile'; mobileModule?: MobilePropModule; onMobileModuleChange?: (module: MobilePropModule) => void; + elementClipboard?: ElementClipboardActions; } const MOBILE_PROP_MODULES: { id: MobilePropModule; label: string; icon: React.ReactNode }[] = [ @@ -202,6 +206,7 @@ export const PropSidebar: React.FC = ({ variant = 'desktop', mobileModule: mobileModuleProp, onMobileModuleChange, + elementClipboard, }) => { const { showConfirm } = useAppDialog(); const isNarrowScreen = useIsNarrowScreen(); @@ -2307,6 +2312,29 @@ export const PropSidebar: React.FC = ({ ))}
+ {elementClipboard?.canPaste && ( + + )} +