公开页补齐自引用 Canonical 与按类型的结构化数据,图片补上说明文字。
后台设置和内容编辑改成可导航布局;部署用访客 origin 生成 Canonical,避免落到容器地址。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
69
frontend/hooks/useUnsavedGuard.ts
Normal file
69
frontend/hooks/useUnsavedGuard.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
|
||||
/**
|
||||
* 有未保存修改时:关闭/刷新走 beforeunload;站内链接先确认再跳转。
|
||||
* 未修改时不打扰。hash 分类切换由调用方自行拦截。
|
||||
*/
|
||||
export default function useUnsavedGuard(dirty: boolean) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const dirtyRef = useRef(dirty);
|
||||
dirtyRef.current = dirty;
|
||||
const [pendingHref, setPendingHref] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const onBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||
if (!dirtyRef.current) return;
|
||||
e.preventDefault();
|
||||
e.returnValue = "";
|
||||
};
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const onClick = (e: MouseEvent) => {
|
||||
if (!dirtyRef.current) return;
|
||||
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
||||
return;
|
||||
}
|
||||
const target = e.target;
|
||||
if (!(target instanceof Element)) return;
|
||||
const a = target.closest("a[href]");
|
||||
if (!a) return;
|
||||
const href = a.getAttribute("href");
|
||||
if (!href || href.startsWith("#") || href.startsWith("mailto:") || href.startsWith("javascript:")) return;
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(href, window.location.href);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (url.origin !== window.location.origin) return;
|
||||
const next = `${url.pathname}${url.search}${url.hash}`;
|
||||
const cur = `${pathname}${window.location.search}${window.location.hash}`;
|
||||
if (next === cur) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setPendingHref(next);
|
||||
};
|
||||
document.addEventListener("click", onClick, true);
|
||||
return () => document.removeEventListener("click", onClick, true);
|
||||
}, [pathname]);
|
||||
|
||||
const confirmLeave = () => {
|
||||
if (!pendingHref) return;
|
||||
const href = pendingHref;
|
||||
setPendingHref(null);
|
||||
router.push(href);
|
||||
};
|
||||
|
||||
return {
|
||||
leaveOpen: pendingHref != null,
|
||||
confirmLeave,
|
||||
cancelLeave: () => setPendingHref(null),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user