Replaced the static navigation with a dynamic, internationalized dropdown menu powered by a new `useNavLinks` composable. The navigation items are now sourced from i18n files. The featured project cards on the homepage have been refactored to use the `<UPageCard>` component, and the content schema is updated with `spotlight` and `highlight` options for enhanced display.
219 lines
5.8 KiB
Vue
219 lines
5.8 KiB
Vue
<template>
|
|
<div>
|
|
<!-- 全幅 Hero - 在 Page 布局之外 -->
|
|
<UPageHero
|
|
: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',
|
|
}"
|
|
:style="{
|
|
'--bg-image': `url('${currentBgImage}')`,
|
|
}"
|
|
/>
|
|
|
|
<UPageSection
|
|
:title="page?.capabilities.title"
|
|
:features="page?.capabilities.features"
|
|
/>
|
|
|
|
<UPageSection :title="page?.featuredProjects.title">
|
|
<UCarousel
|
|
v-slot="{ item }"
|
|
:items="page?.featuredProjects.projects"
|
|
:ui="{ item: 'basis-full sm:basis-1/2 lg:basis-1/3' }"
|
|
>
|
|
<UPageCard
|
|
class="my-2"
|
|
:title="item.title"
|
|
:description="item.description"
|
|
:highlight="item.highlight"
|
|
:spotlight="item.spotlight"
|
|
>
|
|
<img :src="item.image" :alt="item.title" />
|
|
<UButton
|
|
v-if="item.demoLink"
|
|
:href="item.demoLink"
|
|
target="_blank"
|
|
rel="noopener"
|
|
size="sm"
|
|
>
|
|
{{ $t("index.featuredProjects.viewDemo") }}
|
|
</UButton>
|
|
</UPageCard>
|
|
</UCarousel>
|
|
</UPageSection>
|
|
|
|
<UPageSection :title="page?.techStack.title">
|
|
<UMarquee>
|
|
<UIcon
|
|
v-for="icon in techIcons"
|
|
:key="icon"
|
|
:name="icon"
|
|
class="size-16"
|
|
/>
|
|
</UMarquee>
|
|
<UMarquee reverse>
|
|
<UIcon
|
|
v-for="icon in toolsIcons"
|
|
:key="icon"
|
|
:name="icon"
|
|
class="size-16"
|
|
/>
|
|
</UMarquee>
|
|
</UPageSection>
|
|
|
|
<UPageSection
|
|
:title="page?.whyChooseUs.title"
|
|
:features="page?.whyChooseUs.features"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
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: page.value?.seo.title,
|
|
});
|
|
|
|
const colorMode = useColorMode();
|
|
|
|
const backgroundImages = [
|
|
"http://img.tootaio.com/i/2025/11/05/avc5ld.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avcaff.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avcjbw.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avcp16.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avcv1q.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avd47a.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avdx6a.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avegxy.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avemgn.png",
|
|
"http://img.tootaio.com/i/2025/11/05/avf3wl.png",
|
|
];
|
|
|
|
const currentBgImage = ref<string | undefined>("");
|
|
|
|
// 随机选择背景
|
|
const randomBg = () => {
|
|
const randomIndex = Math.floor(Math.random() * backgroundImages.length);
|
|
currentBgImage.value = backgroundImages[randomIndex];
|
|
};
|
|
|
|
// 轮播背景
|
|
onMounted(() => {
|
|
randomBg();
|
|
});
|
|
|
|
const techIcons = computed(() => [
|
|
"skill-icons:html",
|
|
"skill-icons:css",
|
|
"skill-icons:javascript",
|
|
"skill-icons:typescript",
|
|
"skill-icons:docker",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:vuejs-dark"
|
|
: "skill-icons:vuejs-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:nuxtjs-dark"
|
|
: "skill-icons:nuxtjs-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:tailwindcss-dark"
|
|
: "skill-icons:tailwindcss-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:nodejs-dark"
|
|
: "skill-icons:nodejs-light",
|
|
"skill-icons:cs",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:python-dark"
|
|
: "skill-icons:python-light",
|
|
]);
|
|
|
|
const toolsIcons = ref([
|
|
"skill-icons:photoshop",
|
|
"skill-icons:illustrator",
|
|
"skill-icons:git",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:vscode-dark"
|
|
: "skill-icons:vscode-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:visualstudio-dark"
|
|
: "skill-icons:visualstudio-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:github-dark"
|
|
: "skill-icons:github-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:godot-dark"
|
|
: "skill-icons:godot-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:unity-dark"
|
|
: "skill-icons:unity-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:blender-dark"
|
|
: "skill-icons:blender-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:androidstudio-dark"
|
|
: "skill-icons:androidstudio-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:windows-dark"
|
|
: "skill-icons:windows-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:linux-dark"
|
|
: "skill-icons:linux-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:apple-dark"
|
|
: "skill-icons:apple-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:idea-dark"
|
|
: "skill-icons:idea-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:pycharm-dark"
|
|
: "skill-icons:pycharm-light",
|
|
colorMode.value === "dark"
|
|
? "skill-icons:rider-dark"
|
|
: "skill-icons:rider-light",
|
|
]);
|
|
</script>
|
|
|
|
<style></style>
|