114 lines
3.3 KiB
Vue
114 lines
3.3 KiB
Vue
<template>
|
|
<div class="max-w-4xl mx-auto px-4 py-12">
|
|
<div v-if="!article" class="text-center py-20">
|
|
<h1 class="text-3xl font-bold mb-4">{{ $t('errors.missingTranslation.title') }}</h1>
|
|
<p class="text-gray-600 mb-8">{{ $t('errors.missingTranslation.message') }}</p>
|
|
<div class="mb-8">
|
|
<p class="font-semibold mb-4">{{ $t('errors.missingTranslation.availableLanguages') }}</p>
|
|
<div class="flex justify-center space-x-4">
|
|
<NuxtLink
|
|
v-for="lang in availableLanguages"
|
|
:key="lang"
|
|
:to="getLanguagePath(lang)"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
>
|
|
{{ lang.toUpperCase() }}
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
<NuxtLink :to="localePath('/blog')" class="text-blue-600 hover:text-blue-800">
|
|
← Back to Blog
|
|
</NuxtLink>
|
|
</div>
|
|
|
|
<article v-else>
|
|
<header class="mb-8">
|
|
<h1 class="text-4xl font-bold text-gray-900 mb-4">{{ article.title }}</h1>
|
|
<div class="flex items-center justify-between mb-6">
|
|
<time class="text-gray-600">{{ formatDate(article.date) }}</time>
|
|
<div class="flex space-x-2">
|
|
<span
|
|
v-for="tag in article.tags"
|
|
:key="tag"
|
|
class="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm"
|
|
>
|
|
{{ tag }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="prose prose-lg max-w-none">
|
|
<ContentRenderer :value="article" />
|
|
</div>
|
|
|
|
<!-- 语言切换 -->
|
|
<div class="mt-12 pt-8 border-t border-gray-200">
|
|
<h3 class="text-lg font-semibold mb-4">Available Languages:</h3>
|
|
<div class="flex space-x-4">
|
|
<NuxtLink
|
|
v-for="lang in availableLanguages"
|
|
:key="lang"
|
|
:to="getLanguagePath(lang)"
|
|
class="px-4 py-2 border rounded hover:bg-gray-50"
|
|
:class="{ 'bg-blue-600 text-white': lang === locale }"
|
|
>
|
|
{{ getLanguageName(lang) }}
|
|
</NuxtLink>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
const { locale } = useI18n()
|
|
const route = useRoute()
|
|
const localePath = useLocalePath()
|
|
|
|
// 尝试获取当前语言的文章
|
|
const { data: article } = await useAsyncData(`blog-${route.params.slug}`, () =>
|
|
queryContent(`/${locale.value}`)
|
|
.where({ slug: route.params.slug })
|
|
.findOne()
|
|
.catch(() => null)
|
|
)
|
|
|
|
// 获取所有可用语言
|
|
const availableLanguages = ['en', 'zh', 'zh-hant']
|
|
|
|
const formatDate = (date) => {
|
|
return new Date(date).toLocaleDateString(locale.value, {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric'
|
|
})
|
|
}
|
|
|
|
const getLanguageName = (lang) => {
|
|
const names = {
|
|
'en': 'English',
|
|
'zh': '简体中文',
|
|
'zh-hant': '繁體中文'
|
|
}
|
|
return names[lang] || lang
|
|
}
|
|
|
|
const getLanguagePath = (lang) => {
|
|
// 英文是默认语言,不需要添加语言路径
|
|
if (lang === 'en') {
|
|
return `/blog/${route.params.slug}`
|
|
}
|
|
return `/${lang}/blog/${route.params.slug}`
|
|
}
|
|
|
|
// SEO设置
|
|
if (article.value) {
|
|
useSeoMeta({
|
|
title: article.value.title,
|
|
description: article.value.description,
|
|
ogTitle: article.value.title,
|
|
ogDescription: article.value.description
|
|
})
|
|
}
|
|
</script> |