This commit is contained in:
iqudoo
2026-06-13 03:41:20 +08:00
parent 979a565468
commit 3b9724beb3
7 changed files with 634 additions and 129 deletions

View File

@@ -25,6 +25,7 @@ import { exportLabelsPdfAsync, type PdfExportProgress } from './pdfExport';
import { useIsMobile } from './hooks/useIsMobile';
import { useUndoRedo } from './hooks/useUndoRedo';
import { useAppDialog } from './components/AppDialog';
import { AnchoredMenu } from './components/AnchoredMenu';
import {
FileDown,
Code,
@@ -153,7 +154,7 @@ export default function App() {
const [editingTemplateId, setEditingTemplateId] = useState<string | null>(null);
const [editTmplName, setEditTmplName] = useState<string>('');
const [templateMenuId, setTemplateMenuId] = useState<string | null>(null);
const templateMenuRef = useRef<HTMLDivElement | null>(null);
const templateMenuAnchorRef = useRef<HTMLDivElement | null>(null);
const [headerTemplateMenuOpen, setHeaderTemplateMenuOpen] = useState(false);
const headerTemplateMenuRef = useRef<HTMLDivElement | null>(null);
const importTemplateInputRef = useRef<HTMLInputElement | null>(null);
@@ -390,26 +391,6 @@ export default function App() {
const closeTemplateMenu = () => setTemplateMenuId(null);
useEffect(() => {
if (!templateMenuId) return;
const handlePointerDown = (e: PointerEvent) => {
if (templateMenuRef.current?.contains(e.target as Node)) return;
closeTemplateMenu();
};
document.addEventListener('pointerdown', handlePointerDown);
return () => document.removeEventListener('pointerdown', handlePointerDown);
}, [templateMenuId]);
useEffect(() => {
if (!headerTemplateMenuOpen) return;
const handlePointerDown = (e: PointerEvent) => {
if (headerTemplateMenuRef.current?.contains(e.target as Node)) return;
setHeaderTemplateMenuOpen(false);
};
document.addEventListener('pointerdown', handlePointerDown);
return () => document.removeEventListener('pointerdown', handlePointerDown);
}, [headerTemplateMenuOpen]);
const handleEditTemplateSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!editingTemplateId) return;
@@ -841,37 +822,37 @@ export default function App() {
>
<MoreHorizontal className="w-5 h-5 shrink-0" />
</button>
{headerTemplateMenuOpen && (
<div
role="menu"
className="absolute right-0 top-full mt-1 z-30 min-w-[148px] py-1 bg-white border border-gray-200 rounded-xl shadow-lg"
<AnchoredMenu
open={headerTemplateMenuOpen}
onClose={() => setHeaderTemplateMenuOpen(false)}
anchorRef={headerTemplateMenuRef}
className="min-w-[148px] py-1 bg-white border border-gray-200 rounded-xl shadow-lg"
>
<button
type="button"
role="menuitem"
onClick={() => {
setHeaderTemplateMenuOpen(false);
setShowAddTmplForm(true);
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 cursor-pointer"
>
<button
type="button"
role="menuitem"
onClick={() => {
setHeaderTemplateMenuOpen(false);
setShowAddTmplForm(true);
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 cursor-pointer"
>
<Plus className="w-4 h-4 shrink-0" />
</button>
<button
type="button"
role="menuitem"
onClick={() => {
setHeaderTemplateMenuOpen(false);
importTemplateInputRef.current?.click();
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 cursor-pointer"
>
<Upload className="w-4 h-4 shrink-0" />
</button>
</div>
)}
<Plus className="w-4 h-4 shrink-0" />
</button>
<button
type="button"
role="menuitem"
onClick={() => {
setHeaderTemplateMenuOpen(false);
importTemplateInputRef.current?.click();
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 cursor-pointer"
>
<Upload className="w-4 h-4 shrink-0" />
</button>
</AnchoredMenu>
</div>
</>
) : (
@@ -1067,7 +1048,7 @@ export default function App() {
{isMobile ? '生成' : '排版导出'}
</button>
<div
ref={templateMenuId === tmpl.id ? templateMenuRef : null}
ref={templateMenuId === tmpl.id ? templateMenuAnchorRef : null}
className="relative shrink-0"
>
<button
@@ -1085,53 +1066,52 @@ export default function App() {
>
<MoreHorizontal className={`${isMobile ? 'w-5 h-5' : 'w-4 h-4'} shrink-0`} />
</button>
{templateMenuId === tmpl.id && (
<div
role="menu"
className="absolute right-0 top-full mt-1 z-30 min-w-[90px] py-1 bg-white border border-gray-200 rounded-xl shadow-lg"
onClick={(e) => e.stopPropagation()}
<AnchoredMenu
open={templateMenuId === tmpl.id}
onClose={closeTemplateMenu}
anchorRef={templateMenuAnchorRef}
className="min-w-[120px] py-1 bg-white border border-gray-200 rounded-xl shadow-lg"
>
<button
type="button"
role="menuitem"
onClick={(e) => {
e.stopPropagation();
closeTemplateMenu();
openEditTemplateDialog(tmpl);
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 cursor-pointer"
>
<button
type="button"
role="menuitem"
onClick={(e) => {
e.stopPropagation();
closeTemplateMenu();
openEditTemplateDialog(tmpl);
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 cursor-pointer"
>
<Pencil className="w-4 h-4 shrink-0" />
</button>
<button
type="button"
role="menuitem"
onClick={(e) => {
e.stopPropagation();
closeTemplateMenu();
handleExportTemplate(tmpl);
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 cursor-pointer"
>
<FileDown className="w-4 h-4 shrink-0" />
</button>
<button
type="button"
role="menuitem"
onClick={(e) => {
e.stopPropagation();
closeTemplateMenu();
void handleDeleteTemplate(tmpl.id!);
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-rose-600 hover:bg-rose-50 active:bg-rose-100 cursor-pointer"
>
<Trash2 className="w-4 h-4 shrink-0" />
</button>
</div>
)}
<Pencil className="w-4 h-4 shrink-0" />
</button>
<button
type="button"
role="menuitem"
onClick={(e) => {
e.stopPropagation();
closeTemplateMenu();
handleExportTemplate(tmpl);
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-gray-700 hover:bg-gray-50 active:bg-gray-100 cursor-pointer"
>
<FileDown className="w-4 h-4 shrink-0" />
</button>
<button
type="button"
role="menuitem"
onClick={(e) => {
e.stopPropagation();
closeTemplateMenu();
void handleDeleteTemplate(tmpl.id!);
}}
className="inline-flex items-center gap-3 w-full px-3 py-2.5 text-left text-sm text-rose-600 hover:bg-rose-50 active:bg-rose-100 cursor-pointer"
>
<Trash2 className="w-4 h-4 shrink-0" />
</button>
</AnchoredMenu>
</div>
</div>
</div>