refactor(ui): adopt Nuxt UI components for homepage and layout
This commit refactors the main layout and homepage to utilize components from the Nuxt UI library, simplifying the codebase and reducing custom boilerplate. - The default layout is now built with `UPage`, `UHeader`, `UMain`, and `UFooter`. - The homepage (`pages/index.vue`) has been updated to use `UPageHero` and `UBlogPosts`. - The `IndexHero` and `IndexNews` components have been removed as their functionality is now integrated directly into the index page. - `Donate`, `Events`, and `HallOfFame` components are now wrapped with `UPageCTA` or `UPageSection`.
This commit is contained in:
@@ -1,16 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 捐赠模块 -->
|
||||
<section id="donate" class="py-16 text-center bg-secondary">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-4">支持与捐赠(功能未开放)</h3>
|
||||
<p class="max-w-2xl mx-auto text-gray-700 mb-6">您的捐赠将用于奖学金、校园建设及校友活动发展。感谢您对母校的支持!</p>
|
||||
<a href="#" class="bg-primary text-white px-8 py-3 rounded-xl shadow hover:opacity-90">立即捐赠</a>
|
||||
</section>
|
||||
</div>
|
||||
<UPageCTA
|
||||
class="bg-secondary"
|
||||
title="支持与捐赠"
|
||||
description="您的捐赠将用于奖学金、校园建设及校友活动发展。感谢您对母校的支持!"
|
||||
:links="donationLinks"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
const donationLinks = ref([{ label: "立即捐赠", icon: "mdi:cash" }]);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 活动模块 -->
|
||||
<section id="events" class="bg-gray-100 py-16">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-6">校友活动</h3>
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<div v-for="event in events" :key="event.id" class="bg-white shadow rounded-xl">
|
||||
<img :src="event.cover" :alt="event.title" class="rounded-xl" />
|
||||
<div class="p-6">
|
||||
<h4 class="font-semibold text-lg mb-2">{{ event.title }}</h4>
|
||||
<p class="text-sm text-gray-600 mb-1">日期:{{ useChineseDateFormat(event.date) }}</p>
|
||||
<p class="text-sm text-gray-600 mb-4">地点:{{ event.location }}</p>
|
||||
<a :href="event.path" class="bg-primary text-white px-5 py-2 rounded-lg hover:opacity-90">阅读详情</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 活动模块 -->
|
||||
<UPageSection title="校友活动" class="bg-gray-100">
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<div
|
||||
v-for="event in events"
|
||||
:key="event.id"
|
||||
class="bg-white shadow rounded-xl"
|
||||
>
|
||||
<img :src="event.cover" :alt="event.title" class="rounded-xl" />
|
||||
<div class="p-6">
|
||||
<h4 class="font-semibold text-lg mb-2">{{ event.title }}</h4>
|
||||
<p class="text-sm text-gray-600 mb-1">
|
||||
日期:{{ useChineseDateFormat(event.date) }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-600 mb-4">地点:{{ event.location }}</p>
|
||||
<!-- <a
|
||||
:href="event.path"
|
||||
class="bg-primary text-white px-5 py-2 rounded-lg hover:opacity-90"
|
||||
>阅读详情</a
|
||||
> -->
|
||||
<UButton label="阅读详情" :to="event.path" trailing-icon="mdi:glasses"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</UPageSection>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { data: events } = await useAsyncData('events', () =>
|
||||
queryCollection('events')
|
||||
.order("date", "DESC")
|
||||
.limit(3)
|
||||
.all()
|
||||
)
|
||||
const { data: events } = await useAsyncData("events", () =>
|
||||
queryCollection("events").order("date", "DESC").limit(3).all()
|
||||
);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<section id="hall-of-fame" class="py-16">
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<h3 class="text-2xl font-bold text-center text-gray-900 mb-6">名人堂</h3>
|
||||
<div class="grid md:grid-cols-4 gap-6">
|
||||
<div v-for="person in persons" :key="person.id" class="flex flex-col items-center cursor-pointer transition hover:scale-105 hover:drop-shadow-2xl hover:-translate-y-1" @click="jumpToPersonIntro(person.path)">
|
||||
<img :src="person.photo" :alt="person.name" class="w-40 rounded-full border-primary border-4" />
|
||||
<h4 class="text-lg font-bold">{{ person.name }}</h4>
|
||||
<p class="text-sm text-gray-500">{{ person.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<UPageSection title="名人堂">
|
||||
<div class="grid md:grid-cols-4 gap-6">
|
||||
<div
|
||||
v-for="person in persons"
|
||||
:key="person.id"
|
||||
class="flex flex-col items-center cursor-pointer transition hover:scale-105 hover:drop-shadow-2xl hover:-translate-y-1"
|
||||
@click="jumpToPersonIntro(person.path)"
|
||||
>
|
||||
<img
|
||||
:src="person.photo"
|
||||
:alt="person.name"
|
||||
class="w-40 rounded-full border-primary border-4"
|
||||
/>
|
||||
<h4 class="text-lg font-bold">{{ person.name }}</h4>
|
||||
<p class="text-sm text-gray-500">{{ person.title }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</UPageSection>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { data: persons } = await useAsyncData('hall-of-fames', () =>
|
||||
queryCollection('hallOfFames')
|
||||
.limit(4)
|
||||
.all()
|
||||
)
|
||||
const { data: persons } = await useAsyncData("hall-of-fames", () =>
|
||||
queryCollection("hallOfFames").limit(4).all()
|
||||
);
|
||||
|
||||
var router = useRouter()
|
||||
var router = useRouter();
|
||||
const jumpToPersonIntro = (path: string) => {
|
||||
router.push(path)
|
||||
}
|
||||
router.push(path);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style></style>
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Hero Banner -->
|
||||
<section class="relative bg-secondary py-32 md:py-48 lg:py-64 text-center bg-cover bg-center"
|
||||
style="background-image: url('/hero-image.jpg');">
|
||||
<!-- 遮罩 -->
|
||||
<div class="absolute inset-0 bg-black/40"></div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div class="relative z-10 max-w-3xl mx-auto px-4">
|
||||
<h2 class="text-3xl md:text-5xl font-extrabold text-white drop-shadow-lg">
|
||||
连接校友 · 传承精神
|
||||
</h2>
|
||||
<p class="mt-4 text-lg text-gray-100">
|
||||
马来西亚柔佛永平中学校友会官方网站
|
||||
</p>
|
||||
<div class="mt-6 space-x-4">
|
||||
<a href="/join-us" class="bg-primary text-white px-6 py-3 rounded-xl shadow hover:opacity-90">
|
||||
立即加入我们
|
||||
</a>
|
||||
<!-- <a href="#donate"
|
||||
class="bg-white border-2 border-secondary text-secondary px-6 py-3 rounded-xl hover:bg-secondary hover:text-white">
|
||||
支持捐赠
|
||||
</a> -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,37 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 新闻公告 -->
|
||||
<section id="news" class="max-w-6xl mx-auto py-16 px-4">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">最新新闻与公告</h2>
|
||||
<div class="grid md:grid-cols-3 gap-6">
|
||||
<article v-for="n in news" :key="n.id" @click="jumpToNewsDetail(n.stem)"
|
||||
class="bg-secondary/10 rounded-xl shadow cursor-pointer transition transform hover:-translate-y-1 hover:scale-105 hover:shadow-xl duration-300 ease-in-out">
|
||||
<img class="rounded-xl" :src="n.cover" :alt="n.title">
|
||||
<div class="p-5">
|
||||
<h3 class="font-semibold mb-2">{{ n.title }}</h3>
|
||||
<div class="px-1 w-max bg-secondary/25 border-secondary border-2 rounded-xl text-primary text-sm mb-2">{{
|
||||
useChineseDateFormat(n.date) }}</div>
|
||||
<p class="text-sm text-gray-600">{{ n.description }}</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { data: news } = await useAsyncData('news', () =>
|
||||
queryCollection('news')
|
||||
.order("date", "DESC")
|
||||
.limit(3)
|
||||
.all()
|
||||
)
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const jumpToNewsDetail = (newsPath: string) => {
|
||||
router.push(newsPath);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,62 +1,74 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<!-- 导航栏 -->
|
||||
<header class="bg-white shadow-md sticky top-0 z-50">
|
||||
<div class="max-w-6xl mx-auto px-4 py-3 flex justify-between items-center">
|
||||
<div>
|
||||
<img class="inline w-16" src="/Logo.svg" alt="YPHS Alumni" />
|
||||
<h1 class="inline text-xl font-bold text-gray-900">
|
||||
<a href="/" class="ml-4 hover:text-primary">永平中学校友会</a>
|
||||
</h1>
|
||||
</div>
|
||||
<UPage>
|
||||
<UHeader>
|
||||
<template #left>
|
||||
<img class="inline h-12 w-auto" src="/Logo.svg" alt="YPHS Alumni" />
|
||||
<h1 class="inline text-xl font-bold text-gray-900">
|
||||
<a href="/" class="ml-4 hover:text-primary">永平中学校友会</a>
|
||||
</h1>
|
||||
</template>
|
||||
<template #right>
|
||||
<nav class="space-x-6 hidden md:flex items-center">
|
||||
<a href="#news" class="hover:text-primary">新闻</a>
|
||||
<a href="#events" class="hover:text-primary">活动</a>
|
||||
<a href="#donate" class="hover:text-primary">捐赠(未开放)</a>
|
||||
<a href="#about" class="hover:text-primary">关于</a>
|
||||
<a href="/join-us"
|
||||
class="inline-flex items-center gap-2 bg-primary text-white px-4 py-2 rounded-xl shadow hover:opacity-90">
|
||||
<UNavigationMenu :items="items" />
|
||||
<a
|
||||
href="/join-us"
|
||||
class="inline-flex items-center gap-2 bg-primary text-white px-4 py-2 rounded-xl shadow hover:opacity-90"
|
||||
>
|
||||
加入
|
||||
<Icon name="mdi:account-plus" class="w-5 h-5" />
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 主体部分 -->
|
||||
<main class="flex-1">
|
||||
</template>
|
||||
</UHeader>
|
||||
<UMain>
|
||||
<slot />
|
||||
</main>
|
||||
</UMain>
|
||||
<UFooter>
|
||||
<template #left>
|
||||
<p>© 2025 永平中学校友会. 保留所有权利.</p>
|
||||
</template>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-gray-900 text-gray-300 py-6">
|
||||
<div class="max-w-6xl mx-auto px-4 flex flex-col md:flex-row justify-between items-center">
|
||||
<div class="text-center md:text-left">
|
||||
<p>© 2025 永平中学校友会. 保留所有权利.</p>
|
||||
</div>
|
||||
<template #default>
|
||||
<p class="mt-1">
|
||||
Powered by:
|
||||
<a
|
||||
href="https://tootaio.com"
|
||||
target="_blank"
|
||||
class="font-semibold hover:underline"
|
||||
style="color: #e24545"
|
||||
>
|
||||
Tootaio Studio
|
||||
</a>
|
||||
<span class="mt-1 text-sm"
|
||||
>2018 级毕业学长(麦祖奕)</span
|
||||
>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<div>
|
||||
<p class="mt-1">
|
||||
Powered by:
|
||||
<a href="https://tootaio.com" target="_blank" class="font-semibold hover:underline" style="color: #e24545;">
|
||||
Tootaio Studio
|
||||
</a>
|
||||
<span class="mt-1 text-sm text-gray-400">2018 级毕业学长(麦祖奕)</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-4 mt-3 md:mt-0">
|
||||
<a href="#">
|
||||
<Icon name="mdi-facebook" />
|
||||
</a>
|
||||
<a href="#">
|
||||
<Icon name="mdi-instagram" />
|
||||
</a>
|
||||
<a href="#">
|
||||
<Icon name="mdi-gmail" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<template #right>
|
||||
<a href="#">
|
||||
<Icon name="mdi-facebook" />
|
||||
</a>
|
||||
<a href="#">
|
||||
<Icon name="mdi-instagram" />
|
||||
</a>
|
||||
<a href="#">
|
||||
<Icon name="mdi-gmail" />
|
||||
</a>
|
||||
</template>
|
||||
</UFooter>
|
||||
</UPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { NavigationMenuItem } from "@nuxt/ui";
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const items = computed<NavigationMenuItem[]>(() => [
|
||||
{ label: "新闻", to: "#news", active: route.path.startsWith("#news") },
|
||||
{ label: "活动", to: "#events", active: route.path.startsWith("#events") },
|
||||
{ label: "捐赠", to: "#donate", active: route.path.startsWith("#donate") },
|
||||
{ label: "关于", to: "#about", active: route.path.startsWith("#about") },
|
||||
]);
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<IndexHero />
|
||||
<IndexNews />
|
||||
<!-- Hero Banner -->
|
||||
<UPageHero
|
||||
class="bg-cover bg-center"
|
||||
style="
|
||||
background-image: url("/hero-image.jpg");
|
||||
background-color: rgba(255, 255, 255, 0.5); /* Semi-transparent black */
|
||||
background-blend-mode: lighten;
|
||||
"
|
||||
title="连接校友 · 传承精神"
|
||||
description="马来西亚柔佛永平中学校友会官方网站"
|
||||
:links="heroCta"
|
||||
/>
|
||||
|
||||
<!-- News Blog Posts -->
|
||||
<UPageSection title="新闻与公告">
|
||||
<UBlogPosts :posts="newsPost" />
|
||||
</UPageSection>
|
||||
|
||||
<IndexEvents />
|
||||
<IndexHallOfFame />
|
||||
<IndexDonate />
|
||||
@@ -10,7 +26,41 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { BlogPostProps, PageCardProps } from "@nuxt/ui";
|
||||
|
||||
const heroCta = ref([
|
||||
{
|
||||
label: "立即加入我们",
|
||||
to: "/join-us",
|
||||
icon: "mdi:account-plus",
|
||||
},
|
||||
]);
|
||||
|
||||
// ========================================================
|
||||
// 新闻模块
|
||||
// ========================================================
|
||||
const { data: news } = await useAsyncData("news", () =>
|
||||
queryCollection("news").order("date", "DESC").limit(3).all()
|
||||
);
|
||||
|
||||
// 将 news 数据转换成 UBlogPosts 可用格式
|
||||
const newsPost = computed<BlogPostProps[]>(() =>
|
||||
(news.value || []).map((n: any) => ({
|
||||
title: n.title,
|
||||
description: n.description,
|
||||
image: n.cover,
|
||||
date: n.date,
|
||||
to: n.path, // ✅ 建议加路由跳转
|
||||
variant: "subtle",
|
||||
}))
|
||||
);
|
||||
|
||||
// ========================================================
|
||||
// 活动模块
|
||||
// ========================================================
|
||||
const { data: events } = await useAsyncData("events", () =>
|
||||
queryCollection("events").order("date", "DESC").limit(3).all()
|
||||
);
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
<style></style>
|
||||
|
||||
Reference in New Issue
Block a user