update
This commit is contained in:
@@ -1,28 +1,35 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useState } from "#app";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
path?: string;
|
path?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
date?: string;
|
date?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
image?: string;
|
image?: string | null;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
tags?: Array<string>;
|
tags?: Array<string>;
|
||||||
categories?: Array<string>;
|
categories?: Array<string>;
|
||||||
published?: boolean;
|
published?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
path: "/",
|
path: "/",
|
||||||
title: "no-title",
|
title: "no-title",
|
||||||
date: "no-date",
|
date: "no-date",
|
||||||
description: "no-description",
|
description: "no-description",
|
||||||
image: getRandomFallbackImage(),
|
image: null,
|
||||||
alt: "no-alt",
|
alt: "no-alt",
|
||||||
ogImage: getRandomFallbackImage(),
|
|
||||||
tags: () => [],
|
tags: () => [],
|
||||||
categories: () => [],
|
categories: () => [],
|
||||||
published: false,
|
published: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Use a per-card state key so the chosen fallback is the same on server and client
|
||||||
|
const imageStateKey = `card-image-${props.path}`;
|
||||||
|
const imageSrc = useState<string>(imageStateKey, () => {
|
||||||
|
return props.image || getRandomFallbackImage();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -32,8 +39,8 @@ withDefaults(defineProps<Props>(), {
|
|||||||
<div class="overflow-hidden aspect-video">
|
<div class="overflow-hidden aspect-video">
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
class="h-full w-full object-cover object-center transition-transform duration-700 group-hover:scale-110"
|
class="h-full w-full object-cover object-center transition-transform duration-700 group-hover:scale-110"
|
||||||
:src="image"
|
:src="imageSrc"
|
||||||
:alt="alt" />
|
:alt="props.alt || 'no-alt'" />
|
||||||
</div>
|
</div>
|
||||||
<div class="px-5 py-5 flex flex-col grow">
|
<div class="px-5 py-5 flex flex-col grow">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
|||||||
@@ -3,9 +3,21 @@ import siteConfig from "~/config";
|
|||||||
import SocialLinks from "./SocialLinks.vue";
|
import SocialLinks from "./SocialLinks.vue";
|
||||||
import Typed from "typed.js";
|
import Typed from "typed.js";
|
||||||
|
|
||||||
const descriptions = siteConfig.hero.description;
|
// Normalize `siteConfig.hero.description` to a string[] to satisfy TypeScript
|
||||||
|
const rawDescription = siteConfig.hero?.description;
|
||||||
|
let descriptions: string[] = [];
|
||||||
|
if (Array.isArray(rawDescription)) {
|
||||||
|
descriptions = rawDescription.filter((d) => typeof d === "string" && d.length > 0) as string[];
|
||||||
|
} else if (typeof rawDescription === "string" && rawDescription.length > 0) {
|
||||||
|
descriptions = [rawDescription];
|
||||||
|
} else if (typeof siteConfig.hero?.title === "string" && siteConfig.hero.title.length > 0) {
|
||||||
|
descriptions = [siteConfig.hero.title];
|
||||||
|
} else {
|
||||||
|
descriptions = [""];
|
||||||
|
}
|
||||||
|
|
||||||
const typedElement = ref<HTMLElement | null>(null);
|
const typedElement = ref<HTMLElement | null>(null);
|
||||||
const randomDescription = ref(descriptions[0]);
|
const randomDescription = ref(descriptions[0] || "");
|
||||||
let typed: Typed | null = null;
|
let typed: Typed | null = null;
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@@ -18,7 +30,7 @@ onMounted(() => {
|
|||||||
backDelay: siteConfig.hero.typed.backDelay || 2000,
|
backDelay: siteConfig.hero.typed.backDelay || 2000,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
randomDescription.value = descriptions[Math.floor(Math.random() * descriptions.length)];
|
randomDescription.value = descriptions[Math.floor(Math.random() * descriptions.length)] || "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -42,6 +54,8 @@ onUnmounted(() => {
|
|||||||
:src="siteConfig.profile.avatar"
|
:src="siteConfig.profile.avatar"
|
||||||
alt="avatar"
|
alt="avatar"
|
||||||
class="relative h-full w-full object-cover rounded-full border-4 border-white/80 dark:border-slate-800/80 shadow-2xl transition-transform duration-500 group-hover:scale-105"
|
class="relative h-full w-full object-cover rounded-full border-4 border-white/80 dark:border-slate-800/80 shadow-2xl transition-transform duration-500 group-hover:scale-105"
|
||||||
|
width="160px"
|
||||||
|
height="160px"
|
||||||
loading="eager" />
|
loading="eager" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const formattedData = computed(() => {
|
|||||||
path: articles.path,
|
path: articles.path,
|
||||||
title: articles.title || "no-title available",
|
title: articles.title || "no-title available",
|
||||||
description: articles.description || "no-description available",
|
description: articles.description || "no-description available",
|
||||||
image: articles.image || getRandomFallbackImage(),
|
image: articles.image || getRandomFallbackImage(articles.path),
|
||||||
alt: articles.alt || "no alter data available",
|
alt: articles.alt || "no alter data available",
|
||||||
date: articles.date || "not-date-available",
|
date: articles.date || "not-date-available",
|
||||||
tags: articles.tags || [],
|
tags: articles.tags || [],
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const formattedData = computed(() => {
|
|||||||
path: articles.path,
|
path: articles.path,
|
||||||
title: articles.title || "no-title available",
|
title: articles.title || "no-title available",
|
||||||
description: articles.description || "no-description available",
|
description: articles.description || "no-description available",
|
||||||
image: articles.image || getRandomFallbackImage(),
|
image: articles.image || getRandomFallbackImage(articles.path),
|
||||||
alt: articles.alt || "no alter data available",
|
alt: articles.alt || "no alter data available",
|
||||||
date: articles.date || "not-date-available",
|
date: articles.date || "not-date-available",
|
||||||
tags: articles.tags || [],
|
tags: articles.tags || [],
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const formattedData = computed(() => {
|
|||||||
path: articles.path,
|
path: articles.path,
|
||||||
title: articles.title || "no-title available",
|
title: articles.title || "no-title available",
|
||||||
description: articles.description || "no-description available",
|
description: articles.description || "no-description available",
|
||||||
image: articles.image || getRandomFallbackImage(),
|
image: articles.image || getRandomFallbackImage(articles.path),
|
||||||
alt: articles.alt || "no alter data available",
|
alt: articles.alt || "no alter data available",
|
||||||
date: formatDate(articles.date) || "not-date-available",
|
date: formatDate(articles.date) || "not-date-available",
|
||||||
tags: articles.tags || [],
|
tags: articles.tags || [],
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export function formatDate(dateString: string): string {
|
|||||||
* Gets a random 404 image from the /public/404/ directory
|
* Gets a random 404 image from the /public/404/ directory
|
||||||
* @returns A random image path from /public/404/
|
* @returns A random image path from /public/404/
|
||||||
*/
|
*/
|
||||||
export function getRandomFallbackImage(): string {
|
export function getRandomFallbackImage(seed?: string): string {
|
||||||
const fallbackImages: string[] = [
|
const fallbackImages: string[] = [
|
||||||
"/404/1.webp",
|
"/404/1.webp",
|
||||||
"/404/2.webp",
|
"/404/2.webp",
|
||||||
@@ -42,11 +42,17 @@ export function getRandomFallbackImage(): string {
|
|||||||
"/404/9.webp",
|
"/404/9.webp",
|
||||||
];
|
];
|
||||||
|
|
||||||
if (import.meta.server) {
|
// If a seed is provided, choose a deterministic image based on the seed.
|
||||||
// 在服务器端返回第一张图片以保证 SSR 一致性
|
if (seed) {
|
||||||
return fallbackImages[0]!;
|
let hash = 0;
|
||||||
|
for (let i = 0; i < seed.length; i++) {
|
||||||
|
hash = (hash << 5) - hash + seed.charCodeAt(i);
|
||||||
|
hash |= 0;
|
||||||
|
}
|
||||||
|
const idx = Math.abs(hash) % fallbackImages.length;
|
||||||
|
return fallbackImages[idx]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
const randomIndex = Math.floor(Math.random() * fallbackImages.length);
|
// No seed: return the first image to keep SSR/client consistent.
|
||||||
return fallbackImages[randomIndex]!;
|
return fallbackImages[0]!;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user