公开页补齐自引用 Canonical 与按类型的结构化数据,图片补上说明文字。

后台设置和内容编辑改成可导航布局;部署用访客 origin 生成 Canonical,避免落到容器地址。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-20 00:11:24 +08:00
parent 2b3d122e56
commit 40bbad2c82
51 changed files with 3042 additions and 1318 deletions

View 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),
};
}