能检索数据

This commit is contained in:
doget 2025-09-04 19:06:49 +08:00
parent 955a04d6ee
commit 6c3cdc2470
4 changed files with 81 additions and 15 deletions

View File

@ -118,7 +118,9 @@ const currentLocale = ref(locale.value)
// Get the slug from the route // Get the slug from the route
const slug = route.params.slug const slug = route.params.slug
const articlePath = `/${Array.isArray(slug) ? slug.join('/') : slug}` const base = `/${Array.isArray(slug) ? slug.join('/') : slug}`
// Our list links to /blog/{locale}/{slug}, remove /blog prefix to query content file
const articlePath = base.replace(/^\/blog(?=\/)/, '')
// Fetch the article // Fetch the article
const { data: article, pending, error } = await useAsyncData(`article-${articlePath}`, async () => { const { data: article, pending, error } = await useAsyncData(`article-${articlePath}`, async () => {
@ -140,9 +142,7 @@ useSeoMeta({
twitterCard: 'summary_large_image' twitterCard: 'summary_large_image'
}) })
const availableLocales = computed(() => const availableLocales = computed(() => locales.value)
locales.value.filter(locale => locale.code !== 'en' || locale.code === currentLocale.value)
)
const formatDate = (date) => { const formatDate = (date) => {
return new Date(date).toLocaleDateString('en-US', { return new Date(date).toLocaleDateString('en-US', {
@ -156,9 +156,10 @@ const switchToTranslation = async (targetLocale) => {
if (targetLocale === currentLocale.value) return if (targetLocale === currentLocale.value) return
try { try {
const translated = await queryContent(`/${targetLocale}${articlePath}`).findOne() const base = articlePath.replace(/^\/(en|zh|zh-hant)/,'')
const translated = await queryContent(`/${targetLocale}${base}`).findOne()
if (translated?._path) { if (translated?._path) {
await navigateTo(translated._path) await navigateTo(`/blog${translated._path}`)
return return
} }
throw new Error('Not found') throw new Error('Not found')

View File

@ -47,7 +47,7 @@
</div> </div>
<h2 class="text-xl font-bold mb-3 group-hover:text-aws-orange transition-colors duration-200"> <h2 class="text-xl font-bold mb-3 group-hover:text-aws-orange transition-colors duration-200">
<NuxtLink :to="article._path"> <NuxtLink :to="articleLink(article)">
{{ article.title }} {{ article.title }}
</NuxtLink> </NuxtLink>
</h2> </h2>
@ -58,7 +58,7 @@
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<NuxtLink <NuxtLink
:to="article._path" :to="articleLink(article)"
class="text-aws-orange hover:underline font-medium" class="text-aws-orange hover:underline font-medium"
> >
{{ $t('blog.readMore') }} {{ $t('blog.readMore') }}
@ -107,11 +107,11 @@ useSeoMeta({
const { locales } = useI18n() const { locales } = useI18n()
const currentLocale = ref(locale.value) const currentLocale = ref(locale.value)
// Fetch blog articles // Fetch blog articles (filter by current _locale provided by @nuxt/content)
const { data: articles, pending, error } = await useAsyncData('blog-articles', async () => { const { data: articles, pending, error } = await useAsyncData(() => `blog-${locale.value}`, async () => {
try { try {
return await queryContent(`/${locale.value}`) return await queryContent()
.where({ _type: 'markdown' }) .where({ _locale: locale.value })
.sort({ date: -1 }) .sort({ date: -1 })
.find() .find()
} catch (err) { } catch (err) {
@ -119,6 +119,15 @@ const { data: articles, pending, error } = await useAsyncData('blog-articles', a
return [] return []
} }
}) })
// Build localized link for article
const articleLink = (a) => {
if (!a || !a._path) return '/blog'
const hasLocale = /^\/(en|zh|zh-hant)(\/|$)/.test(a._path)
const localized = hasLocale
? a._path
: (locale.value === 'en' ? a._path : `/${locale.value}${a._path}`)
return `/blog${localized}`
}
const availableLocales = computed(() => const availableLocales = computed(() =>
locales.value.filter(locale => locale.code !== 'en' || locale.code === currentLocale.value) locales.value.filter(locale => locale.code !== 'en' || locale.code === currentLocale.value)
@ -136,9 +145,10 @@ const switchToTranslation = async (article, targetLocale) => {
if (targetLocale === currentLocale.value) return if (targetLocale === currentLocale.value) return
try { try {
const translated = await queryContent(`/${targetLocale}${article._path}`).findOne() const basePath = article._path.replace(/^\/(en|zh|zh-hant)/, '')
const translated = await queryContent(`/${targetLocale}${basePath}`).findOne()
if (translated?._path) { if (translated?._path) {
await navigateTo(translated._path) await navigateTo(`/blog${translated._path}`)
return return
} }
throw new Error('Not found') throw new Error('Not found')

View File

@ -209,11 +209,18 @@ const submitForm = async () => {
throw new Error('invalid_email') throw new Error('invalid_email')
} }
const fd = new FormData() const fd = new FormData()
//
fd.append('🧑 姓名', form.value.name)
fd.append('✉️ 邮箱', form.value.email)
fd.append('🧩 主题', form.value.subject)
fd.append('💬 消息', form.value.message)
// 便
fd.append('name', form.value.name) fd.append('name', form.value.name)
fd.append('email', form.value.email) fd.append('email', form.value.email)
fd.append('subject', form.value.subject) fd.append('subject', form.value.subject)
fd.append('message', form.value.message) fd.append('message', form.value.message)
fd.append('_subject', `Website Contact: ${form.value.subject}`) // //
fd.append('_subject', `📨 ${t('contact.title')} | ${form.value.subject}`)
fd.append('_language', locale.value) fd.append('_language', locale.value)
fd.append('_replyto', form.value.email) fd.append('_replyto', form.value.email)

View File

@ -46,6 +46,33 @@
</div> </div>
</section> </section>
<!-- Latest Articles Section -->
<section class="section-padding bg-gray-50" v-if="(articles && articles.length) || pending">
<div class="container-custom">
<div class="text-center mb-12">
<h2 class="text-4xl font-bold text-gray-900 mb-4">最新文章</h2>
<p class="text-xl text-gray-600">为你精选的最新内容</p>
</div>
<div v-if="pending" class="text-center text-gray-500">加载中...</div>
<div v-else class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
<NuxtLink
v-for="a in articles"
:key="a._path"
:to="articleLink(a)"
class="card hover:shadow-aws transition-shadow duration-300 block"
>
<div class="mb-4">
<h3 class="text-xl font-semibold text-gray-900 line-clamp-2">{{ a.title || 'Untitled' }}</h3>
</div>
<p class="text-gray-600 line-clamp-3 mb-4">{{ a.description || '' }}</p>
<div class="text-sm text-gray-500">{{ a.date ? new Date(a.date).toLocaleDateString() : '' }}</div>
</NuxtLink>
</div>
</div>
</section>
<!-- Pricing Section --> <!-- Pricing Section -->
<section class="section-padding bg-gray-50"> <section class="section-padding bg-gray-50">
<div class="container-custom"> <div class="container-custom">
@ -129,6 +156,27 @@ useSeoMeta({
description: () => t('seo.home.description') description: () => t('seo.home.description')
}) })
// Latest articles (from @nuxt/content) filtered by _locale
const { data: articles, pending } = await useAsyncData(() => `home-articles-${locale.value}`,
async () => {
try {
return await queryContent()
.where({ _locale: locale.value })
.sort({ date: -1 })
.limit(3)
.find()
} catch (err) {
console.error('Error fetching home articles:', err)
return []
}
}
)
const articleLink = (a) => {
if (!a || !a._path) return '/blog'
return `/blog${a._path}`
}
// Services data // Services data
const services = computed(() => [ const services = computed(() => [
{ {