feat: 可配置站点 URL 风格

- 新增 urlStyle 工具与测试:postPath/userPath/sitePagePath 统一生成链接
- 管理端新增 UrlStylePanel 设置面板,settings 服务与 handler 适配
- 全站路由链接与 JSON-LD/sitemap 切换为统一 URL 生成
- middleware 与相关组件适配
This commit is contained in:
2026-09-27 04:01:42 +08:00
parent 1d862a0dd3
commit 7a44c1f2cf
64 changed files with 1183 additions and 200 deletions

View File

@@ -11,6 +11,7 @@ import {
type SitePageInput,
} from "@/lib/api";
import { sitePagePublicPath, sitePagePublicURL } from "@/lib/sitePagePath";
import { useUrlStyle } from "@/components/SiteBrandProvider";
import { toast } from "@/lib/toast";
import MarkdownEditor from "@/components/MarkdownEditor";
import { AdminField, AdminSegmented, AdminStatusChip, AdminSwitch } from "@/components/admin";
@@ -129,6 +130,7 @@ export default function PageComposeClient({
}) {
const router = useRouter();
const searchParams = useSearchParams();
const urlStyle = useUrlStyle();
const status = readStatus(searchParams.get("status"));
const backHref = listHref(status);
const loaded = useMemo(() => toForm(initial), [initial]);
@@ -160,7 +162,7 @@ export default function PageComposeClient({
};
const slugPreview = normalizeSlug(form.slug);
const publicPath = sitePagePublicPath(slugPreview);
const publicPath = sitePagePublicPath(slugPreview, urlStyle);
const canViewOnline = savedPage?.published === true;
async function commit() {
@@ -252,7 +254,7 @@ export default function PageComposeClient({
setErrors((prev) => ({ ...prev, slug: nextErrors.slug }));
return;
}
const url = sitePagePublicURL(origin || window.location.origin, slug);
const url = sitePagePublicURL(origin || window.location.origin, slug, urlStyle);
try {
await navigator.clipboard.writeText(url);
toast("已复制访问地址", "ok");
@@ -280,7 +282,7 @@ export default function PageComposeClient({
}
if (pendingSave?.slugChange) {
confirmBits.push(
`访问地址将从 ${sitePagePublicPath(baseline.slug)} 改为 ${sitePagePublicPath(normalizeSlug(form.slug))}。系统不会把旧地址转到新地址,已经分享的旧链接会打不开。`
`访问地址将从 ${sitePagePublicPath(baseline.slug, urlStyle)} 改为 ${sitePagePublicPath(normalizeSlug(form.slug), urlStyle)}。系统不会把旧地址转到新地址,已经分享的旧链接会打不开。`
);
}
@@ -304,7 +306,7 @@ export default function PageComposeClient({
<div className="admin-doc-sticky-actions">
{canViewOnline && savedPage ? (
<a
href={sitePagePublicPath(savedPage.slug)}
href={sitePagePublicPath(savedPage.slug, urlStyle)}
target="_blank"
rel="noopener noreferrer"
className="btn btn-line btn-sm"
@@ -413,7 +415,7 @@ export default function PageComposeClient({
</div>
<p className="admin-doc-hint" id="page-slug-hint">
完整路径 {publicPath || "/p/"}。小写字母、数字和连字符,最多 64 字符。
{origin && slugPreview ? ` 线上地址 ${sitePagePublicURL(origin, slugPreview)}` : ""}
{origin && slugPreview ? ` 线上地址 ${sitePagePublicURL(origin, slugPreview, urlStyle)}` : ""}
</p>
{isEdit && baseline.published && slugPreview !== baseline.slug ? (
<p className="admin-doc-hint">保存后访问地址会改变。旧链接不会自动跳转。</p>