feat(about): add team member profile page

This commit introduces a new section to showcase team members, starting with the founder's profile.

- Adds a dynamic page route `/about/` to display individual member profiles.
- Creates a new `about` content collection to source profile information from Markdown files.
- Adds the first profile for 'Xiaomai', including a detailed resume and background image.
- Integrates a 'Teams' dropdown into the main navigation header.
- Implements a `copyToClipboard` utility with a toast notification for sharing profile links.
This commit is contained in:
2025-11-08 13:40:23 +08:00
parent fb67355a15
commit f9e02372b2
10 changed files with 254 additions and 61 deletions

View File

@@ -80,6 +80,14 @@ export const useNavLinks = () => {
},
],
},
{label: t("common.header.teams.label"),
icon: "mdi:account-group-outline",
children: [{
label: t("common.header.teams.children.xiaomai.label"),
description: t("common.header.teams.children.xiaomai.description"),
to: '/about/xiaomai'
}]
}
]);
return navLinks;

View File

@@ -0,0 +1,49 @@
<script lang="ts" setup>
const route = useRoute();
const articleLink = computed(() => `${window?.location}`);
const { data: page } = await useAsyncData(route.path, () =>
queryCollection("about").path(`/about/${route.params.slug}`).first()
);
</script>
<template>
<UPage>
<div
class="fixed top-0 left-0 -z-999 w-screen h-screen bg-[url('/images/xiaomai.png')] bg-cover lg:bg-contain bg-no-repeat opacity-40 animate-slide-in"
></div>
<UPageBody class="max-w-3xl mx-auto">
<ContentRenderer v-if="page?.body" :value="page" />
<div class="flex items-center justify-end gap-2 text-sm text-muted">
<UButton
size="sm"
variant="link"
color="neutral"
label="Copy link"
@click="
copyToClipboard(articleLink, 'Article link copied to clipboard')
"
/>
</div>
</UPageBody>
</UPage>
</template>
<style scoped>
@keyframes slide-in {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 0.4;
}
}
.animate-slide-in {
animation: slide-in 1.2s ease-out forwards;
}
</style>

View File

@@ -71,14 +71,14 @@
</template>
<script lang="ts" setup>
const { data: page } = await useLocalizedCollection("index");
const { data: page } = await useLocalizedCollection("index", {
throwOnMissing: false,
});
useSeoMeta({
title: page.value?.seo.title,
});
const colorMode = useColorMode();
const backgroundImages = [
"https://img.tootaio.com/i/2025/11/05/avc5ld.png",
"https://img.tootaio.com/i/2025/11/05/avcaff.png",
@@ -111,67 +111,31 @@ const techIcons = computed(() => [
"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:vuejs-light",
"skill-icons:nuxtjs-light",
"skill-icons:tailwindcss-light",
"skill-icons:nodejs-light",
"skill-icons:cs",
colorMode.value === "dark"
? "skill-icons:python-dark"
: "skill-icons:python-light",
"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",
"skill-icons:vscode-light",
"skill-icons:visualstudio-light",
"skill-icons:github-light",
"skill-icons:godot-light",
"skill-icons:unity-light",
"skill-icons:blender-light",
"skill-icons:androidstudio-light",
"skill-icons:windows-light",
"skill-icons:linux-light",
"skill-icons:apple-light",
"skill-icons:idea-light",
"skill-icons:pycharm-light",
"skill-icons:rider-light",
]);
</script>

13
app/utils/clipboard.ts Normal file
View File

@@ -0,0 +1,13 @@
export function copyToClipboard(
toCopy: string,
message: string = "Copied to clipboard"
) {
const toast = useToast();
navigator.clipboard.writeText(toCopy).then(() => {
toast.add({
title: message,
color: "success",
icon: "i-lucide-check-circle",
});
});
}