优化:修改撤回
This commit is contained in:
108
src/App.tsx
108
src/App.tsx
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useMemo, useEffect, useLayoutEffect, useRef } from 'react';
|
||||
import React, { useState, useMemo, useEffect, useLayoutEffect, useRef, useCallback } from 'react';
|
||||
import { LabelTemplate, PaperConfig, ImportData, RecordRow, ExportRangeConfig, CutLineConfig } from './types';
|
||||
import {
|
||||
stripTemplateForStorage,
|
||||
@@ -22,6 +22,7 @@ import { CanvasLabelImage } from './components/CanvasLabelImage';
|
||||
import { renderLabelToDataUrl } from './labelRenderer';
|
||||
import { exportLabelsPdfAsync, type PdfExportProgress } from './pdfExport';
|
||||
import { useIsMobile } from './hooks/useIsMobile';
|
||||
import { useUndoRedo } from './hooks/useUndoRedo';
|
||||
import { useAppDialog } from './components/AppDialog';
|
||||
import {
|
||||
FileDown,
|
||||
@@ -33,6 +34,8 @@ import {
|
||||
Pencil,
|
||||
Trash2,
|
||||
MoreHorizontal,
|
||||
Undo2,
|
||||
Redo2,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import logoUrl from './assets/logo.svg';
|
||||
@@ -150,6 +153,12 @@ export default function App() {
|
||||
const [editTmplName, setEditTmplName] = useState<string>('');
|
||||
const [templateMenuId, setTemplateMenuId] = useState<string | null>(null);
|
||||
const templateMenuRef = useRef<HTMLDivElement | null>(null);
|
||||
const designHistory = useUndoRedo<LabelTemplate>();
|
||||
const skipDesignHistoryRef = useRef(false);
|
||||
const designHistorySessionRef = useRef<{ step: string; templateId: string | null }>({
|
||||
step: '',
|
||||
templateId: null,
|
||||
});
|
||||
|
||||
const paper = workingTemplate.paper ?? DEFAULT_PAPER_CONFIG;
|
||||
const cutLine = workingTemplate.cutLine ?? DEFAULT_CUT_LINE_CONFIG;
|
||||
@@ -169,6 +178,79 @@ export default function App() {
|
||||
saveTemplatesToStorage(updatedList);
|
||||
};
|
||||
|
||||
const handleDesignTemplateChange = useCallback(
|
||||
(updated: LabelTemplate) => {
|
||||
if (
|
||||
!skipDesignHistoryRef.current &&
|
||||
workflowStep === 'template-design' &&
|
||||
activeTemplate &&
|
||||
updated.id === activeTemplate.id
|
||||
) {
|
||||
designHistory.record(activeTemplate, updated);
|
||||
}
|
||||
skipDesignHistoryRef.current = false;
|
||||
handleTemplateChange(updated);
|
||||
},
|
||||
[workflowStep, activeTemplate, designHistory, templates]
|
||||
);
|
||||
|
||||
const handleDesignUndo = useCallback(() => {
|
||||
if (!activeTemplate) return;
|
||||
const previous = designHistory.undo(activeTemplate);
|
||||
if (!previous) return;
|
||||
skipDesignHistoryRef.current = true;
|
||||
handleTemplateChange(previous);
|
||||
}, [activeTemplate, designHistory, templates]);
|
||||
|
||||
const handleDesignRedo = useCallback(() => {
|
||||
if (!activeTemplate) return;
|
||||
const next = designHistory.redo(activeTemplate);
|
||||
if (!next) return;
|
||||
skipDesignHistoryRef.current = true;
|
||||
handleTemplateChange(next);
|
||||
}, [activeTemplate, designHistory, templates]);
|
||||
|
||||
useEffect(() => {
|
||||
if (workflowStep !== 'template-design' || !activeTemplate?.id) return;
|
||||
const needReset =
|
||||
designHistorySessionRef.current.step !== 'template-design' ||
|
||||
designHistorySessionRef.current.templateId !== activeTemplate.id;
|
||||
if (needReset) {
|
||||
designHistory.reset();
|
||||
designHistorySessionRef.current = {
|
||||
step: workflowStep,
|
||||
templateId: activeTemplate.id,
|
||||
};
|
||||
}
|
||||
}, [workflowStep, activeTemplate?.id, designHistory]);
|
||||
|
||||
useEffect(() => {
|
||||
if (workflowStep !== 'template-design') return;
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const target = e.target as HTMLElement | null;
|
||||
if (
|
||||
target &&
|
||||
(target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.tagName === 'SELECT' ||
|
||||
target.isContentEditable)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const mod = e.metaKey || e.ctrlKey;
|
||||
if (!mod) return;
|
||||
if (e.key === 'z' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleDesignUndo();
|
||||
} else if (e.key === 'y' || (e.key === 'z' && e.shiftKey) || (e.key === 'Z' && e.shiftKey)) {
|
||||
e.preventDefault();
|
||||
handleDesignRedo();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, [workflowStep, handleDesignUndo, handleDesignRedo]);
|
||||
|
||||
// Save active template selected state and sync its dataset
|
||||
const handleSelectTemplate = (id: string) => {
|
||||
setSelectedTemplateId(id);
|
||||
@@ -1026,6 +1108,26 @@ export default function App() {
|
||||
{activeTemplate.width} × {activeTemplate.height} mm
|
||||
</span>
|
||||
<div className="flex-1" />
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDesignUndo}
|
||||
disabled={!designHistory.canUndo}
|
||||
title="撤销 (Ctrl+Z)"
|
||||
className="inline-flex items-center justify-center w-7 h-7 text-[#ccc] hover:text-white hover:bg-[#444] rounded transition cursor-pointer disabled:opacity-35 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:text-[#ccc]"
|
||||
>
|
||||
<Undo2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDesignRedo}
|
||||
disabled={!designHistory.canRedo}
|
||||
title="恢复 (Ctrl+Shift+Z / Ctrl+Y)"
|
||||
className="inline-flex items-center justify-center w-7 h-7 text-[#ccc] hover:text-white hover:bg-[#444] rounded transition cursor-pointer disabled:opacity-35 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:text-[#ccc]"
|
||||
>
|
||||
<Redo2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<span className="hidden md:inline text-[10px] text-[#666]">
|
||||
Shift/Ctrl+点击多选
|
||||
</span>
|
||||
@@ -1042,13 +1144,13 @@ export default function App() {
|
||||
<div className="flex-1 flex min-h-0">
|
||||
<LabelDesigner
|
||||
template={activeTemplate}
|
||||
onChange={handleTemplateChange}
|
||||
onChange={handleDesignTemplateChange}
|
||||
selectedElementIds={selectedElementIds}
|
||||
onSelectElements={setSelectedElementIds}
|
||||
/>
|
||||
<PropSidebar
|
||||
template={activeTemplate}
|
||||
onChangeTemplate={handleTemplateChange}
|
||||
onChangeTemplate={handleDesignTemplateChange}
|
||||
selectedElementIds={selectedElementIds}
|
||||
onSelectElements={setSelectedElementIds}
|
||||
activeTab="element"
|
||||
|
||||
Reference in New Issue
Block a user