- Enable `ssr: true` in `nuxt.config.ts` for server-side rendering of meta tags. - Implement `useSeoMeta` in `events/.vue` with fallback logic for Open Graph and Twitter cards. - Update `content.config.ts` to use `asSeoCollection` for the news collection. - Migrate event markdown frontmatter to use standardized SEO fields.
104 lines
3.5 KiB
Vue
104 lines
3.5 KiB
Vue
<template>
|
||
<div class="min-h-screen">
|
||
<section class="relative py-16 px-4 sm:px-6 lg:px-8">
|
||
<div class="container mx-auto max-w-4xl">
|
||
<!-- 内容卡片 -->
|
||
<div class="relative">
|
||
<!-- 卡片装饰边框(浅色渐变) -->
|
||
<!-- <div class="absolute inset-0 bg-linear-to-r from-blue-100 to-purple-100 rounded-2xl blur-sm"></div> -->
|
||
|
||
<div
|
||
class="relative rounded-xl border border-gray-200 shadow-xl overflow-hidden"
|
||
>
|
||
<!-- 顶部装饰条(明亮渐变) -->
|
||
<div
|
||
class="h-1 bg-linear-to-r from-blue-400 via-purple-400 to-cyan-400"
|
||
></div>
|
||
|
||
<div class="p-8 sm:p-10 lg:p-12">
|
||
<!-- 内容渲染器 -->
|
||
<div class="prose prose-lg max-w-none text-gray-800">
|
||
<ContentRenderer :value="event ?? {}">
|
||
<template #empty>
|
||
<div class="text-center py-16">
|
||
<div
|
||
class="inline-flex items-center justify-center w-16 h-16 bg-blue-100 rounded-full mb-4"
|
||
>
|
||
<svg
|
||
class="w-8 h-8 text-blue-500 animate-spin"
|
||
fill="none"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<circle
|
||
class="opacity-25"
|
||
cx="12"
|
||
cy="12"
|
||
r="10"
|
||
stroke="currentColor"
|
||
stroke-width="4"
|
||
></circle>
|
||
<path
|
||
class="opacity-75"
|
||
fill="currentColor"
|
||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||
></path>
|
||
</svg>
|
||
</div>
|
||
<p class="text-gray-700 text-lg font-medium">
|
||
内容加载中...
|
||
</p>
|
||
<p class="text-gray-400 text-sm mt-2">请稍等片刻</p>
|
||
</div>
|
||
</template>
|
||
</ContentRenderer>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
const route = useRoute();
|
||
const { data: event } = await useAsyncData("event-detail", () =>
|
||
queryCollection("events").path(`/events/${route.params.slug}`).first()
|
||
);
|
||
|
||
if (event.value) {
|
||
// 1. 确定图片:优先用 ogImage,没有就用 cover
|
||
const shareImage = event.value.ogImage || event.value.cover;
|
||
|
||
// 2. 确定标题和描述:优先用 seoTitle,没有就用 title
|
||
const shareTitle = event.value.seoTitle || event.value.title;
|
||
const shareDesc = event.value.seoDescription || event.value.description;
|
||
|
||
// 3. 注入 SEO
|
||
useSeoMeta({
|
||
// 基础
|
||
title: shareTitle,
|
||
description: shareDesc,
|
||
|
||
// Open Graph (Facebook / WhatsApp)
|
||
ogTitle: shareTitle,
|
||
ogDescription: shareDesc,
|
||
ogImage: shareImage,
|
||
ogType: "article",
|
||
|
||
// Twitter Card
|
||
twitterCard: "summary_large_image",
|
||
twitterTitle: shareTitle,
|
||
twitterDescription: shareDesc,
|
||
twitterImage: shareImage,
|
||
});
|
||
|
||
// 如果你用了 nuxt-og-image 模块生成动态图
|
||
if (shareImage) {
|
||
defineOgImage({ url: shareImage });
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped></style>
|