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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user