Compare commits

...

6 Commits

Author SHA1 Message Date
xiaomai
c3e05d790c feat(content): add founding history page for the alumni association
This commit introduces a new page detailing the founding history of the alumni association.

- Adds the new page at `/about/founded-history` which includes the origin story, organizational structure, and a list of donors for the clubhouse.
- Restructures the `/about` route by moving the existing page to `/about/index` to accommodate nested pages.
- Updates the main navigation menu to include a dropdown link to the new 'Founding History' page.
- Adds a link to the old alumni website under 'Friendly Links' for historical reference.
2025-10-27 20:50:59 +08:00
xiaomai
0a46c3e591 refactor(join-us): adopt UPage layout components
The 'Join Us' page layout has been refactored to use the `<UPage>` and `<UPageBody>` components from Nuxt UI. This change ensures a consistent page structure with the rest of the application. Additionally,
quote styles in the script section have been standardized to double quotes.
2025-10-26 23:44:04 +08:00
xiaomai
567c9ef9c9 feat(app): restructure to multi-page layout and add content pages
Converts the website from a single-page design with anchor links to a full multi-page application. This change improves site organization, navigation, and scalability.

- Adds new top-level pages: `/news`, `/events`, and `/about`.
- Introduces a new section for the 40th Anniversary at `/40th-anniversary`, including a proposal sub-page.
- Updates the default layout with a new navigation menu, a promotional banner, social links, and page transitions.
2025-10-26 22:18:29 +08:00
xiaomai
a864ffd9cf chore(deps): bump nuxt from 4.1.3 to 4.2.0
This commit updates the Nuxt framework dependency to version 4.2.0 to incorporate the latest features and bug fixes.
2025-10-25 15:11:45 +08:00
xiaomai
7bcabb0c71 feat(ui): implement responsive header and update content
Adds a responsive header with a mobile navigation menu to improve usability on small screens. This also includes adding a new event page, updating an existing event with a schedule, and refactoring the 'Donate' CTA by
inlining it on the homepage.
2025-10-23 14:12:52 +08:00
xiaomai
6473bdcc15 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`.
2025-10-23 09:16:59 +08:00
28 changed files with 2254 additions and 1123 deletions

View File

@@ -1,17 +0,0 @@
<template>
<div>
<!-- 关于我们 -->
<section id="about" class="max-w-6xl mx-auto py-16 px-4">
<h3 class="text-2xl font-bold text-gray-900 mb-4">关于校友会</h3>
<p class="text-gray-700 leading-relaxed">
永平中学校友会成立的宗旨是连接全球校友传承母校精神支持在校学生成长我们定期举办活动推动交流与合作并积极回馈母校
</p>
</section>
</div>
</template>
<script lang="ts" setup>
</script>
<style></style>

View File

@@ -1,16 +0,0 @@
<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>
</template>
<script lang="ts" setup>
</script>
<style></style>

View File

@@ -1,32 +1,38 @@
<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">
<UPageGrid>
<div
v-for="event in events"
:key="event.id"
class="bg-white shadow rounded-xl"
>
<img
:src="event.cover"
:alt="event.title"
class="w-full aspect-[16/9] object-cover rounded-t-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>
<UButton
label="阅读详情"
:to="event.path"
trailing-icon="mdi:book-open-blank-variant-outline"
/>
</div>
</div>
</section>
</div>
</UPageGrid>
</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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,62 +1,194 @@
<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>
<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">
加入
<Icon name="mdi:account-plus" class="w-5 h-5" />
</a>
</nav>
</div>
</header>
<UPage>
<UBanner
title="永中校友会 40 周年庆典活动火热筹募中!"
icon="hugeicons:party"
close
:actions="bannerActions"
/>
<UHeader>
<template #left>
<NuxtLink to="/">
<div class="flex gap-4 items-center">
<img class="inline h-12 w-auto" src="/Logo.svg" alt="YPHS Alumni" />
<h1
class="text-xl font-bold text-gray-900 hover:text-primary hidden md:inline"
>
永平中学校友会
</h1>
</div>
</NuxtLink>
</template>
<!-- 主体部分 -->
<main class="flex-1">
<slot />
</main>
<template #default>
<UNavigationMenu :items="items" content-orientation="vertical" />
</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 #right>
<UColorModeButton />
<UButton
v-for="link in socialLinks"
color="neutral"
variant="ghost"
v-bind="link"
/>
<UButton
icon="line-md:account-add"
to="/join-us"
color="primary"
variant="solid"
label="加入"
/>
</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>
<template #body>
<UNavigationMenu
:items="items"
orientation="vertical"
class="-mx-2.5"
/>
</template>
</UHeader>
<div class="flex space-x-4 mt-3 md:mt-0">
<a href="#">
<Icon name="mdi-facebook" />
<UMain>
<Transition name="page" mode="out-in">
<NuxtPage />
</Transition>
</UMain>
<UFooter>
<template #left>
<p>© 2025 永平中学校友会. 保留所有权利.</p>
</template>
<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>
<a href="#">
<Icon name="mdi-instagram" />
</a>
<a href="#">
<Icon name="mdi-gmail" />
</a>
</div>
</div>
</footer>
</div>
<span class="mt-1 text-sm">2018 级毕业学长麦祖奕</span>
</p>
</template>
<template #right>
<UButton
v-for="link in socialLinks"
color="neutral"
variant="ghost"
v-bind="link"
/>
</template>
</UFooter>
</UPage>
</template>
<script setup lang="ts">
import type { NavigationMenuItem, ButtonProps } from "@nuxt/ui";
const route = useRoute();
const bannerActions = ref<ButtonProps[]>([
{
label: "查看策划书",
variant: "outline",
icon: "lucide:arrow-right",
to: "/40th-anniversary/proposal",
},
]);
const items = computed<NavigationMenuItem[]>(() => [
{ label: "首页", to: "/" },
{ label: "新闻", to: "/news", active: route.path.startsWith("/news") },
{
label: "活动",
to: "/events",
active: route.path.startsWith("/events"),
children: [
{
label: "40 周年庆",
to: "/40th-anniversary",
active: route.path.startsWith("/40th-anniversary"),
badge: {
label: "Hot",
color: "error",
},
},
],
},
{
label: "关于校友会",
to: "/about",
active: route.path.startsWith("/about"),
children: [
{
label: "创会简史",
to: "/about/founded-history",
active: route.path.startsWith("/about/founded-history"),
icon: "mdi:history",
},
],
},
{
label: "友情链接",
children: [
{
label: "永平中学官网",
icon: "mdi:book-education",
to: "https://yphs.edu.my/",
target: "_blank",
},
{
label: "桃李教育网",
icon: "mdi:web",
to: "https://efuxi.vtour.my/",
target: "_blank",
},
{
label: "董总 Dong Zong",
icon: "mdi:web",
to: "https://www.dongzong.my/",
target: "_blank",
},
{
label: "永中校友网 - 旧站",
icon: "mdi:web-clock",
to: "https://vtour.my/yphsalumni/",
target: "_blank",
},
],
},
// { label: "捐赠", to: "#donate", active: route.path.startsWith("#donate") },
// { label: "关于", to: "#about", active: route.path.startsWith("#about") },
]);
const socialLinks = ref<ButtonProps[]>([
{
icon: "line-md:tiktok",
to: "https://www.tiktok.com/@yphs.alumni",
target: "_blank",
},
{
icon: "line-md:facebook",
to: "https://www.facebook.com/YPHS.Alumni/",
target: "_blank",
},
]);
</script>
<style lang="css" scoped>
.page-enter-active,
.page-leave-active {
transition: all 0.4s;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
filter: blur(1rem);
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<UContainer>
<UDashboardToolbar>
<UNavigationMenu :items="subPages" />
</UDashboardToolbar>
<NuxtPage />
</UContainer>
</template>
<script lang="ts" setup>
import type { NavigationMenuItem } from "@nuxt/ui";
const subPages = ref<NavigationMenuItem[]>([
{ label: "庆祝四十载", to: "/40th-anniversary", exact: true },
{ label: "策划案", to: "/40th-anniversary/proposal" },
]);
</script>
<style></style>

View File

@@ -0,0 +1,13 @@
<template>
<div>
40 周年庆纪念页
</div>
</template>
<script lang="ts" setup>
</script>
<style>
</style>

View File

@@ -0,0 +1,55 @@
<template>
<UPage>
<UPageBody>
<UContainer>
<template #default>
<UStepper :items="proposalSteps" size="xs" disabled />
<UPageHeader
title="永平中学校友会 40 周年纪念册筹划"
description="四十载薪火相传,情系永平,共创未来"
/>
<UPageSection title="纪念册定位">
<h3>目标受众</h3>
<ul>
<li>各届校友老中青三代</li>
<li>教职员工与校董会</li>
<li>在校学生与家长</li>
<li>地区社会贤达 / 赞助商 / 友校代表</li>
</ul>
<h3>风格方向</h3>
<p>庄重 × 情感 × 历史厚度 × 现代视觉感</p>
<p>
类似大学纪念刊风格不是单纯的活动册而是一部 *时代见证作品*
</p>
</UPageSection>
<UPageSection
title="总体结构规划"
description="建议页数120160 页"
>
</UPageSection>
</template>
</UContainer>
</UPageBody>
</UPage>
</template>
<script lang="ts" setup>
import type { StepperItem } from "@nuxt/ui";
useSeoMeta({
title: "40 周年纪念册筹划",
});
const proposalSteps = ref<StepperItem[]>([
{ title: "筹划期", description: "成立编辑组,确定风格、预算、印刷规格" },
{ title: "资料收集期", description: "访谈、征文、收照片、整理档案" },
{ title: "撰写与设计期", description: "文稿成稿、图片修复、初稿排版" },
{ title: "审校与赞助期", description: "校对、内容确认、广告页洽谈" },
{ title: "印刷准备期", description: "定稿送印、样书确认" },
{ title: "发布期", description: "校庆活动同步发行、媒体推广、线上版本上线" },
]);
</script>
<style></style>

View File

@@ -0,0 +1,393 @@
<template>
<UPage>
<UPageBody>
<UContainer>
<UPageHeader title="校友会创立简史" description="2017 年整理" />
</UContainer>
<UPageSection title="源起与经过">
<p>
一所学校的发展 校友可扮演着重要的角色 而校友会的成立
则能更有效的凝聚众人的力量 回馈母校的培育恩情
永平中学建校三十多年 培育了数以千计的莘莘学子
毕业校友遍布全国各地 在文教界工商界各领域均卓有成就
为凝聚校友情谊 加强与母校的联系 这一股力量开始在校友心中醖酿
盼着有校友会的成立 期盼透过群体的力量 共同协助母校的建设与发展
群策群力 回馈母校
</p>
<p>
自七十年代始便有成立校友会的构想 李文鍊校长也数次为此而南北奔波
号召校友发起组织 可是因为种种原因而胎死腹中
直到一九八四年十月十日方正式产生了筹委会
成员有刘响华杨顺发刘振昌罗玉珍黄保成范志清郑祥才刘用周许莉云陈美英刘建万陈家发陈仁芳周国盛余养耕何民荣陈崠甡陈亚瑞
由于筹委会成立伊始 百事待举 惟以起草章程和招收会员为主
因此这一群热心的校友即展开工作 访问友会收集资料
并通过郭进财先生协助申请注册工作
</p>
<p>
然而立意草创 始业惟艰 由于个人因素致使筹委会未能有效的协调与配合
因此进度缓慢 鉴于早日落实校友会成立的意愿
众校友在一九八四年十二月十八日决定重组筹委会 结果杨顺发荣任主席
副主席范志清 财政王飞兴 秘书罗玉珍 助理秘书陈宋丽
委员许志毅黄保成陈美英郑祥才刘建万陈仁芳
查账罗细妹吴恒发
</p>
<p>
在彼等的热诚推动与互助之下 各项工作顺利展开
一九八六年一月二十三日 永中校友会终于获得社团注册官批准成立
同年三月九日即召开会员大会选出了第一届理事会 这是历史性的一刻
在永中校友的心中写下辉煌灿烂的一页 自此校友会立下了根基
藉此迈向新的里程碑 为母语母校中华文化做出贡献
</p>
<p>
由于经费不足 一直未能自置会所
直到一九八九年方与永平树胶公会合租永平种植合作社商业大厦四楼一单位
并获得名誉主席马文清报效装修费用 才落实有一个的愿望
</p>
<p>
至2010年校友会庆祝创会25周年 在余深智学长及中马区学长陈亚龙
郑惠民 吴恒灿 陈杰民 何宗荣等推动下
由余和安主席与刘建万筹委会主席及理事 校友们的踊跃支持下
成功筹得46万零吉的会所基金参见征信录附表
其中马文清学长捐献10万元 2011年也成功购置现址之会所
并于2012年6月23日举办本会庆祝27周年庆会所启用仪式623校友团圆日暨第14届理事就职典礼晚宴
当晚也推介了永中校友全球服务网
让校友从网站获取更多有关母校发展的最新资讯
</p>
</UPageSection>
<UPageSection title="组织与结构">
<p>
自永中校友会成立之后 依章程第四条拟定 凡曾在永中就读
年龄达十八岁以上 而现已离校
不在任何中学肆业之校友均可申请为会员并规定每年四月底之前必须召开会员大会一次
每二年得举行改选 产生理事会 以下为各股职责
</p>
<ul>
<li>主席为会员大会及理事会主持人 执行会务及其他事项</li>
<li>
秘书执行会员大会及理事会依章程所达致的议决案的行政工作
处理及保管本会的一切文件
</li>
<li>财政负责会计 出纳及一切财务事宜</li>
<li>总务负责一切庶务事项</li>
<li>康乐股负责一切育乐活动</li>
<li>文教股负责学术 出版等事项</li>
<li>福利股谋求会员福利</li>
<li>会员局收集及整理会员资料档案</li>
</ul>
<p>
本会常年规划举办各项健康文艺表演 各种专题讲座等活动
让乡民增进知识发扬优良的传统文化及提倡健康的社会风气
并举办了许多活动
包括新春大团拜校友回校日卡拉ok歌唱比赛小型旅游
庆生会 都能获得热烈的响应
</p>
<p>
2015年5月份成立本会属下永平合唱团 2017年成立本会属下永中校友排球组
</p>
<p>
为加强各界校友联系 本会在Whatsapp成立了历届校友联系人群组
并架设<UTooltip
text="该域名已经下线,现由当前网站进行代替,结合全新站点的上线,代代传承,生生不息!"
:delay-duration="0"
><span>永中校友网站yphs-alumni.org</span></UTooltip
>及在面子书成立永中校友网YongPeng
Yphsalumni以不时分享各项活动资讯
</p>
<p>
本会目前的会务稳健发展 规模日宏 会员人数不断增加 作为华教先锋
本会始终立场鲜明 坚持立会宗旨 也配合董教总方针维护华教
本会向来与时并进 走在时代尖端 关注时局及各类社会问题
并提出针砭
</p>
<p>
历届理事们凭着一股强烈的使命感 对校友会不离不弃
默默地为校友会付出 贡献良多 包括老中青三代的理事紧密结合
和谐共处如一家人 在大家通力合作之下 必将秉承一贯的光辉传统
谱下更美好的篇章
</p>
</UPageSection>
<UPageSection
title="永平中学校友会二十五周年购置会所筹募基金征信录"
description="1986年-2010年制成牌匾 置挂会所"
headline="2012年6月23日"
>
<div class="overflow-x-auto rounded-2xl shadow-lg">
<table class="min-w-full border-collapse bg-white text-gray-800">
<thead
class="bg-gray-100 text-left text-sm font-semibold uppercase tracking-wide"
>
<tr>
<th class="w-32 border-b border-gray-200 px-4 py-3">金额</th>
<th class="border-b border-gray-200 px-4 py-3">捐献者名单</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 text-sm leading-relaxed">
<tr
v-for="(donor, idx) in donors"
:key="donor.amount"
class="hover:bg-gray-50"
>
<td
:class="[
'px-4',
'py-2',
idx < 2 ? 'font-bold text-primary' : '',
]"
>
{{ donor.amount }}
</td>
<td class="px-4 py-2">{{ donor.peoples.join(" ") }}</td>
</tr>
</tbody>
</table>
</div>
<p>总筹得马币四十六万元正</p>
<div class="overflow-x-auto rounded-2xl shadow-lg mt-10">
<table class="min-w-full border-collapse bg-white text-gray-800">
<thead
class="bg-gray-100 text-left text-sm font-semibold uppercase tracking-wide"
>
<tr>
<th class="w-40 border-b border-gray-200 px-4 py-3">项目</th>
<th class="border-b border-gray-200 px-4 py-3">详情</th>
<th class="w-40 border-b border-gray-200 px-4 py-3">
价值马币
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100 text-sm leading-relaxed">
<tr
class="hover:bg-gray-50"
v-for="serviceDonor in serviceDonations"
:key="serviceDonor.name"
>
<td class="px-4 py-2">{{ serviceDonor.name }}</td>
<td class="px-4 py-2">{{ serviceDonor.item }}</td>
<td class="px-4 py-2">
{{ money.format(serviceDonor.amount) }}
</td>
</tr>
<tr class="bg-amber-50 font-semibold text-primary">
<td class="px-4 py-2">总额</td>
<td class="px-4 py-2">专业服务及装修项目</td>
<td class="px-4 py-2">{{ money.format(33170) }}</td>
</tr>
</tbody>
</table>
</div>
</UPageSection>
</UPageBody>
</UPage>
</template>
<script lang="ts" setup>
useSeoMeta({
title: "校友会创立简史",
ogTitle: "校友会创立简史",
});
const money = new Intl.NumberFormat("zh-CN", {
style: "currency",
currency: "MYR",
maximumFractionDigits: 0,
});
const donors = ref<{ amount: string; peoples: string[] }[]>([
{ amount: "十万元", peoples: ["马文清"] },
{ amount: "五万元", peoples: ["余深智太平局绅"] },
{ amount: "一万八千元", peoples: ["余和安"] },
{ amount: "一万七千元", peoples: ["郑惠民"] },
{ amount: "一万五千元", peoples: ["陈亚龙", "刘建万"] },
{ amount: "一万二千元", peoples: ["吴恒灿", "郑惠顺"] },
{
amount: "一万元",
peoples: [
"YB拿督魏家祥博士",
"刘文兴",
"蓝群华",
"陈月丽",
"林俊伟",
"黄振兴",
],
},
{ amount: "六千元", peoples: ["盛添寿"] },
{ amount: "五千五百元", peoples: ["何宗荣"] },
{
amount: "五千元",
peoples: ["杨文松", "吴荣贵", "黄宗海", "余根业", "陈德福"],
},
{
amount: "三千元",
peoples: [
"拿督张玉兴",
"黄金斗",
"王飞兴",
"周隆发",
"黄世纯",
"陈正龙",
"美术广告",
"富华冷气酒家",
],
},
{
amount: "二千五百元",
peoples: [
"陈伟权",
"余清安",
"陈杰民",
"陈芳龙",
"许赐福",
"林玻璃有限公司",
],
},
{ amount: "两千元", peoples: ["周厚钿", "刘振昌", "刘用周"] },
{ amount: "一千六百元", peoples: ["余东照"] },
{ amount: "一千五百元", peoples: ["YB林其妹", "余新礼", "林建宝"] },
{
amount: "一千元",
peoples: [
"拿督黄仰白",
"陈宋丽",
"郑惠忠",
"黄荣发",
"江仁瑞",
"杨惜平",
"黄招兴",
"林家祥",
"杨顺发",
"江义顺",
"陈俊浩",
"黄仰惠",
"江先利",
"吴福华",
"余养耕",
"范志清",
"黎星堂",
"邱财德",
"邱祥春",
"覃庆星",
"陈秀彩",
"张珠英",
"吴恒发",
"余赐喜",
"林仁钦",
"黄潮明",
"谭丕光",
"郑双",
"林有兴",
"颜丰樑",
"周卓开",
"江梅香",
"谭信飞",
"邬焕铭",
"王振现",
"汤孝森",
"王辉恩",
"许秀莉",
"潘瑞平",
"王启耕",
"瞿军圣",
"余养廉",
"Yeak Nai Siew",
"许永隆有限公司",
"永平合作社",
"永平留台同学会",
],
},
{ amount: "八百元", peoples: ["余清洋"] },
{
amount: "五百元",
peoples: [
"黄楚茵",
"余莉莉",
"陈仁芳",
"郑惠国",
"王桂友",
"赖致圣",
"陈亚友",
"陈成德",
"阮文琼",
"李万同",
"刘素贞",
"林宝益",
"陈芳桂",
"余明瑞",
"郑金顺",
"汤孝式",
"林远来",
"苏继仁",
"张森錪",
"盛永錥",
"陈增华",
"胡少菲",
"邱亚泉",
"王文发",
"瞿夏莲",
"林奕用",
"王奕琪",
"王亚勇",
"林炳坤",
"郑宇曙",
"财兴铁厂",
"萧茶餐室",
],
},
{ amount: "四百元", peoples: ["福名氏"] },
{
amount: "三百元",
peoples: [
"许木春",
"杜丕石",
"张东升",
"陈祖利",
"余惠敏",
"余光云",
"余云志",
"福州会馆",
"张德满",
"黄锦彪",
"黄福顺",
"吴云景",
"黄奕洲",
"林新桦",
"廖国华",
"黄金星",
"和合汽车服务中心",
"顺兴车厂",
"闽南公会",
"友爱慈善社",
"林氏宗亲会",
"通达柅轮",
"和隆金铺",
"万裕兴有限公司",
"永平马来亚银行",
"super yes computer",
],
},
{
amount: "二百元",
peoples: [
"罗联永",
"王仕德",
"黄祖严",
"蔡晓芳",
"黄美新",
"胡述良",
"萧月英",
"蔡立义",
"林瑞平",
"张秀兰",
],
},
{ amount: "一百元", peoples: ["林春英", "黄友和", "象棋公会", "永平歌友会"] },
]);
const serviceDonations = ref<{ name: string; item: string; amount: number }[]>([
{ name: "美珠", item: "报效地砖", amount: 25000 },
{ name: "刘文兴", item: "报效漆料", amount: 3000 },
{ name: "何世荣", item: "报效画作", amount: 3000 },
{ name: "余云志律师", item: "报效律师费", amount: 2170 },
]);
</script>
<style></style>

61
app/pages/about/index.vue Normal file
View File

@@ -0,0 +1,61 @@
<template>
<UPage>
<UPageBody>
<UContainer>
<UPageHeader
title="关于校友会"
description="永平中学校友会成立的宗旨是连接全球校友,传承母校精神,支持在校学生成长。我们定期举办活动,推动交流与合作,并积极回馈母校。"
/>
<UPageSection
title="会徽"
description="你了解校友会会徽的含义吗"
:features="logoFeatures"
orientation="horizontal"
>
<img class="size-88" src="/Logo.svg" alt="YPHS Alumni" />
</UPageSection>
<UPageSection
title="永平中学校歌"
description="这首伴随着我们整个中学生涯的曲子,想必能勾起您不少的青春回忆吧!"
orientation="horizontal"
reverse
>
<div class="flex flex-col gap-y-8">
<img class="w-full" src="/about/校歌.webp" alt="YPHS Alumni" />
<div class="flex items-center gap-x-8">
<p>伴奏版</p>
<audio
controls
class="flex-1"
src="/about/永平中学校歌.mp3"
></audio>
</div>
<div class="flex items-center gap-x-8">
<p>咏唱版</p>
<audio
controls
class="flex-1"
src="/about/YongPing_SchoolSong_V2.mp3"
></audio>
</div>
</div>
</UPageSection>
</UContainer>
</UPageBody>
</UPage>
</template>
<script lang="ts" setup>
import type { PageFeatureProps } from "@nuxt/ui";
const logoFeatures = ref<PageFeatureProps[]>([
{ title: "九轮", description: "永久随时代齿轮前进" },
{ title: "太阳九条线", description: "久久发出光芒照耀华友" },
{ title: "三个菱形", description: "代表校友、董事会及社会人士团结一致" },
{ title: "两道绿叶", description: "代表组织团结、不断茁壮成长" },
]);
</script>
<style></style>

View File

@@ -0,0 +1,30 @@
<template>
<UPage>
<UPageBody>
<UContainer>
<UChangelogVersions :versions="newsPost" />
</UContainer>
</UPageBody>
</UPage>
</template>
<script lang="ts" setup>
import type { ChangelogVersionProps } from "@nuxt/ui";
const { data: events } = await useAsyncData("events", () =>
queryCollection("events").order("date", "DESC").limit(3).all()
);
// 将 news 数据转换成 UBlogPosts 可用格式
const newsPost = computed<ChangelogVersionProps[]>(() =>
(events.value || []).map((evt: any) => ({
title: evt.title,
description: evt.description,
image: evt.cover,
date: evt.date,
to: evt.path, // ✅ 建议加路由跳转
}))
);
</script>
<style></style>

View File

@@ -1,16 +1,70 @@
<template>
<div>
<IndexHero />
<IndexNews />
<UPage>
<!-- Hero Banner -->
<UPageHero
class="bg-cover bg-center"
style="
background-image: url(&quot;/hero-image.jpg&quot;);
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 />
<IndexAbout />
</div>
<!-- 捐赠模块 -->
<UPageCTA
class="bg-secondary"
title="支持与捐赠(功能暂未开放)"
description="您的捐赠将用于奖学金、校园建设及校友活动发展。感谢您对母校的支持!"
:links="donationLinks"
/>
</UPage>
</template>
<script lang="ts" setup>
import type { BlogPostProps } 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 donationLinks = ref([{ label: "立即捐赠", icon: "mdi:cash" }]);
</script>
<style></style>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref, reactive, computed, defineComponent, h } from 'vue';
import { Icon } from '@iconify/vue';
import { ref, reactive, computed, defineComponent, h } from "vue";
import { Icon } from "@iconify/vue";
import { vMaska } from "maska/vue";
// Reka primitive parts we actually need
@@ -11,7 +11,7 @@ import {
RadioGroupRoot,
RadioGroupItem,
RadioGroupIndicator,
} from 'reka-ui';
} from "reka-ui";
/**
* Local lightweight FormField wrapper:
@@ -19,7 +19,7 @@ import {
* - renders: <Label for="..."> + slot(default) + error paragraph
*/
const FormField = defineComponent({
name: 'FormField',
name: "FormField",
props: {
label: { type: String, required: false },
error: { type: String, required: false },
@@ -28,13 +28,13 @@ const FormField = defineComponent({
setup(props, { slots }) {
return () =>
h(
'div',
{ class: 'grid gap-2' },
"div",
{ class: "grid gap-2" },
[
props.label ? h(Label, { for: props.for }, () => props.label) : null,
slots.default ? slots.default() : null,
props.error
? h('p', { class: 'text-sm text-red-600 mt-1' }, () => props.error)
? h("p", { class: "text-sm text-red-600 mt-1" }, () => props.error)
: null,
].filter(Boolean)
);
@@ -45,30 +45,30 @@ const FormField = defineComponent({
const currentYear = new Date().getFullYear();
const form = reactive({
chineseName: '',
englishName: '',
ic: '',
email: '',
phone: '',
chineseName: "",
englishName: "",
ic: "",
email: "",
phone: "",
gradYear: null as number | null,
unknownGradYear: false,
educationLevel: '',
maritalStatus: '',
country: '',
address: '',
educationLevel: "",
maritalStatus: "",
country: "",
address: "",
});
const errors = reactive<Record<string, string>>({});
const toUpperCaseEnglish = () => {
form.englishName = (form.englishName || '').toUpperCase();
form.englishName = (form.englishName || "").toUpperCase();
};
const graduationBatch = computed(() => {
if (form.gradYear) {
if (form.educationLevel === '高中毕业') {
if (form.educationLevel === "高中毕业") {
return form.gradYear - 1965;
} else if (form.educationLevel === '初中毕业') {
} else if (form.educationLevel === "初中毕业") {
return form.gradYear - 1958;
}
}
@@ -76,25 +76,28 @@ const graduationBatch = computed(() => {
});
const validate = () => {
errors.chineseName = !form.chineseName ? '请输入中文姓名' : '';
errors.englishName = !form.englishName ? '请输入英文姓名' : '';
errors.ic = /^\d{6}-\d{2}-\d{4}$/.test(form.ic) ? '' : '格式应为 000000-00-0000';
errors.chineseName = !form.chineseName ? "请输入中文姓名" : "";
errors.englishName = !form.englishName ? "请输入英文姓名" : "";
errors.ic = /^\d{6}-\d{2}-\d{4}$/.test(form.ic)
? ""
: "格式应为 000000-00-0000";
errors.email =
(!form.email && form.country !== '马来西亚')
? '国外居住必须填写电邮'
!form.email && form.country !== "马来西亚"
? "国外居住必须填写电邮"
: form.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)
? '请输入有效的电邮地址'
: '';
? "请输入有效的电邮地址"
: "";
errors.phone =
!/^01\d{1}-\d{7,8}$/.test(form.phone) && !/^\+\d{1,3}\s?\d+$/.test(form.phone)
? '请输入马来西亚号 (01x-xxxxxxx) 或带区号的号码'
: '';
errors.educationLevel = !form.educationLevel ? '请选择毕业层次' : '';
!/^01\d{1}-\d{7,8}$/.test(form.phone) &&
!/^\+\d{1,3}\s?\d+$/.test(form.phone)
? "请输入马来西亚号 (01x-xxxxxxx) 或带区号的号码"
: "";
errors.educationLevel = !form.educationLevel ? "请选择毕业层次" : "";
errors.gradYear =
!form.unknownGradYear && !form.gradYear ? '请输入毕业年份或勾选“不详”' : '';
errors.maritalStatus = !form.maritalStatus ? '请选择婚姻状态' : '';
errors.country = !form.country ? '请选择国家' : '';
errors.address = !form.address ? '请输入详细地址' : '';
!form.unknownGradYear && !form.gradYear ? "请输入毕业年份或勾选“不详”" : "";
errors.maritalStatus = !form.maritalStatus ? "请选择婚姻状态" : "";
errors.country = !form.country ? "请选择国家" : "";
errors.address = !form.address ? "请输入详细地址" : "";
return Object.values(errors).every((e) => !e);
};
@@ -102,204 +105,254 @@ const validate = () => {
const handleSubmit = () => {
if (validate()) {
// 如果你已在根组件挂载 Reka 的 ToastProvider + useToast可替换下面 alert 的实现(见备注)
alert('提交成功!理事会将尽快联系您。');
alert("提交成功!理事会将尽快联系您。");
} else {
alert('请完善表单信息');
alert("请完善表单信息");
}
};
</script>
<template>
<div class="cursor-not-allowed fixed flex items-center justify-center min-h-screen min-w-screen bg-black opacity-50">
<p class="text-white text-2xl">此功能尚未开放敬请期待谢谢</p>
</div>
<div class="min-h-screen bg-primary py-10">
<div class="max-w-3xl mx-auto p-8 bg-white rounded-2xl shadow-lg">
<h1 class="text-3xl font-bold mb-6 text-center text-secondary">
永平中学校友会入会申请表
</h1>
<UPage class="bg-primary">
<div
class="cursor-not-allowed fixed flex items-center justify-center min-h-screen min-w-screen bg-black opacity-50"
>
<p class="text-white text-2xl">此功能尚未开放敬请期待谢谢</p>
</div>
<UPageBody>
<div class="max-w-3xl mx-auto p-8 bg-white rounded-2xl shadow-lg">
<h1 class="text-3xl font-bold mb-6 text-center text-secondary">
永平中学校友会入会申请表
</h1>
<p class="text-sm text-gray-600 my-6 text-center leading-relaxed">
兹申请加入成为永平中学校友会会员愿遵守会规及议决案并填此表为据<br />
入会费 <span class="font-bold text-secondary">RM60 / </span><br />
填写此表格之后会有理事联系您协商入会费事宜
</p>
<p class="text-sm text-gray-600 my-6 text-center leading-relaxed">
兹申请加入成为永平中学校友会会员愿遵守会规及议决案并填此表为据<br />
入会费 <span class="font-bold text-secondary">RM60 / </span><br />
填写此表格之后会有理事联系您协商入会费事宜
</p>
<form @submit.prevent="handleSubmit" class="space-y-6">
<!-- 中文姓名 -->
<FormField label="中文姓名" :error="errors.chineseName" for="chineseName">
<input
id="chineseName"
v-model="form.chineseName"
class="w-full border rounded px-3 py-2"
placeholder="请输入中文姓名"
/>
</FormField>
<!-- 英文姓名 -->
<FormField label="英文姓名" :error="errors.englishName" for="englishName">
<input
id="englishName"
v-model="form.englishName"
@input="toUpperCaseEnglish"
class="w-full border rounded px-3 py-2"
placeholder="请输入英文姓名"
/>
</FormField>
<!-- IC -->
<FormField label="IC" :error="errors.ic" for="ic">
<input
id="ic"
v-model="form.ic"
class="w-full border rounded px-3 py-2"
placeholder="000000-00-0000"
v-maska="'######-##-####'"
/>
</FormField>
<!-- 电邮 -->
<FormField label="电邮" :error="errors.email" for="email">
<input
id="email"
v-model="form.email"
class="w-full border rounded px-3 py-2"
placeholder="选填 / 国外必填"
/>
</FormField>
<!-- 电话 -->
<FormField label="电话" :error="errors.phone" for="phone">
<input
id="phone"
v-model="form.phone"
class="w-full border rounded px-3 py-2"
placeholder="请输入电话WhatsApp 号码为佳)"
/>
</FormField>
<!-- 毕业层次 (使用 Reka Radio primitives) -->
<FormField label="毕业层次" :error="errors.educationLevel" for="educationLevel">
<RadioGroupRoot
v-model="form.educationLevel"
class="flex flex-col gap-2"
name="educationLevel"
<form @submit.prevent="handleSubmit" class="space-y-6">
<!-- 中文姓名 -->
<FormField
label="中文姓名"
:error="errors.chineseName"
for="chineseName"
>
<RadioGroupItem value="初中毕业" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>初中毕业</span>
</RadioGroupItem>
<RadioGroupItem value="高中毕业" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>高中毕业</span>
</RadioGroupItem>
<RadioGroupItem value="辍学/转学肄业" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>辍学/转学肄业</span>
</RadioGroupItem>
<RadioGroupItem value="不确定" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>不确定</span>
</RadioGroupItem>
</RadioGroupRoot>
</FormField>
<!-- 毕业年份 -->
<FormField label="毕业年份" :error="errors.gradYear" for="gradYear">
<div class="flex items-center gap-3">
<input
id="gradYear"
type="number"
v-model="form.gradYear"
:min="1957"
:max="currentYear"
:disabled="form.unknownGradYear"
class="w-32 border rounded px-3 py-2"
id="chineseName"
v-model="form.chineseName"
class="w-full border rounded px-3 py-2"
placeholder="请输入中文姓名"
/>
<label class="flex items-center gap-2 select-none">
<CheckboxRoot v-model="form.unknownGradYear" class="w-5 h-5 rounded border flex items-center justify-center">
<CheckboxIndicator class="flex items-center justify-center w-full h-full">
<Icon icon="radix-icons:check" class="h-4 w-4 text-secondary" />
</CheckboxIndicator>
</CheckboxRoot>
<span>毕业年份不详</span>
</label>
</FormField>
<span class="text-sm text-gray-500" v-if="graduationBatch">
您是第 <span class="font-bold">{{ graduationBatch }}</span> 届毕业生
</span>
</div>
</FormField>
<!-- 英文姓名 -->
<FormField
label="英文姓名"
:error="errors.englishName"
for="englishName"
>
<input
id="englishName"
v-model="form.englishName"
@input="toUpperCaseEnglish"
class="w-full border rounded px-3 py-2"
placeholder="请输入英文姓名"
/>
</FormField>
<!-- 婚姻状态 -->
<FormField label="婚姻状态" :error="errors.maritalStatus" for="maritalStatus">
<div class="flex flex-col gap-2">
<RadioGroupRoot v-model="form.maritalStatus" name="maritalStatus">
<RadioGroupItem value="未婚" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<!-- IC -->
<FormField label="IC" :error="errors.ic" for="ic">
<input
id="ic"
v-model="form.ic"
class="w-full border rounded px-3 py-2"
placeholder="000000-00-0000"
v-maska="'######-##-####'"
/>
</FormField>
<!-- 电邮 -->
<FormField label="电邮" :error="errors.email" for="email">
<input
id="email"
v-model="form.email"
class="w-full border rounded px-3 py-2"
placeholder="选填 / 国外必填"
/>
</FormField>
<!-- 电话 -->
<FormField label="电话" :error="errors.phone" for="phone">
<input
id="phone"
v-model="form.phone"
class="w-full border rounded px-3 py-2"
placeholder="请输入电话WhatsApp 号码为佳)"
/>
</FormField>
<!-- 毕业层次 (使用 Reka Radio primitives) -->
<FormField
label="毕业层次"
:error="errors.educationLevel"
for="educationLevel"
>
<RadioGroupRoot
v-model="form.educationLevel"
class="flex flex-col gap-2"
name="educationLevel"
>
<RadioGroupItem value="初中毕业" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>未婚</span>
<span>初中毕业</span>
</RadioGroupItem>
<RadioGroupItem value="已婚" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<RadioGroupItem value="高中毕业" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>已婚</span>
<span>高中毕业</span>
</RadioGroupItem>
<RadioGroupItem value="其他" class="flex items-center gap-3">
<RadioGroupIndicator class="w-4 h-4 rounded-full border flex items-center justify-center">
<RadioGroupItem
value="辍学/转学肄业"
class="flex items-center gap-3"
>
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>其他</span>
<span>辍学/转学肄业</span>
</RadioGroupItem>
<RadioGroupItem value="不确定" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>不确定</span>
</RadioGroupItem>
</RadioGroupRoot>
</div>
</FormField>
</FormField>
<!-- 国家原生 select简单且稳定 -->
<FormField label="国家" :error="errors.country" for="country">
<select id="country" v-model="form.country" class="w-full border rounded px-3 py-2">
<option value="" disabled>请选择国家</option>
<option>马来西亚</option>
<option>新加坡</option>
<option>中国</option>
<option>美国</option>
<option>其他</option>
</select>
</FormField>
<!-- 毕业年份 -->
<FormField label="毕业年份" :error="errors.gradYear" for="gradYear">
<div class="flex items-center gap-3">
<input
id="gradYear"
type="number"
v-model="form.gradYear"
:min="1957"
:max="currentYear"
:disabled="form.unknownGradYear"
class="w-32 border rounded px-3 py-2"
/>
<label class="flex items-center gap-2 select-none">
<CheckboxRoot
v-model="form.unknownGradYear"
class="w-5 h-5 rounded border flex items-center justify-center"
>
<CheckboxIndicator
class="flex items-center justify-center w-full h-full"
>
<Icon
icon="radix-icons:check"
class="h-4 w-4 text-secondary"
/>
</CheckboxIndicator>
</CheckboxRoot>
<span>毕业年份不详</span>
</label>
<!-- 详细地址 -->
<FormField label="详细地址" :error="errors.address" for="address">
<textarea
id="address"
v-model="form.address"
class="w-full border rounded px-3 py-2"
placeholder="请输入现居详细地址"
rows="4"
/>
</FormField>
<span class="text-sm text-gray-500" v-if="graduationBatch">
您是第
<span class="font-bold">{{ graduationBatch }}</span> 届毕业生
</span>
</div>
</FormField>
<div class="text-center mt-8">
<button
type="submit"
class="bg-secondary text-white font-bold px-8 py-2 rounded-xl shadow hover:scale-105 transition"
<!-- 婚姻状态 -->
<FormField
label="婚姻状态"
:error="errors.maritalStatus"
for="maritalStatus"
>
提交申请
</button>
</div>
</form>
</div>
</div>
<div class="flex flex-col gap-2">
<RadioGroupRoot v-model="form.maritalStatus" name="maritalStatus">
<RadioGroupItem value="未婚" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>未婚</span>
</RadioGroupItem>
<RadioGroupItem value="已婚" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>已婚</span>
</RadioGroupItem>
<RadioGroupItem value="其他" class="flex items-center gap-3">
<RadioGroupIndicator
class="w-4 h-4 rounded-full border flex items-center justify-center"
>
<span class="block w-2 h-2 rounded-full bg-secondary" />
</RadioGroupIndicator>
<span>其他</span>
</RadioGroupItem>
</RadioGroupRoot>
</div>
</FormField>
<!-- 国家原生 select简单且稳定 -->
<FormField label="国家" :error="errors.country" for="country">
<select
id="country"
v-model="form.country"
class="w-full border rounded px-3 py-2"
>
<option value="" disabled>请选择国家</option>
<option>马来西亚</option>
<option>新加坡</option>
<option>中国</option>
<option>美国</option>
<option>其他</option>
</select>
</FormField>
<!-- 详细地址 -->
<FormField label="详细地址" :error="errors.address" for="address">
<textarea
id="address"
v-model="form.address"
class="w-full border rounded px-3 py-2"
placeholder="请输入现居详细地址"
rows="4"
/>
</FormField>
<div class="text-center mt-8">
<button
type="submit"
class="bg-secondary text-white font-bold px-8 py-2 rounded-xl shadow hover:scale-105 transition"
>
提交申请
</button>
</div>
</form>
</div>
</UPageBody>
</UPage>
</template>

30
app/pages/news/index.vue Normal file
View File

@@ -0,0 +1,30 @@
<template>
<UPage>
<UPageBody>
<UContainer>
<UChangelogVersions :versions="newsPost" />
</UContainer>
</UPageBody>
</UPage>
</template>
<script lang="ts" setup>
import type { ChangelogVersionProps } from "@nuxt/ui";
const { data: news } = await useAsyncData("news", () =>
queryCollection("news").order("date", "DESC").all()
);
// 将 news 数据转换成 UBlogPosts 可用格式
const newsPost = computed<ChangelogVersionProps[]>(() =>
(news.value || []).map((n: any) => ({
title: n.title,
description: n.description,
image: n.cover,
date: n.date,
to: n.path, // ✅ 建议加路由跳转
}))
);
</script>
<style></style>

View File

@@ -42,6 +42,31 @@ cover: "/events/20250927-return-to-school/event-photo-1.jpg"
午宴当天,永中二十四节令鼓队与舞蹈社学员带来精彩表演,“永中之星 1.0”歌唱赛校友组冠军 **黄秋慧** 与学生组冠军 **林妤桐** 亦倾情献唱。现场欢声笑语不断,气氛热烈温馨,为会庆增添了浓厚的节日色彩。
## 午宴 • 流程表
| 时间 | 流程 |
| ------- | ----------------------------------------------------------------------- |
| 12.30pm | 二十四节令鼓迎宾/校友交流 |
| 01.00pm | 仪式开始 |
| 01.02pm | 唱国歌州歌 |
| 01.05pm | 大会主席李煜斌致欢迎词 |
| 01.10pm | 大会开幕嘉宾-投资贸易及工业部副部长暨伊斯干达公主城国会议员YB刘镇东致词 |
| | 赠送纪念品给予大会开幕嘉宾 |
| 01.20pm | 大会荣誉嘉宾-亚依淡区国会议员拿督斯里YB魏家祥博士工程师致辞 |
| | 赠送纪念品给予大会荣誉嘉宾 |
| 01.25pm | 大会荣誉嘉宾-柔佛州行政议员暨永平区州议员 YB 林添顺致词 |
| | 赠送纪念品给予大会荣誉嘉宾 |
| 01.40pm | 永平中学-舞蹈团表演 |
| 01.50pm | 永平中学张嘉群校长致词 |
| 01.55pm | 切生日蛋糕 |
| 02.00pm | 播放上午校友回校活动 |
| 02.05pm | 永中之星1.0冠军得主表演(校友组-黄秋慧,及在籍学生组-林妤桐) |
| 02.35pm | 工委会主席蓝宜宏学长致谢词 |
| 02.40pm | 永中校友会理事会带领唱校歌 |
| 02.45pm | 欢送嘉宾离席 |
我们 40 周年庆再会!感谢大家的莅临!感恩!
---
<iframe

View File

@@ -0,0 +1,79 @@
---
title: "柔联校友会 20 届历史就职典礼"
subtitle: "中学生写作比赛颁奖"
date: "2025-10-09"
location: "富华冷气酒家 2 楼"
cover: "/events/20251009-roulian-xiaoyouhui-20th/event-photo-1.jpg"
---
# 柔联校友会 20 届历史就职典礼
柔佛州华校校友联合会日前举办第 20 届理事就职典礼与 2025 年 “林赛花教育基金” 柔佛州中学生现场写作比赛颁奖典礼,由马来西亚华校校友会联合总会会长萧成兴担任监誓人。
联合会主席陈星和在致词时强调,华教事业的发展离不开团结与传承,新一届理事会将继续致力于推动华文教育,弘扬中华文化。
## 柔佛州华校校友会联合会
### 第20届理事会
| 职位 | 姓名 |
| ---------- | ---------------------------------------------- |
| 主席 | 陈星和 |
| 副主席 | 陈月丽、陈重存、颜青积 |
| 总务 | 李秀琴 |
| 副总务 | 李煜斌 |
| 财政 | 陈保妤 |
| 副财政 | 吴沺成 |
| 文书 | 莫文豪 |
| 副文书 | 林道民 |
| 教育主任 | 姚理介 |
| 副教育主任 | 陈成祖 |
| 文娱主任 | 郑臻董 |
| 副文娱主任 | 梁德荣 |
| 联络主任 | 郑伟亮 |
| 副联络主任 | 郑清华 |
| 查账 | 涂馨尹、谢祥庆 |
| 理事 | 何国光、吕哲明、罗升隆、黄如龙、郑明裕、温维华 |
---
赞助金移交仪式上,妙妙机构执行董事荘坡政将赞助金移交给峇株文艺协会,以支持文艺出版与推广工作,随后颁发奖状与奖金给 26 名获奖学生。
## 🏆 2025年“林賽花教育基金”柔佛州中學生現場寫作比賽得獎名單
### 🥇 初中组
| 奖项 | 姓名 | 学校 |
| ------ | ------ | ------------------ |
| 特优奖 | 彭艺元 | 居銮中华中学 |
| 特优奖 | 黄守蒽 | 居銮中华中学 |
| 特优奖 | 方乐颖 | 居銮中华中学 |
| 优秀奖 | 余姿亿 | 永平中学 |
| 优秀奖 | 廖玮乐 | 永平中学 |
| 优秀奖 | 卓婧琳 | 峇株吧辖华仁中学 |
| 优秀奖 | 叶贯均 | 峇株吧辖华仁中学 |
| 优秀奖 | 董清清 | 峇株吧辖华仁中学 |
| 优秀奖 | 林婉仪 | 峇株吧辖华仁中学 |
| 优秀奖 | 陈惠敏 | 峇株吧辖华仁中学 |
| 优秀奖 | 曾愉峻 | 居銮中华中学 |
| 优秀奖 | 曾佳滢 | 利丰港培华独立中学 |
| 优秀奖 | 陆颖霏 | 新山宽柔中学 |
---
### 🥈 高中组
| 奖项 | 姓名 | 学校 |
| ------ | ------ | ---------------- |
| 特优奖 | 王祺齐 | 永平中学 |
| 特优奖 | 汤滢菲 | 居銮中华中学 |
| 特优奖 | 陈思宇 | 居銮中华中学 |
| 优秀奖 | 黄婉芯 | 拿督国中 |
| 优秀奖 | 林欣慧 | 拿督国中 |
| 优秀奖 | 蔡欣艳 | 拿督国中 |
| 优秀奖 | 许恩芮 | 永平中学 |
| 优秀奖 | 陈佳萱 | 宽柔中学古来分校 |
| 优秀奖 | 苏新致 | 永平中学 |
| 优秀奖 | 刘祈悦 | 峇株吧辖华仁中学 |
| 优秀奖 | 林淳希 | 峇株吧辖华仁中学 |
| 优秀奖 | 黄子宸 | 新山宽柔中学 |
| 优秀奖 | 苏祐萱 | 新山宽柔中学 |

View File

@@ -11,6 +11,7 @@ highlight: true
seoTitle: "永中校友会官网上线 | 最新活动与资讯平台"
seoDescription: "永中校友会官网正式上线,校友可在平台获取最新资讯、报名活动及参与互动。"
ogImage: "/images/og/news-launch.jpg"
slug: "/news/20251001-official-web-launch"
---
永中校友会官网正式上线啦!🎉

View File

@@ -0,0 +1,148 @@
# 40 周年纪念册策划案
## 🏛 一、纪念册定位
**主题定位**
> 「四十载薪火相传,情系永平,共创未来」
**目标受众**
* 各届校友(老中青三代)
* 教职员工与校董会
* 在校学生与家长
* 地区社会贤达 / 赞助商 / 友校代表
**风格方向**
庄重 × 情感 × 历史厚度 × 现代视觉感
→ 类似大学纪念刊风格,不是单纯的活动册,而是一部 *时代见证作品*
---
## 📘 二、总体结构规划建议页数120160 页)
### **封面**
* 主视觉设计:校徽 × 火焰 / 时光流线 / 数字“40”标志
* 副标题“1986—2026 四十周年纪念册”
* 封底:赞助单位 + 出版声明 + QR码导向线上影像纪录
---
### **前序部分(约 10 页)**
1. **题词与献词**
* 校友会主席致辞
* 校长致辞
* 董事长致辞
* 特邀嘉宾寄语(如地方议员 / 教育界代表)
2. **编委会名单**
* 筹委会 / 编辑组 / 摄影 / 设计 / 出版等名单
3. **大事记总览**
* 用年表形式列出 19862026 的重要节点
* 例如:创会、历届理事更替、重大活动、奖学金设立、母校建设捐助等
---
### **第一章|溯源篇:起点与精神(约 20 页)**
* 成立背景1980s 马来西亚华教环境)
* 永平中学历史简介
* 校友会成立缘起、第一届理事成员介绍
* “校友情”的精神与口号传承
* 早期活动影像、报纸剪报、珍贵照片扫描件
---
### **第二章|成长篇:四十年的足迹(约 40 页)**
分阶段叙述,每 10 年为一章:
* **19861996**:创业维艰期
* **19972006**:稳定扩展期
* **20072016**:数码化与社会联结期
* **20172026**:复兴与再创辉煌
每个阶段包含:
* 代表性活动介绍(如校庆、义演、体育赛、募捐项目等)
* 重要人物访谈(理事长 / 校友代表)
* 历史照片 & 当年文宣再现
* 时代感对比(旧照片 vs 现今校园)
---
### **第三章|人物篇:传承与榜样(约 25 页)**
* **杰出校友特写**58 人)
* 各领域代表:教育、企业、科技、文化、公益、艺术等
* 每人一页访谈式报道 + 肖像照 + 人生金句
* **幕后人物**:长期服务校友会者(秘书、摄影、活动志工)
* **纪念人物**:已故贡献校友与老师纪念
---
### **第四章|情感篇:故事与回忆(约 25 页)**
* “我们的青春”主题征文(精选 1015 篇)
* “老照片背后的故事”——由投稿校友自述
* 班级回忆录(部分毕业届集体投稿)
* 手写留言页 / QR码链接至音视频寄语
---
### **第五章|未来篇:传承与愿景(约 15 页)**
* 永平中学未来建设规划(教育蓝图)
* 校友会未来十年计划
* 青年校友接班计划
* “数字校友会”构想/推介:网站 / 校友数据库
---
### **第六章|花絮与活动特辑(约 15 页)**
> 如果本书是在庆典活动前几天印刷好,然后活动当天发出
* 本次四十周年庆典活动全记录
* 开幕典礼
* 校友之夜
* 义跑 / 晚宴 / 展览 / 演出
* 纪念品设计展示
---
### **附录**
* 校友会历届理事名录
* 捐款鸣谢名单(依金额分级)
* 赞助与广告页
* 联系方式与 QR Link线上相册、纪录片、网站
---
## 🧭 三、工作时间规划8 个月执行建议)
| 阶段 | 时长 | 工作内容 |
| -------------- | ------- | ---------------------------------------- |
| 1⃣ 筹划期 | 第1月 | 成立编辑组,确定风格、预算、印刷规格 |
| 2⃣ 资料收集期 | 第23月 | 访谈、征文、收照片、整理档案 |
| 3⃣ 撰写与设计期 | 第45月 | 文稿成稿、图片修复、初稿排版 |
| 4⃣ 审校与赞助期 | 第6月 | 校对、内容确认、广告页洽谈 |
| 5⃣ 印刷准备期 | 第7月 | 定稿送印、样书确认 |
| 6⃣ 发布期 | 第8月 | 校庆活动同步发行、媒体推广、线上版本上线 |
---
## 💡 四、可拓展创意(让纪念册更现代)
* 📱 **AR互动页**:扫描校徽出现 3D 校史影片
* 🌐 **线上纪念册版**(在校友会官网嵌入同步信息)
* 🎬 **纪录片二维码嵌入**(对应章节)
* 🕊️ **数字留言墙**(供校友写下祝福)

Binary file not shown.

View File

@@ -29,28 +29,46 @@ export default defineNuxtConfig({
],
meta: [
// 基础 SEO
{ name: "description", content: "永平中学校友会官网 - 连接校友,共享资源,传承母校精神。" },
{ name: "keywords", content: "永平中学, 校友会, 永平中学校友, 永平校友, 同学会" },
{
name: "description",
content: "永平中学校友会官网 - 连接校友,共享资源,传承母校精神。",
},
{
name: "keywords",
content: "永平中学, 校友会, 永平中学校友, 永平校友, 同学会",
},
{ name: "author", content: "永平中学校友会" },
{ name: "viewport", content: "width=device-width, initial-scale=1" },
// Open GraphFacebook/LinkedIn
{ property: "og:title", content: "永平中学校友会" },
{ property: "og:description", content: "永平中学校友会官网 - 连接校友,共享资源,传承母校精神。" },
{
property: "og:description",
content: "永平中学校友会官网 - 连接校友,共享资源,传承母校精神。",
},
{ property: "og:type", content: "website" },
{ property: "og:url", content: "https://yphsalumni.org" }, // ✅ 换成你网站的真实域名
{ property: "og:image", content: "https://yphsalumni.org/hero-image.jpg" }, // ✅ 上传一张封面图
{
property: "og:image",
content: "https://yphsalumni.org/hero-image.jpg",
}, // ✅ 上传一张封面图
// Twitter Card
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: "永平中学校友会" },
{ name: "twitter:description", content: "连接校友,共享资源,传承母校精神。" },
{ name: "twitter:image", content: "https://yphsalumni.org/hero-image.jpg" },
{
name: "twitter:description",
content: "连接校友,共享资源,传承母校精神。",
},
{
name: "twitter:image",
content: "https://yphsalumni.org/hero-image.jpg",
},
],
},
},
site: {
url: "https://yphsalumni.com",
name: "永中校友会 YPHS Alumni"
}
name: "永中校友会 YPHS Alumni",
},
});

View File

@@ -22,7 +22,7 @@
"html2pdf.js": "^0.12.1",
"maska": "^3.2.0",
"md-editor-v3": "^6.0.1",
"nuxt": "^4.1.3",
"nuxt": "^4.2.0",
"reka-ui": "^2.5.1",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3",
@@ -31,6 +31,7 @@
"vue-sonner": "^2.0.9"
},
"devDependencies": {
"@iconify-json/lucide": "^1.2.70",
"sass-embedded": "^1.93.2"
}
}

1502
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

BIN
public/about/校歌.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB