diff --git a/.vitepress/theme/components/PrevNext.vue b/.vitepress/theme/components/PrevNext.vue index 5e4988e..f3775bd 100644 --- a/.vitepress/theme/components/PrevNext.vue +++ b/.vitepress/theme/components/PrevNext.vue @@ -4,92 +4,101 @@ import { useGlobalData } from "../composables/useGlobalData"; import { usePostStore } from "../stores/posts"; const { page } = useGlobalData(); - const postsStore = usePostStore(); -const postsRef = computed(() => postsStore.posts); -function normalize(u: string | undefined | null) { +/** + * 规范化路径字符串,移除 Origin、.html 后缀及末尾斜杠 + * @param {string | undefined | null} u 原始路径或 URL + * @returns {string} 处理后的规范化路径 + */ +function normalize(u: string | undefined | null): string { if (!u) return ""; - try { const url = String(u); const withoutOrigin = url.replace(/^https?:\/\/[^/]+/, ""); return withoutOrigin.replace(/(?:\.html)?\/?$/, ""); - } catch (e) { + } catch { return String(u); } } +/** + * 计算当前页面的潜在匹配标识符(路径、Slug、文件名等) + * @returns {ComputedRef} 规范化后的候选标识符数组 + */ const currentCandidates = computed(() => { const p = page.value as any; - const cand: string[] = []; + if (!p) return []; + const cand = new Set(); + + // 基础属性收集 ["path", "regularPath", "url", "relativePath", "filePath", "_file"].forEach((k) => { - if (p && p[k]) cand.push(String(p[k])); + if (p[k]) cand.add(String(p[k])); }); - if (p && p.frontmatter) { - if (p.frontmatter.permalink) cand.push(String(p.frontmatter.permalink)); - if (p.frontmatter.slug) cand.push(String(p.frontmatter.slug)); + // Frontmatter 标识收集 + if (p.frontmatter) { + if (p.frontmatter.permalink) cand.add(String(p.frontmatter.permalink)); + if (p.frontmatter.slug) cand.add(String(p.frontmatter.slug)); } - const filePath = p && (p.filePath || p._file || p.relativePath || ""); - if (filePath && typeof filePath === "string") { - const m = filePath.match(/posts\/(.+?)\.mdx?$/) || filePath.match(/posts\/(.+?)\.md$/); - if (m && m[1]) { - const name = m[1]; - cand.push(`/posts/${encodeURIComponent(name)}`); - cand.push(`/posts/${encodeURIComponent(name)}.html`); + // 针对博客文章路径的特殊解析 + const filePath = p.filePath || p._file || p.relativePath || ""; + if (filePath) { + const match = filePath.match(/posts\/(.+?)\.mdx?$/); + if (match?.[1]) { + const name = match[1]; + cand.add(`/posts/${encodeURIComponent(name)}`); + cand.add(`/posts/${encodeURIComponent(name)}.html`); } } - if (p && p.title) cand.push(String(p.title)); + // 标题作为保底匹配项 + if (p.title) cand.add(String(p.title)); - return Array.from(new Set(cand.map((c) => normalize(c)))); + return Array.from(cand).map((c) => normalize(c)); }); +/** + * 在文章列表中查找当前页面的索引 + * @returns {ComputedRef} 当前文章的索引,未找到返回 -1 + */ const currentIndex = computed(() => { - const posts = postsRef.value || []; + const posts = postsStore.posts || []; + const candidates = currentCandidates.value; + const pTitle = (page.value as any)?.title; - for (let i = 0; i < posts.length; i++) { - const post = posts[i]; + return posts.findIndex((post) => { const postNorm = normalize(post.url); - - for (const c of currentCandidates.value) { - if (!c) continue; - if (postNorm === c) return i; - if (postNorm === c + "") return i; - } - - const pTitle = (page.value as any)?.title; - if (pTitle && post.title && String(post.title) === String(pTitle)) return i; - } - - return -1; + // 路径/标识符匹配 + if (candidates.some((c) => c && postNorm === c)) return true; + // 标题匹配(保底) + if (pTitle && post.title && String(post.title) === String(pTitle)) return true; + return false; + }); }); +/** 上一篇文章对象 */ const prev = computed(() => { - const posts = postsRef.value || []; const idx = currentIndex.value; - if (idx > 0) return posts[idx - 1]; - return null; + return idx > 0 ? postsStore.posts[idx - 1] : null; }); +/** 下一篇文章对象 */ const next = computed(() => { - const posts = postsRef.value || []; const idx = currentIndex.value; - if (idx >= 0 && idx < posts.length - 1) return posts[idx + 1]; - return null; + return idx >= 0 && idx < postsStore.posts.length - 1 ? postsStore.posts[idx + 1] : null; });