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

feat: add post redirect handling

This commit is contained in:
2025-12-10 15:33:10 +08:00
parent b693666cae
commit 61b4e918a1
2 changed files with 82 additions and 37 deletions

View File

@@ -6,59 +6,80 @@ import { generateColorPalette } from "../utils/colorPalette";
import { onMounted, nextTick, computed, ref, watch } from "vue";
import { useRoute } from "vitepress";
import { useGlobalData } from "../composables/useGlobalData";
import { usePostStore } from "../stores/posts";
const { site, page, frontmatter, theme } = useGlobalData();
const route = useRoute();
const postStore = usePostStore();
const isTransitioning = ref(false);
const currentRoutePath = ref(route.path);
const pendingRoutePath = ref<string | null>(null);
const isRedirecting = ref(false);
let redirectTimer: ReturnType<typeof setTimeout> | null = null;
/**
* 检查并执行重定向
* 返回 true 表示正在处理重定向
*/
function checkAndRedirect(path: string): boolean {
if (isRedirecting.value) return true;
if (!path.startsWith("/p/")) return false;
const match = path.match(/^\/p\/([a-zA-Z0-9]+)\/?$/);
if (match) {
const id = match[1];
const post = postStore.getPostById(id);
if (post) {
isRedirecting.value = true;
if (typeof document !== "undefined") {
document.title = `跳转中 | ${site.value.title}`;
}
redirectTimer = setTimeout(() => {
if (typeof window !== "undefined") {
window.location.replace(post.url);
}
}, 100);
return true;
}
}
return false;
}
let currentPaletteTask: Promise<void> | null = null;
let isProcessingPalette = false;
async function updatePalette() {
if (isProcessingPalette) {
return;
}
if (isRedirecting.value || route.path.startsWith("/p/")) return;
if (isProcessingPalette) return;
isProcessingPalette = true;
currentPaletteTask = (async () => {
try {
await nextTick();
const defaultColor = theme.value.defaultColor;
const defaultArgb = argbFromHex(defaultColor);
await generateColorPalette(defaultArgb);
let logColor = defaultColor;
const el = document.querySelector(".Header .image");
if (el) {
const colorAttr = el.getAttribute("impression-color");
if (colorAttr && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(colorAttr)) {
const argb = argbFromHex(colorAttr);
await generateColorPalette(argb);
logColor = colorAttr;
}
}
// 最后输出最终使用的颜色
console.log(
`%c ${logColor} `,
`padding: 3px 4px; border-radius: 6px; color: #fff; background: ${logColor}; font-weight: bold; text-shadow: 0px 1px black;`
);
} finally {
isProcessingPalette = false;
currentPaletteTask = null;
}
})();
currentPaletteTask.catch((error) => {
console.warn("Palette update failed:", error);
});
}
const layoutMap = {
@@ -68,6 +89,7 @@ const layoutMap = {
type LayoutKey = keyof typeof layoutMap;
const currentLayout = computed(() => {
if (isRedirecting.value) return null;
if (frontmatter.value.home) return null;
if (page.value.isNotFound) return NotFoundLayout;
const key = (frontmatter.value.layout ?? "article") as LayoutKey;
@@ -77,7 +99,20 @@ const currentLayout = computed(() => {
watch(
() => route.path,
(newPath, oldPath) => {
if (newPath !== oldPath && !isTransitioning.value) {
if (checkAndRedirect(newPath)) {
return;
}
// 如果之前是重定向状态,清理状态
if (isRedirecting.value) {
isRedirecting.value = false;
if (redirectTimer) {
clearTimeout(redirectTimer);
redirectTimer = null;
}
}
if (newPath !== oldPath && !isTransitioning.value && oldPath !== undefined) {
isTransitioning.value = true;
pendingRoutePath.value = newPath;
}
@@ -86,6 +121,7 @@ watch(
);
function onAfterEnter() {
if (isRedirecting.value) return;
isTransitioning.value = false;
currentRoutePath.value = route.path;
pendingRoutePath.value = null;
@@ -93,34 +129,43 @@ function onAfterEnter() {
}
function onBeforeLeave() {
if (isRedirecting.value) return;
isTransitioning.value = true;
}
if (typeof window !== "undefined") {
onMounted(() => {
updatePalette();
if (!route.path.startsWith("/p/")) {
updatePalette();
}
});
}
</script>
<template>
<div class="MainLayout">
<NavBar />
<AppBar />
<Transition name="layout" mode="out-in" @before-leave="onBeforeLeave" @after-enter="onAfterEnter">
<div class="content-flow" :key="route.path">
<main v-if="frontmatter.home" class="home-content">
<hgroup class="title">
<h1>{{ site.title }}</h1>
<h6>{{ site.description }}</h6>
</hgroup>
<ArticleMasonry />
</main>
<component v-else :is="currentLayout" />
<ScrollToTop />
<Footer />
</div>
</Transition>
<div v-if="isRedirecting">
<h1>Redirecting...</h1>
</div>
<template v-else>
<NavBar />
<AppBar />
<Transition name="layout" mode="out-in" @before-leave="onBeforeLeave" @after-enter="onAfterEnter">
<div class="content-flow" :key="route.path">
<main v-if="frontmatter.home" class="home-content">
<hgroup class="title">
<h1>{{ site.title }}</h1>
<h6>{{ site.description }}</h6>
</hgroup>
<ArticleMasonry />
</main>
<component v-else :is="currentLayout" />
<ScrollToTop />
<Footer />
</div>
</Transition>
</template>
</div>
</template>

View File

@@ -41,7 +41,7 @@
padding: 24px;
.title {
hgroup.title {
display: flex;
align-items: flex-start;
flex-direction: column;