1
0
mirror of https://github.com/sendevia/website.git synced 2026-03-05 23:32:45 +08:00

refactor(PrevNext): improve path normalization and candidate matching logic

This commit is contained in:
2026-01-14 01:16:50 +08:00
parent 336fbb34fc
commit 3b61388272

View File

@@ -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<string[]>} 规范化后的候选标识符数组
*/
const currentCandidates = computed(() => {
const p = page.value as any;
const cand: string[] = [];
if (!p) return [];
const cand = new Set<string>();
// 基础属性收集
["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<number>} 当前文章的索引,未找到返回 -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;
});
</script>
<template>
<div class="PrevNext">
<a class="prev" :href="prev.url" v-if="prev">
<a v-if="prev" class="prev" :href="prev.url">
<span class="label">上一篇</span>
<span class="title">{{ prev.title }}</span>
</a>
<a class="next" :href="next.url" v-if="next">
<a v-if="next" class="next" :href="next.url">
<span class="label">下一篇</span>
<span class="title">{{ next.title }}</span>
</a>
@@ -98,5 +107,6 @@ const next = computed(() => {
<style lang="scss" scoped>
@use "sass:meta";
/* 引用现有的导航组件样式 */
@include meta.load-css("../styles/components/PrevNext");
</style>