refactor(content): migrate index page to Nuxt Content

This commit refactors the index page to source its content from @nuxt/content, replacing the previous implementation that used i18n
JSON files and hardcoded data within the component.

Key changes:
- Introduced `content.config.ts` to define collections and Zod schemas for type-safe content.
- Moved page content into localized YAML files (`content/en-US/index.yml` and `content/zh-CN/index.yml`).
- Updated `app/pages/index.vue` to fetch data dynamically using `useAsyncData` and `queryCollection`.
- Removed redundant content from i18n JSON files and the Vue component script.

This change decouples content from presentation, improves maintainability, and centralizes content management.
This commit is contained in:
xiaomai
2025-11-05 17:54:59 +08:00
parent 87731a6379
commit 78bc2c34a0
7 changed files with 1353 additions and 1293 deletions

View File

@@ -2,8 +2,8 @@
<div>
<!-- 全幅 Hero - Page 布局之外 -->
<UPageHero
title="Tootaio Studio"
:description="$t('index.heroDescription')"
:title="page?.title"
:description="page?.description"
:ui="{
root: 'relative before:absolute before:inset-0 before:bg-[image:var(--bg-image)] before:bg-cover before:bg-center before:-z-10 before:opacity-40',
}"
@@ -13,14 +13,14 @@
/>
<UPageSection
:title="$t('index.capabilities.title')"
:features="capabilitiesFeatures"
:title="page?.capabilities.title"
:features="page?.capabilities.features"
/>
<UPageSection :title="$t('index.featuredProjects.title')">
<UPageSection :title="page?.featuredProjects.title">
<UCarousel
v-slot="{ item }"
:items="featuredProjects"
:items="page?.featuredProjects.projects"
:ui="{ item: 'basis-full sm:basis-1/2 lg:basis-1/3' }"
>
<UCard class="my-2">
@@ -46,7 +46,7 @@
</UCarousel>
</UPageSection>
<UPageSection :title="$t('index.techStack.title')">
<UPageSection :title="page?.techStack.title">
<UMarquee>
<UIcon
v-for="icon in techIcons"
@@ -66,17 +66,57 @@
</UPageSection>
<UPageSection
:title="$t('index.whyChooseUs.title')"
:features="whyChooseUsFeatures"
:title="page?.whyChooseUs.title"
:features="page?.whyChooseUs.features"
/>
</div>
</template>
<script lang="ts" setup>
import type { PageFeatureProps } from "@nuxt/ui";
import type { Collections } from "@nuxt/content";
const { locale } = useI18n();
const { data: page } = await useAsyncData(
"index-" + locale.value,
async () => {
// Build collection name based on current locale
let localeSuffix = "";
switch (locale.value) {
case "en":
localeSuffix = "en";
break;
case "zh-CN":
localeSuffix = "zh";
break;
default:
localeSuffix = "en";
break;
}
const collection = ("index_" + localeSuffix) as keyof Collections;
const content = await queryCollection(collection).first();
// Optional: fallback to default locale if content is missing
if (!content && locale.value !== "en") {
return await queryCollection("index_en").first();
}
return content;
},
{
watch: [locale], // Refetch when locale changes
}
);
if (!page.value) {
throw createError({
statusCode: 404,
statusMessage: `Page not found, the index_${locale.value} couldn't be found.`,
fatal: true,
});
}
useSeoMeta({
title: $t("index.seo.title"),
title: page.value?.seo.title,
});
const colorMode = useColorMode();
@@ -94,7 +134,7 @@ const backgroundImages = [
"http://img.tootaio.com/i/2025/11/05/avf3wl.png",
];
const currentBgImage = ref("");
const currentBgImage = ref<string | undefined>("");
// 随机选择背景
const randomBg = () => {
@@ -107,62 +147,6 @@ onMounted(() => {
randomBg();
});
const capabilitiesFeatures = computed<PageFeatureProps[]>(() => [
{
title: $t("index.capabilities.features[0].title"),
description: $t("index.capabilities.features[0].description"),
icon: "mdi:web",
},
{
title: $t("index.capabilities.features[1].title"),
description: $t("index.capabilities.features[1].description"),
icon: "mdi:cog-outline",
},
{
title: $t("index.capabilities.features[2].title"),
description: $t("index.capabilities.features[2].description"),
icon: "mdi:gamepad-variant-outline",
},
{
title: $t("index.capabilities.features[3].title"),
description: $t("index.capabilities.features[3].description"),
icon: "mdi:monitor-dashboard",
},
{
title: $t("index.capabilities.features[4].title"),
description: $t("index.capabilities.features[4].description"),
icon: "mdi:flask-outline",
},
{
title: $t("index.capabilities.features[5].title"),
description: $t("index.capabilities.features[5].description"),
icon: "mdi:lightbulb-outline",
},
]);
const featuredProjects = ref([
{
title: "永中校友会官方网站",
description:
"永平中学校友会官方网站的设计与开发项目,整体价值 RM28,000由本工作室创办人无偿捐赠予校友会永久使用。",
image: "http://img.tootaio.com/i/2025/11/05/d9kurl.png",
demoLink: "https://yphsalumni.org",
},
{
title: "留华生来华资料汇总",
description:
"2022 年疫情期间,为马来西亚留学生开发的返校攻略网站。帮助 5000+ 名留学生顺利返校。并获得马来西亚外交部推荐。",
image: "http://img.tootaio.com/i/2025/11/05/d9kcma.png",
demoLink: "https://tootaio.github.io",
},
{
title: "光追",
description: "基于 Godot 引擎的 2023 年吉比特高校挑战赛参赛作品。",
image: "https://img.tootaio.com/i/2025/09/26/j2swgq.png",
// demoLink: "https://tootaio.com/projects/ray-tracing",
},
]);
const techIcons = computed(() => [
"skill-icons:html",
"skill-icons:css",
@@ -231,39 +215,6 @@ const toolsIcons = ref([
? "skill-icons:rider-dark"
: "skill-icons:rider-light",
]);
const whyChooseUsFeatures = computed<PageFeatureProps[]>(() => [
{
title: $t("index.whyChooseUs.features[0].title"),
description: $t("index.whyChooseUs.features[0].description"),
icon: "mdi:brush-variant", // Fully Custom-Built
},
{
title: $t("index.whyChooseUs.features[1].title"),
description: $t("index.whyChooseUs.features[1].description"),
icon: "mdi:cog-sync-outline", // Tech-Driven
},
{
title: $t("index.whyChooseUs.features[2].title"),
description: $t("index.whyChooseUs.features[2].description"),
icon: "mdi:gamepad-variant-outline", // Cross-Domain
},
{
title: $t("index.whyChooseUs.features[3].title"),
description: $t("index.whyChooseUs.features[3].description"),
icon: "mdi:rocket-launch-outline", // End-to-End Service
},
{
title: $t("index.whyChooseUs.features[4].title"),
description: $t("index.whyChooseUs.features[4].description"),
icon: "mdi:chart-timeline-variant", // Proven Project Value
},
{
title: $t("index.whyChooseUs.features[5].title"),
description: $t("index.whyChooseUs.features[5].description"),
icon: "mdi:lightbulb-on-outline", // Future-Oriented
},
]);
</script>
<style></style>