Compare commits

...

19 Commits

Author SHA1 Message Date
xiaomai
6de61c24b2 style(theme): expand color palettes and refine dark mode styles
- Expand CSS variables for primary and secondary colors to include full 50-950 scales in `main.css`.
- Update components to reference specific color shades (e.g., `primary-400`, `secondary-200`) instead of generic variables.
- Add dark mode background and text colors to Events, Hall of Fame, and Index sections.
- Adjust image aspect ratio in the Events component.
2025-11-28 17:27:34 +08:00
xiaomai
f6bbd95b77 chore(assets): migrate local images to remote storage
- Replace local image paths with remote URLs in components, content, and config.
- Remove local image files from the public directory to reduce repository size.
- Add dark mode support for the hero background image in `index.vue`.
2025-11-28 16:45:30 +08:00
xiaomai
a28bb3a54d feat(news): enhance SEO meta tags and add clarification article
- Update news detail page to use `useSeoMeta` for comprehensive Open Graph and Twitter Card support.
- Implement logic to prioritize `ogImage` and `seoTitle` with fallbacks to standard fields.
- Add new content article clarifying the distinction between the Alumni Association and the Alumni Liaison Office.
- Add documentation drafts for onboarding messages and announcement design.
2025-11-28 11:54:11 +08:00
xiaomai
9bca019b50 feat(seo): improve SEO configuration and enable SSR
- Enable `ssr: true` in `nuxt.config.ts` for server-side rendering of meta tags.
- Implement `useSeoMeta` in `events/.vue` with fallback logic for Open Graph and Twitter cards.
- Update `content.config.ts` to use `asSeoCollection` for the news collection.
- Migrate event markdown frontmatter to use standardized SEO fields.
2025-11-27 23:07:24 +08:00
xiaomai
6288a1b01b feat(content): add draft support for content collections
This commit introduces a draft system for the 'events' and 'news' collections. A `draft` boolean field has been added to the content schema, and frontend queries are now updated to only fetch and
display content where `draft` is `false`. This allows content to be created and saved without being publicly visible, improving the publishing workflow.
2025-11-27 17:53:42 +08:00
xiaomai
c7da09d327 refactor(ui): adopt Nuxt UI prose styles and apply various fixes
Replaces custom `markdown.css` with the default prose styling from Nuxt UI to simplify the styling architecture. This commit also includes several other fixes and improvements:

- fix(members): Correctly
handle empty `graduateYear` strings in the member list.
- fix(layout): Wrap `NuxtPage` in a div to resolve page transition issues.
- feat(seo): Add a meta title to the homepage.
- docs(content): Correct
the title and add an image to an event page.
2025-11-15 13:25:42 +08:00
xiaomai
2ac1428c34 feat(members): add member ID to members list
This commit introduces a 'Member ID' column to the members table, making it the first column for easy reference.

- The member data schema and sample CSV have been updated to include the `memberId`.
- The members page now displays the `memberId` for each member.
- Additionally, a version query string has been added to the hero image URL to force a cache refresh.
2025-11-02 22:37:17 +08:00
xiaomai
3da20d0097 feat(members): add members listing page
This commit introduces a new `/members` page to display a directory of alumni association members.

- Member data is sourced from a CSV file (`content/members/members.csv`) and managed via Nuxt Content.
- The page presents member information in a table, including calculated graduation class (`届别`).
- A link to the new page has been added to the main navigation.
- Minor UI tweaks and data corrections in other sections are also included.
2025-11-02 22:09:57 +08:00
xiaomai
f5d9963f3c feat(about): add organizational structure page
This commit introduces a new page to display the alumni association's organizational structure.

- Adds a new page at `/about/org-structure` to showcase the committee members.
- Displays each member's photo, position, name, and a brief description of their role.
- The structure is organized into categories: Leadership Team, Functional Departments, and Specialized Departments.
- A navigation link has been added to the main layout sidebar for easy access.
2025-11-02 15:25:19 +08:00
xiaomai
cf6dfac6a3 docs(presentation): add drafts for launch presentation slides
This commit adds two draft documents for the official website launch presentation. These files outline the structure, key messages, and content for the slides.

- `PPT Designs v1.md`: Provides a standard presentation structure covering the project's goals, features, and roadmap.
- `PPT Designs v2.md`: Offers a more refined narrative focusing on value, contribution, and open-source aspects, including copy for press releases and announcements.
2025-11-02 00:23:52 +08:00
xiaomai
2649cca69d docs: add comprehensive project documentation and MIT license
Replaces the generic Nuxt starter README with detailed documentation specific to the YPHS Alumni website project.

The new README.md now includes:
- Project overview, tech stack, and key features
- Local development, build, and deployment instructions
- Content authoring guide for news, events, and hall of fame
- Project roadmap and contribution guidelines

Additionally, this commit adds an MIT license file to formally establish the project's open-source status.
2025-11-01 23:38:39 +08:00
xiaomai
59fc6cb13d feat(seo): improve SEO and add analytics tracking
This commit introduces several enhancements for SEO and analytics.

- Updates the canonical site URL from `yphsalumni.com` to `yphsalumni.org`.
- Enriches the 'middle-highschool-tuition-class' page with detailed Open Graph (og) meta tags for better social sharing.
- Implements Umami event tracking for the 'Join Us' button on the homepage.
2025-11-01 23:31:45 +08:00
xiaomai
33334a7515 style(ui): improve layout and responsiveness of tuition class page
This commit adjusts the styling on the 'Middle/High School Tuition Class' page to enhance its appearance and readability,
particularly on mobile devices.

- Implemented responsive padding for the main page and content container.
- Justified the main text block for better readability.
- Centered the student name badges.
- Added configuration files for the `repomix` project analysis tool.
2025-10-30 17:48:30 +08:00
xiaomai
c468da8780 feat(about): add page for 1956 middle highschool tuition class
This commit introduces a new page dedicated to the 'Middle Highschool Tuition Class' of 1956 (中补班). The page displays a letter written in 2022, calling for a reunion of these special alumni. A navigation
link has been added to the 'About' section for easy access to this historical content.
2025-10-27 22:18:07 +08:00
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
57 changed files with 4948 additions and 2908 deletions

4
.gitignore vendored
View File

@@ -23,4 +23,6 @@ logs
.env.*
!.env.example
repomix-output.xml
repomix-output.xml
content/members/members.csv

10
.repomixignore Normal file
View File

@@ -0,0 +1,10 @@
# Add patterns to ignore here, one per line
# Example:
# *.log
# tmp/
pnpm-workspace.yaml
public/
app/assets/
docs/
*.md
countries.ts

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 [Tootaio Studio]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

264
README.md
View File

@@ -1,75 +1,229 @@
# Nuxt Minimal Starter
# 永平中学校友会官网YPHS Alumni
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
一个基于 Nuxt 4 与 Nuxt Content 打造的内容型网站,面向永平中学校友与社会公众,提供新闻公告、活动预告、名人堂与校友组织介绍等信息。项目内置基础的后台界面脚手架,支持后续扩展会员管理与内容发布流程。
## Setup
![Nuxt](https://img.shields.io/badge/Nuxt-4-00DC82?logo=nuxt.js&logoColor=white)
![TypeScript](https://img.shields.io/badge/TypeScript-5.x-3178C6?logo=typescript&logoColor=white)
![TailwindCSS](https://img.shields.io/badge/TailwindCSS-4.x-06B6D4?logo=tailwindcss&logoColor=white)
![License](https://img.shields.io/badge/License-MIT-red)
Make sure to install dependencies:
- 线上地址https://yphsalumni.org
- 技术栈Nuxt 4Vue 3 + Nitro、TypeScript、Tailwind CSS 4、@nuxt/ui@nuxt/content@nuxt/image、SEO/Sitemap/Robots 模块
**项目亮点**
- 内容优先:使用 Markdown + 前置属性Frontmatter管理新闻、活动、名人堂。
- 现代 UI采用 @nuxt/ui + Tailwind CSS 4内置响应式导航与主题样式。
- SEO 友好:内置 @nuxtjs/seo@nuxtjs/sitemap@nuxtjs/robots 与合理的默认 `<head>` 元信息。
- 可扩展的后台:提供基础的「仪表盘 / 内容列表 / 模态编辑器」结构,便于后续对接真实后端。
- 零后端即可上线:作为纯内容站点可静态导出部署,也可运行 SSR 部署。
## 目录结构
关键目录说明:
- `app/`
- `pages/` 路由页面(首页、新闻、活动、名人堂、关于、入会申请、后台等)
- `layouts/` 页面布局(`default``admin-dashboard`
- `components/` 组件(首页模块、后台弹窗、输入组件等)
- `assets/css/` 样式Tailwind 入口与 Markdown 样式)
- `composables/` 可复用逻辑(日期格式化、国家数据等)
- `data/` 静态数据(如国家拨号表)
- `content/` Markdown 内容(`news/``events/``hall-of-fames/`
- `content.config.ts` 内容集合与字段 Schema 定义
- `public/` 公开静态资源Logo、封面图、音频、图片等
- `nuxt.config.ts` Nuxt 主配置模块、SEO、站点信息等
- `docs/` 产品与信息架构文档
## 功能概览
- 首页:英雄区 + 最新新闻 + 活动卡片 + 名人堂预览 + 捐赠占位模块
- 新闻列表与详情Markdown 渲染支持封面、标签、SEO 字段)
- 活动:列表与详情(日期/地点/封面,支持嵌入媒体)
- 名人堂:人物详情 + 图集 + 外链视频/媒体嵌入
- 关于页面:会徽含义、校歌音频等
- 入会申请UI 表单(当前为占位,未连通后端)
- 后台(占位):仪表盘、会员籍管理(示例数据)、新闻/活动/名人堂入口与新建弹窗(`md-editor-v3`
## 环境要求
- Node.js ≥ 18.20 或 20.x推荐 20 LTS
- 包管理器pnpm推荐/ npm / yarn / bun
启用 pnpm建议
```bash
# npm
npm install
corepack enable
corepack prepare pnpm@latest --activate
```
# pnpm
## 本地开发
安装依赖:
```bash
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
启动开发服务器(默认 http://localhost:3000
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
## 构建与预览
SSR 构建与本地预览:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
静态导出(适合静态站部署,如 Netlify/Cloudflare Pages/Nginx 静态目录):
```bash
pnpm generate
# 产物在 .output/public (仓库中有 dist -> .output/public 的软链)
```
## 部署指引
- 静态托管:执行 `pnpm generate` 后,将 `.output/public`(或 `dist` 软链)上传到任意静态空间(如 Cloudflare Pages、Netlify、GitHub Pages、Nginx 静态目录)。
- Node SSR执行 `pnpm build` 后,以 Node 方式运行 `.output/server/index.mjs`,或使用平台的 Nuxt 运行时适配Vercel/Netlify Functions/自托管 Node
- SEO请在 `nuxt.config.ts``site.url``app.head` 中更新为你自己的域名与元信息。
## 内容写作与数据结构
项目使用 `@nuxt/content` 管理内容,字段规则见 `content.config.ts`。在对应目录新增 Markdown 文件即可出现在站点中。
示例:新闻(`content/news/20251001-official-web-launch.md`
```md
---
title: "永中校友会官网正式上线"
date: "2025-10-01"
updated: "2025-10-01"
author: "作者名"
description: "用于列表摘要与 SEO 的简短说明"
cover: "/news/20251001-official-web-launch/Screenshot.png"
tags: ["活动", "公告"]
category: "通知"
highlight: true
seoTitle: "SEO 标题"
seoDescription: "SEO 描述"
ogImage: "/images/og/news-launch.jpg"
---
正文使用 Markdown 书写,支持图片、表格、引用与 HTML 块。
```
示例:活动(`content/events/20250927-return-to-school.md`
```md
---
title: "活动标题"
subtitle: "活动副标题"
date: "2025-09-27"
location: "地点名称"
cover: "/events/20250927-return-to-school/event-photo-1.jpg"
---
活动详情正文...
```
示例:名人堂(`content/hall-of-fames/he-si-rong.md`
```md
---
name: "人物姓名"
photo: "/hall-of-fame/he-si-rong/人物头像.png"
title: "人物头衔"
description: "简短介绍"
gallery:
[
"/hall-of-fame/he-si-rong/图1.webp",
"/hall-of-fame/he-si-rong/图2.webp"
]
---
正文可包含媒体嵌入。
```
## 定制与配置
- 站点信息与 SEO`nuxt.config.ts`
- `app.head.titleTemplate` 与默认 `title`
- Meta、OG、Twitter 卡片默认值
- `site.url`(用于站点地图与 SEO
- 导航与页脚:`app/layouts/default.vue`
- 样式与主题色:`app/assets/css/main.css`Tailwind v4 与自定义 CSS 变量)
- Markdown 渲染样式:`app/assets/css/markdown.css`
- 首页区块:`app/components/index/*.vue`
- TikTok 嵌入组件:`app/components/TikTokEmbed.vue`
- 后台布局与侧边导航:`app/layouts/admin-dashboard.vue``app/composables/useDashboardSidebarLinks.ts`
- 分析脚本:`public/analytics.js`(默认使用 Umami若不需要可在 `nuxt.config.ts` 中移除 `<script src="/analytics.js">` 注入)
## 后台与数据(现状)
- 后台页面为 UI 脚手架,方便未来接入真实后端(当前示例页使用占位数据或仅展示结构)。
- 成员管理页使用示例 API`jsonplaceholder`)占位,待替换为校友会实际后端服务。
- 入会申请页面为 UI 表单与校验演示,暂未接入存储与流程(页面顶部已加“功能未开放”提示遮罩)。
若需接入真实后端,建议:
- 认证与权限OAuth/JWT + 角色权限
- 数据库存储PostgreSQL / MySQL缓存Redis
- 内容发布:保留 Markdown/Content 模式,或引入 Headless CMS如 Strapi/Directus
## 开发者指南
- 代码风格TypeScript + Nuxt 约定式目录;组件与页面尽量保持职责单一
- 内容 schema若新增内容类型请在 `content.config.ts` 中定义并校验字段
- 资源放置:公共图片/音频/图标放在 `public/`,内容相关配图走 `public/{模块}/...`
- 性能与无障碍:优先图片懒加载、合理的语义化标签与可访问性属性
## 路线图Roadmap
- [ ] 入会申请流程与后台审核对接
- [ ] 后台:内容 CRUD新闻/活动/名人堂)与草稿/发布流
- [ ] 捐赠模块与支付对接
- [ ] 多语言与国际化i18n
- [ ] 校友企业/招聘板块
- [ ] 活动报名、签到与相册
## 贡献指南
欢迎 Issue 与 PR
1) Fork 本仓库并新建分支
2) 本地开发与验证(`pnpm dev`
3) 提交 PR 并说明修改动机与影响范围
内容贡献:
- 新增/修改 Markdown 请遵循 `content.config.ts` 中的 schema 字段
- 提交前请自查封面路径与日期格式是否规范
## 安全与隐私
- 如发现安全问题,请通过电子邮件私下报告给维护者,并避免公开披露(可在此处填写邮箱或表单链接)。
- 分析脚本Umami可选启用默认文件位于 `public/analytics.js`,请替换为你自己的域名与 `website-id`,或移除注入脚本。
## 许可证
本项目计划以开源方式发布。建议选择并添加合适的开源许可证(如 MIT、Apache-2.0)。
- 若你决定使用 MIT请在仓库根目录新增 `LICENSE` 文件并写明 MIT 许可条款。
- 在未选择明确许可证前,请勿对外分发未授权的二进制或闭源衍生物。
## 致谢
- 设计与技术支持Tootaio Studio
- 内容与资料:永平中学校友会
- 技术框架与生态Nuxt 团队与社区、@nuxt/ui、Tailwind CSS
—— 连接校友 · 传承精神 ——

View File

@@ -1,9 +1,36 @@
@import "./markdown.css";
/* @import "./markdown.css"; */
@import "tailwindcss";
@import "@nuxt/ui";
@theme {
--color-primary: #fb9e3a;
--color-primary-50: #fff8f0;
--color-primary-100: #feecd8;
--color-primary-200: #fdd4ab;
--color-primary-300: #fbb674;
--color-primary-400: #fb9e3a;
--color-primary-500: #f9840a;
--color-primary-600: #dd6b06;
--color-primary-700: #b74f07;
--color-primary-800: #943e0d;
--color-primary-900: #7a340e;
--color-primary-950: #461902;
--color-secondary: #fcef91;
--color-secondary-50: #fffeea;
--color-secondary-100: #fffbc5;
--color-secondary-200: #fcef91;
--color-secondary-300: #f9df53;
--color-secondary-400: #f6ca24;
--color-secondary-500: #e6b010;
--color-secondary-600: #c6880a;
--color-secondary-700: #9e610c;
--color-secondary-800: #834d12;
--color-secondary-900: #6f3f15;
--color-secondary-950: #412008;
}
.dark {
--color-gray-800: oklch(85% 0 275);
}

View File

@@ -1,565 +0,0 @@
/* markdown.css
默认:明亮 / 白色背景主题
同时提供:.dark .prose 覆盖(如需启用 class-based dark 模式)
依赖:全局定义的 CSS 变量 --color-primary 和 --color-secondary
*/
/* 平滑滚动 */
html {
scroll-behavior: smooth;
}
/* ---------- 默认明亮主题Light ---------- */
/* 美化 prose 内容样式 */
.prose {
@apply text-gray-800 leading-relaxed;
font-feature-settings:
"kern" 1,
"liga" 1;
color: inherit;
}
/* 标题层级 */
.prose h1 {
@apply text-4xl font-bold mt-8 mb-6 pb-4 border-b border-gray-200;
background-image: linear-gradient(
90deg,
var(--color-primary),
var(--color-secondary)
);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
background-size: 200% 200%;
animation: gradientShift 3s ease infinite;
}
.prose h2 {
@apply text-3xl font-bold text-gray-900 mt-10 mb-5 pb-3 border-b border-gray-200 relative;
}
.prose h2::before {
content: "";
@apply absolute bottom-0 left-0 w-12 h-0.5 rounded-full;
background-image: linear-gradient(
90deg,
var(--color-primary),
var(--color-secondary)
);
}
.prose h3 {
@apply text-2xl font-semibold text-gray-800 mt-8 mb-4;
color: rgba(31, 41, 55, 0.95);
}
.prose h4 {
@apply text-xl font-semibold text-gray-700 mt-7 mb-3;
}
.prose h5 {
@apply text-lg font-medium text-gray-700 mt-6 mb-3;
}
.prose h6 {
@apply text-base font-medium text-gray-600 mt-5 mb-2 italic;
}
/* 段落和文本 */
.prose p {
@apply text-gray-700 leading-relaxed mb-5;
text-align: justify;
}
.prose strong {
@apply font-bold px-1 rounded;
background: linear-gradient(
90deg,
rgba(252, 239, 145, 0.22),
rgba(251, 158, 58, 0.12)
);
color: rgba(31, 41, 55, 0.95);
}
.prose em {
@apply italic px-1 rounded;
color: var(--color-secondary);
background: rgba(251, 158, 58, 0.08);
}
.prose del {
@apply line-through px-1 rounded;
color: #ef4444;
background: rgba(239, 68, 68, 0.06);
}
/* 链接 */
.prose a {
@apply font-medium relative transition-all duration-300;
color: var(--color-secondary);
text-decoration: underline;
text-underline-offset: 3px;
}
.prose a:hover {
color: var(--color-primary);
transform: translateY(1px);
}
.prose a::after {
content: "";
@apply absolute bottom-0 left-0 w-0 h-0.5 transition-all duration-300;
background: linear-gradient(
90deg,
var(--color-primary),
var(--color-secondary)
);
}
.prose a:hover::after {
@apply w-full;
}
/* 外部链接图标 */
.prose a[href^="http"]::before {
content: "↗";
@apply inline-block mr-1 text-xs translate-y-[2px] opacity-70;
}
.prose h1 a,
.prose h2 a,
.prose h3 a,
.prose h4 a,
.prose h5 a,
.prose h6 a {
text-decoration: none;
}
/* 列表 */
.prose ul {
@apply list-none space-y-3 mb-6;
}
.prose ul li {
@apply relative pl-6;
}
.prose ul li::before {
content: "";
@apply absolute left-0 top-3 w-1.5 h-1.5 rounded-full;
background: var(--color-secondary);
}
.prose ol {
@apply list-decimal list-inside space-y-3 mb-6;
counter-reset: list-counter;
}
.prose ol li {
@apply relative pl-8;
counter-increment: list-counter;
}
.prose ol li::before {
content: counter(list-counter);
@apply absolute left-0 top-0 w-6 h-6 text-white text-xs rounded-full flex items-center justify-center font-bold;
background-image: linear-gradient(
135deg,
var(--color-primary),
var(--color-secondary)
);
}
/* 代码块(浅色风格) */
.prose pre {
@apply rounded-xl p-6 my-8 border shadow-sm overflow-x-auto;
border: 1px solid rgba(229, 231, 235, 1); /* gray-200 */
background: linear-gradient(180deg, #ffffff, #f8fafc);
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.04);
backdrop-filter: blur(6px);
}
.prose code {
@apply px-2 py-1 rounded text-sm font-mono;
background: rgba(243, 244, 246, 0.8); /* gray-50-ish */
border: 1px solid rgba(229, 231, 235, 1);
color: #0f172a;
}
.prose pre code {
@apply bg-transparent p-0 text-current border-none;
color: #0f172a;
}
/* 引用块(浅色友好) */
.prose blockquote {
@apply pl-6 italic text-gray-700 my-8 py-4 pr-6 rounded-r-xl relative overflow-hidden;
border-left: 4px solid transparent;
border-image: linear-gradient(
to bottom,
rgba(252, 239, 145, 0.9),
rgba(251, 158, 58, 0.9)
)
1;
background: linear-gradient(
90deg,
rgba(252, 239, 145, 0.06),
rgba(255, 255, 255, 0)
);
}
.prose blockquote::before {
content: '"';
@apply absolute -top-4 -left-2 text-6xl opacity-20 font-serif;
color: var(--color-primary);
}
.prose blockquote p {
@apply mb-3 last:mb-0 relative z-10;
}
/* 图片 */
.prose img {
@apply rounded-2xl my-8 mx-auto transition-all duration-500 border-2;
border-color: rgba(226, 232, 240, 0.6); /* gray-200 */
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.04);
}
.prose img:hover {
@apply scale-[1.02];
border-color: var(--color-secondary);
}
.prose figure {
@apply my-8 text-center;
}
.prose figcaption {
@apply text-sm text-gray-500 mt-3 italic;
}
/* 表格 */
.prose table {
@apply w-full border-collapse my-8 text-sm rounded-xl overflow-hidden shadow-sm;
}
.prose thead {
background-image: linear-gradient(
90deg,
rgba(252, 239, 145, 0.18),
rgba(251, 158, 58, 0.12)
);
}
.prose th {
@apply border px-6 py-4 text-left font-bold text-gray-800 text-sm uppercase tracking-wider;
border-color: rgba(226, 232, 240, 0.6);
background: rgba(250, 250, 250, 0.8);
}
.prose td {
@apply border px-6 py-4 text-gray-700;
border-color: rgba(226, 232, 240, 0.4);
}
.prose tr:nth-child(even) {
@apply bg-gray-50;
}
.prose tr:hover {
background: rgba(251, 158, 58, 0.06);
transition: background 0.2s;
}
/* 分割线 */
.prose hr {
@apply border-gray-200 my-12 relative;
}
.prose hr::before {
content: "";
@apply absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-8 h-8 rounded-full opacity-20;
background-image: linear-gradient(
90deg,
var(--color-primary),
var(--color-secondary)
);
}
/* 任务列表checkbox */
.prose input[type="checkbox"] {
@apply mr-3 rounded w-5 h-5 transition-all duration-200;
background: #fff;
border: 1px solid rgba(226, 232, 240, 0.8);
}
.prose input[type="checkbox"]:checked {
background-image: linear-gradient(
90deg,
var(--color-primary),
var(--color-secondary)
);
border: none;
}
.prose .task-list-item {
@apply list-none pl-0 flex items-start;
}
.prose .task-list-item input[type="checkbox"] {
@apply mt-0.5 flex-shrink-0;
}
/* 强调和标记 */
.prose mark {
@apply px-2 py-1 rounded font-medium;
background: linear-gradient(
120deg,
rgba(252, 239, 145, 0.22),
rgba(251, 158, 58, 0.18)
);
color: #8a4b00;
}
/* 键盘按键 */
.prose kbd {
@apply border rounded-lg px-3 py-1.5 text-sm font-mono shadow-sm;
background: rgba(247, 249, 250, 0.9);
border-color: rgba(226, 232, 240, 0.8);
color: #0f172a;
}
/* 动画定义 */
@keyframes gradientShift {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* 滚动条(浅色) */
.prose pre::-webkit-scrollbar {
@apply h-2;
}
.prose pre::-webkit-scrollbar-track {
@apply rounded-full;
background: rgba(243, 244, 246, 0.9);
}
.prose pre::-webkit-scrollbar-thumb {
border-radius: 9999px;
background-image: linear-gradient(
90deg,
var(--color-primary),
var(--color-secondary)
);
}
/* 移动端优化 */
@media (max-width: 768px) {
.prose {
@apply text-base;
}
.prose h1 {
@apply text-3xl;
}
.prose h2 {
@apply text-2xl;
}
.prose h3 {
@apply text-xl;
}
.prose pre {
@apply p-4;
}
}
/* 打印优化 */
@media print {
.prose {
@apply text-black;
}
.prose a {
@apply text-black no-underline;
}
.prose pre {
@apply bg-gray-100 border border-gray-300;
}
}
/* ---------- 可选:深色模式覆盖(.dark class 优先) ---------- */
/* 如果你使用 Tailwind 的 class-based dark 模式(<html class="dark">.dark .prose 会生效 */
/* 也可替换为 @media (prefers-color-scheme: dark) {...} 来自动跟随系统 dark 模式 */
.dark .prose {
@apply text-gray-200;
color: inherit;
}
.dark .prose h1 {
border-color: rgba(55, 65, 81, 0.5);
}
.dark .prose h2 {
color: #fff;
border-color: rgba(55, 65, 81, 0.4);
}
.dark .prose p {
@apply text-gray-300;
}
.dark .prose pre {
border: 1px solid rgba(55, 65, 81, 0.6);
background: linear-gradient(
135deg,
rgba(17, 24, 39, 0.9),
rgba(31, 41, 55, 0.9)
);
box-shadow: 0 8px 30px rgba(2, 6, 23, 0.6);
}
.dark .prose code {
background: rgba(31, 41, 55, 0.6);
border: 1px solid rgba(55, 65, 81, 0.5);
color: #e6eef8;
}
.dark .prose blockquote {
border-image: linear-gradient(
to bottom,
var(--color-primary),
var(--color-secondary)
)
1;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.02),
rgba(255, 255, 255, 0)
);
color: rgba(255, 255, 255, 0.9);
}
.dark .prose table thead {
background-image: linear-gradient(
90deg,
rgba(252, 239, 145, 0.06),
rgba(251, 158, 58, 0.04)
);
}
.dark .prose tr:nth-child(even) {
background: rgba(255, 255, 255, 0.02);
}
.dark .prose td,
.dark .prose th {
color: rgba(255, 255, 255, 0.9);
}
.dark .prose img {
box-shadow: 0 10px 30px rgba(2, 6, 23, 0.6);
border-color: rgba(55, 65, 81, 0.5);
}
.dark .prose kbd {
background: rgba(31, 41, 55, 0.7);
border-color: rgba(55, 65, 81, 0.6);
color: #e6eef8;
}
/* 滚动条(深色) */
.dark .prose pre::-webkit-scrollbar-track {
background: rgba(17, 24, 39, 0.8);
}
.dark .prose pre::-webkit-scrollbar-thumb {
background-image: linear-gradient(
90deg,
var(--color-primary),
var(--color-secondary)
);
}
@media (prefers-color-scheme: dark) {
.prose {
@apply text-gray-200;
color: inherit;
}
.prose h1 {
border-color: rgba(55, 65, 81, 0.5);
}
.prose h2 {
color: #fff;
border-color: rgba(55, 65, 81, 0.4);
}
.prose p {
@apply text-gray-300;
}
.prose pre {
border: 1px solid rgba(55, 65, 81, 0.6);
background: linear-gradient(
135deg,
rgba(17, 24, 39, 0.9),
rgba(31, 41, 55, 0.9)
);
box-shadow: 0 8px 30px rgba(2, 6, 23, 0.6);
}
.prose code {
background: rgba(31, 41, 55, 0.6);
border: 1px solid rgba(55, 65, 81, 0.5);
color: #e6eef8;
}
.prose blockquote {
border-image: linear-gradient(
to bottom,
var(--color-primary),
var(--color-secondary)
)
1;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.02),
rgba(255, 255, 255, 0)
);
color: rgba(255, 255, 255, 0.9);
}
.prose table thead {
background-image: linear-gradient(
90deg,
rgba(252, 239, 145, 0.06),
rgba(251, 158, 58, 0.04)
);
}
.prose tr:nth-child(even) {
background: rgba(255, 255, 255, 0.02);
}
.prose td,
.prose th {
color: rgba(255, 255, 255, 0.9);
}
.prose img {
box-shadow: 0 10px 30px rgba(2, 6, 23, 0.6);
border-color: rgba(55, 65, 81, 0.5);
}
.prose kbd {
background: rgba(31, 41, 55, 0.7);
border-color: rgba(55, 65, 81, 0.6);
color: #e6eef8;
}
/* 滚动条(深色) */
.prose pre::-webkit-scrollbar-track {
background: rgba(17, 24, 39, 0.8);
}
.prose pre::-webkit-scrollbar-thumb {
background-image: linear-gradient(
90deg,
var(--color-primary),
var(--color-secondary)
);
}
}

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,14 +0,0 @@
<template>
<UPageCTA
class="bg-secondary"
title="支持与捐赠"
description="您的捐赠将用于奖学金、校园建设及校友活动发展。感谢您对母校的支持!"
:links="donationLinks"
/>
</template>
<script lang="ts" setup>
const donationLinks = ref([{ label: "立即捐赠", icon: "mdi:cash" }]);
</script>
<style></style>

View File

@@ -1,34 +1,41 @@
<template>
<!-- 活动模块 -->
<UPageSection title="校友活动" class="bg-gray-100">
<div class="grid md:grid-cols-3 gap-6">
<UPageSection title="校友活动" class="bg-gray-100 dark:bg-slate-800">
<UPageGrid>
<div
v-for="event in events"
:key="event.id"
class="bg-white shadow rounded-xl"
class="bg-white dark:bg-slate-700 shadow rounded-xl"
>
<img :src="event.cover" :alt="event.title" class="rounded-xl" />
<img
:src="event.cover"
:alt="event.title"
class="w-full aspect-video 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">
<p class="text-sm text-gray-600 dark:text-gray-400 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"/>
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">地点{{ event.location }}</p>
<UButton
label="阅读详情"
:to="event.path"
trailing-icon="mdi:book-open-blank-variant-outline"
/>
</div>
</div>
</div>
</UPageGrid>
</UPageSection>
</template>
<script lang="ts" setup>
const { data: events } = await useAsyncData("events", () =>
queryCollection("events").order("date", "DESC").limit(3).all()
queryCollection("events")
.where("draft", "=", false)
.order("date", "DESC")
.limit(3)
.all()
);
</script>

View File

@@ -10,10 +10,10 @@
<img
:src="person.photo"
:alt="person.name"
class="w-40 rounded-full border-primary border-4"
class="w-40 rounded-full border-primary-400 border-4"
/>
<h4 class="text-lg font-bold">{{ person.name }}</h4>
<p class="text-sm text-gray-500">{{ person.title }}</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ person.title }}</p>
</div>
</div>
</UPageSection>

View File

@@ -164,7 +164,7 @@ const errorColor = computed(() => {
<button
@click="goHome"
class="px-5 py-2.5 rounded-md text-white font-medium transition-colors flex items-center gap-2"
style="background-color: var(--color-primary)"
style="background-color: var(--color-primary-400)"
>
<span>返回首页</span>
</button>

View File

@@ -1,28 +1,63 @@
<template>
<UPage>
<UBanner
title="永中校友会 40 周年庆典活动火热筹募中!"
icon="hugeicons:party"
close
:actions="bannerActions"
/>
<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>
<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-400 hidden md:inline"
>
永平中学校友会
</h1>
</div>
</NuxtLink>
</template>
<template #default>
<UNavigationMenu :items="items" content-orientation="vertical" />
</template>
<template #right>
<nav class="space-x-6 hidden md:flex items-center">
<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>
<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>
<template #body>
<UNavigationMenu
:items="items"
orientation="vertical"
class="-mx-2.5"
/>
</template>
</UHeader>
<UMain>
<slot />
<Transition name="page" mode="out-in">
<div>
<NuxtPage />
</div>
</Transition>
</UMain>
<UFooter>
<template #left>
<p>© 2025 永平中学校友会. 保留所有权利.</p>
@@ -39,36 +74,141 @@
>
Tootaio Studio
</a>
<span class="mt-1 text-sm"
>2018 级毕业学长麦祖奕</span
>
<span class="mt-1 text-sm">2018 级毕业学长麦祖奕</span>
</p>
</template>
<template #right>
<a href="#">
<Icon name="mdi-facebook" />
</a>
<a href="#">
<Icon name="mdi-instagram" />
</a>
<a href="#">
<Icon name="mdi-gmail" />
</a>
<UButton
v-for="link in socialLinks"
color="neutral"
variant="ghost"
v-bind="link"
/>
</template>
</UFooter>
</UPage>
</template>
<script setup lang="ts">
import type { NavigationMenuItem } from "@nuxt/ui";
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: "#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") },
{ label: "首页", to: "/" },
{ label: "新闻", to: "/news", active: route.path.startsWith("/news") },
{
label: "会员",
to: "/members",
active: route.path.startsWith("/members"),
},
{
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: "组织架构",
to: "/about/org-structure/20",
active: route.path.startsWith("/about/org-structure"),
icon: "mdi:account-group",
},
{
label: "特殊校友:中补班",
description: "永平中学补习班1956年一封迟来的贴文",
to: "/about/middle-highschool-tuition-class",
active: route.path.startsWith("/about/middle-highschool-tuition-class"),
icon: "mdi:mail",
},
],
},
{
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-400' : '',
]"
>
{{ 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-400">
<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,146 @@
<template>
<UPage class="bg-gradient-to-br from-amber-50 to-amber-100 p-2 md:p-8">
<UPageHero title="特殊校友" description="中补班 (1956年): 一封迟来的貼文" />
<UContainer>
<div class="flex justify-center items-center">
<div
class="p-4 md:p-12 text-justify relative max-w-2xl paper-texture bg-amber-50 shadow-md border border-amber-200 rounded-xl font-letter text-gray-800 leading-relaxed handwritten tracking-wide"
>
<UModal
v-model:open="imagePopup"
title="中补班乙班合影"
description="永平学校中辅乙同学合影 - 一九五六年八月九日"
fullscreen
>
<img
src="/about/中补班乙.jpg"
alt="中补班乙班"
class="sm:absolute sm:-top-25 sm:-right-60 md:right-0 sm:rotate-12 md:w-60 lg:w-80 transition-all cursor-pointer"
/>
<template #body>
<img
src="/about/中补班乙.jpg"
alt="中补班乙班"
class="max-w-full max-h-full object-contain mx-auto"
/>
</template>
</UModal>
<div class="text-xl mb-2 font-bold">1956年永中中补班</div>
<div class="text-lg mb-6 font-semibold"> 两班老校友</div>
<p class="indent-8 mb-4">
各位老同学别来无恙这句问候的话迟到了六十多年想当年大家还是青涩年少六十五年后的今天如果还健在人间想必齿危发秃两鬓斑白如果早已驾鹤西去回顾一坯黄土会否感慨此生凡是尘土必归于尘土
</p>
<p class="indent-8 mb-4">
但这个迟到的讯息现在发出将穿越65年的时间和空间一定要也一定会传到你们的心弦引起共鸣因为它共同的曲调是永中的校歌黉舍巍峨著令名时闻弦诵声
</p>
<p class="indent-8 mb-4">
还健在的老同学请响应这个号召已经移民的请魂兮归来让我们老同学聚在一起见证和欣赏生命天空最后的夕阳红莫道桑榆晚红霞尚满天
</p>
<p class="indent-8 mb-6">
召集人刘用周 电话江回潮 电话马文钦 电话杨青发 电话可以再提名
</p>
<p class="text-right mt-8">2022年4月15日</p>
</div>
</div>
<p class="my-4 text-center font-bold">
中补班同学名册就記忆如下马文钦提供
</p>
<UCard>
<div class="space-x-4 space-y-2 text-center">
<UBadge
v-for="name in schoolMates"
size="xl"
variant="subtle"
class="hover:scale-120 transition-all cursor-pointer"
>{{ name }}</UBadge
>
</div>
</UCard>
<p class="my-4 text-center font-bold">
黄绍楠校长李华光等老师的墨宝赠言尤为珍贵
</p>
<UCarousel
v-slot="{ item }"
:items="giveWords"
class="w-full max-w-xs mx-auto"
arrows
dots
>
<img :src="item" width="320" height="320" class="rounded-lg" />
</UCarousel>
</UContainer>
</UPage>
</template>
<script lang="ts" setup>
useSeoMeta({
title: "致1956年永中中补班 — 永平中学校友会",
ogTitle: "致中补班的一封信",
ogDescription:
"致亲爱的1956年两班老校友虽迟六十余年愿这条讯息穿越时间召集大家重聚共叙曾经校园情怀与夕阳红岁月。",
ogUrl: "https://yphsalumni.org/about/middle-highschool-tuition-class/",
ogType: "article",
ogImage: "/about/中补班乙.jpg",
ogImageAlt: "永平学校中辅乙同学合影",
articlePublishedTime: "2022-04-15T00:00:00+08:00",
author: "永平中学校友会",
});
const imagePopup = ref<boolean>(false);
const schoolMates = ref<string[]>([
"萧泽金",
"黄荣发",
"彭海恩",
"掦楚玉",
"揚楚英",
"林进财",
"马文钦",
"陳玉英",
"黄清蘭",
"池伟青",
"冯金才",
"張波香",
"陈红金",
"黄鳳娇",
"戴成雄",
"黄光兴",
"余新礼",
"刘用周",
"杨青发",
"江回潮",
]);
const giveWords = ref<string[]>([
"/about/黄绍南校长赠言.jpg",
"/about/李华光赠言.jpg",
"/about/某老师赠言.jpg",
"/about/赵老师赠言.jpg",
]);
</script>
<style>
/* @import url("https://fonts.googleapis.com/css2?family=Great+Vibes&family=Noto+Serif+SC:wght@400;500&family=Zhi+Mang+Xing&display=swap"); */
/* 自定义字体风格组合 */
.font-letter {
/* font-family: "Zhi Mang Xing", "Great Vibes", "Noto Serif SC", serif; */
font-weight: 400;
}
/* 添加信纸背景纹理(可换 URL 为你自己的纹理图)*/
.paper-texture {
background-image: radial-gradient(
rgba(255, 255, 255, 0.5) 1px,
transparent 1px
);
background-size: 4px 4px;
}
/* 微微倾斜文字,模拟手写不规整 */
.handwritten {
transform: rotate(-0.3deg);
}
</style>

View File

@@ -0,0 +1,16 @@
<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: "20 届", to: "/about/org-structure/20" }]);
</script>
<style></style>

View File

@@ -0,0 +1,228 @@
<template>
<UPage>
<UPageHeader
:title="`第 ${generation} 届组织架构`"
description="精英汇聚,携手共创卓越未来"
/>
<UPageBody>
<UPageSection
v-for="category in categories"
:key="category"
:title="category"
>
<!-- <h2 class="text-3xl font-extrabold text-center mb-10">领导团队</h2> -->
<UPageGrid class="!grid-cols-1 sm:!grid-cols-2 lg:!grid-cols-4">
<UCard
v-for="person in orgStructure.filter((p) => p.category == category)"
:key="person.name"
>
<template #header>
<h3 class="text-xl font-semibold mb-2">{{ person.position }}</h3>
<div class="text-2xl font-bold">{{ person.name }}</div>
</template>
<template #default>
<div class="text-center">
<img
class="h-40 w-auto object-contain inline rounded-md"
:src="person.photo"
:alt="person.name"
/>
</div>
</template>
<template #footer
><p class="text-muted">{{ person.description }}</p></template
>
</UCard>
</UPageGrid>
</UPageSection>
</UPageBody>
</UPage>
</template>
<script lang="ts" setup>
useSeoMeta({
title: "组织架构",
description:
"永平中学校友会组织架构,领导团队、职能部门与专项部门精英汇聚,携手共创卓越未来。",
keywords:
"永平中学校友会, 校友会组织架构, 领导团队, 职能部门, 专项部门, 理事会成员",
ogTitle: "永平中学校友会组织架构",
ogDescription:
"了解永平中学校友会的领导团队、职能与专项部门,见证校友情谊与组织力量。",
ogImage: "https://img.yphsalumni.org/i/2025/11/28/qq4k4z.png",
ogType: "website",
});
const route = useRoute();
const generation = route.params.slug;
const categories = ref(["领导团队", "职能部门", "专项部门"]);
// TODO: Fetch from api
const orgStructure = ref([
{
name: "李煜斌",
position: "主席",
category: "领导团队",
photo: "https://img.yphsalumni.org/i/2025/11/28/qm2sjz.png",
description:
"全面领导与战略决策,统筹理事会整体工作,确保组织愿景与使命的实现。",
},
{
name: "郑惠忠",
position: "副主席",
category: "领导团队",
photo: "https://img.yphsalumni.org/i/2025/11/28/qm3nod.png",
description: "协助主席制定战略方向,分管外部关系与合作伙伴发展。",
},
{
name: "陈月丽",
position: "副主席",
category: "领导团队",
photo: "https://img.yphsalumni.org/i/2025/11/28/qm41qp.png",
description: "协助主席处理内部事务,分管人力资源与组织文化建设。",
},
{
name: "余东照",
position: "副主席",
category: "领导团队",
photo: "https://img.yphsalumni.org/i/2025/11/28/qm4izj.png",
description: "协助主席推动重点项目,分管创新业务与发展规划。",
},
{
name: "蓝宜宏",
position: "秘书",
category: "职能部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qmrsai.png",
description: "文件管理、会议组织、行政协调与日常事务处理。",
},
{
name: "陈冠宇",
position: "副秘书",
category: "职能部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qms4el.png",
description: "协助秘书处理文档、会议安排与行政支持工作。",
},
{
name: "余粝栎",
position: "财政",
category: "职能部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qmsnhy.png",
description: "全面财务管理、预算控制、资金运作与财务报告。",
},
{
name: "曾国书",
position: "副财政",
category: "职能部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qmslmm.jpg",
description: "协助财政处理账务、报销与日常财务管理工作。",
},
{
name: "颜志宝",
position: "总务",
category: "职能部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qnfyb0.png",
description: "物资采购、资产管理、场地协调与后勤保障服务。",
},
{
name: "温敬富",
position: "副总务",
category: "职能部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qnglal.png",
description: "协助总务处理物资管理、设备维护与后勤支持。",
},
{
name: "胡少菲",
position: "康乐",
category: "职能部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qnre6r.png",
description: "文化活动策划、康乐项目组织与会员联谊活动。",
},
{
name: "林剑宝",
position: "副康乐",
category: "职能部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qns52y.png",
description: "协助文康组织文体活动、兴趣小组与社交聚会。",
},
{
name: "余美枫",
position: "福利主任",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qodchd.png",
description: "会员福利规划、实施与评估,提升会员满意度。",
},
{
name: "郑惠国",
position: "副福利主任",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qoe3ua.png",
description: "协助会员福利规划、实施与评估,提升会员满意度。",
},
{
name: "程靖原",
position: "会员籍暨咨询管理",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qof326.png",
description: "会员信息管理、咨询服务提供与会员关系维护。",
},
{
name: "许福源",
position: "副会员籍暨咨询管理",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qog4aa.png",
description: "协助会员管理、咨询响应与信息系统维护。",
},
{
name: "麦祖奕",
position: "副会员籍暨咨询管理",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qoo8lx.png",
description: "协助会员管理、咨询响应与信息系统开发。",
},
{
name: "许浩铭",
position: "奖励金主任",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qoos64.png",
description: "奖励金制度设计、评审组织与发放管理。",
},
{
name: "黄楚茵",
position: "副奖励金主任",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qoxrim.png",
description: "协助奖励金评审、资料审核与发放流程管理。",
},
{
name: "陈廷添",
position: "理事",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qoyjo8.png",
description: "参与理事会决策、专项工作推进与建议提出。",
},
{
name: "邱康勤",
position: "理事",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qp8tni.png",
description: "参与理事会决策、专项工作推进与建议提出。",
},
{
name: "张键国",
position: "稽查",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qp99ia.png",
description: "内部审计、合规监督与风险控制管理。",
},
{
name: "许皓杰",
position: "稽查",
category: "专项部门",
photo: "https://img.yphsalumni.org/i/2025/11/28/qp9l0z.png",
description: "内部审计、合规监督与风险控制管理。",
},
]);
</script>
<style></style>

View File

@@ -1,24 +1,19 @@
<template>
<div class="min-h-screen bg-white">
<!-- 装饰性背景元素浅色柔和光晕 -->
<div class="fixed inset-0 overflow-hidden pointer-events-none">
<div class="absolute -top-40 -right-40 w-80 h-80 bg-blue-100/40 rounded-full blur-3xl"></div>
<div class="absolute -bottom-40 -left-40 w-80 h-80 bg-purple-100/40 rounded-full blur-3xl"></div>
<div
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-96 h-96 bg-cyan-100/40 rounded-full blur-3xl">
</div>
</div>
<div class="min-h-screen">
<section class="relative py-16 px-4 sm:px-6 lg:px-8">
<div class="container mx-auto max-w-4xl">
<!-- 内容卡片 -->
<div class="relative">
<!-- 卡片装饰边框浅色渐变 -->
<div class="absolute inset-0 bg-gradient-to-r from-blue-100 to-purple-100 rounded-2xl blur-sm"></div>
<!-- <div class="absolute inset-0 bg-linear-to-r from-blue-100 to-purple-100 rounded-2xl blur-sm"></div> -->
<div class="relative bg-white rounded-xl border border-gray-200 shadow-xl overflow-hidden">
<div
class="relative rounded-xl border border-gray-200 shadow-xl overflow-hidden"
>
<!-- 顶部装饰条明亮渐变 -->
<div class="h-1 bg-gradient-to-r from-blue-400 via-purple-400 to-cyan-400"></div>
<div
class="h-1 bg-linear-to-r from-blue-400 via-purple-400 to-cyan-400"
></div>
<div class="p-8 sm:p-10 lg:p-12">
<!-- 内容渲染器 -->
@@ -26,16 +21,32 @@
<ContentRenderer :value="event ?? {}">
<template #empty>
<div class="text-center py-16">
<div class="inline-flex items-center justify-center w-16 h-16 bg-blue-100 rounded-full mb-4">
<svg class="w-8 h-8 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
</circle>
<path class="opacity-75" fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
</path>
<div
class="inline-flex items-center justify-center w-16 h-16 bg-blue-100 rounded-full mb-4"
>
<svg
class="w-8 h-8 text-blue-500 animate-spin"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
</div>
<p class="text-gray-700 text-lg font-medium">内容加载中...</p>
<p class="text-gray-700 text-lg font-medium">
内容加载中...
</p>
<p class="text-gray-400 text-sm mt-2">请稍等片刻</p>
</div>
</template>
@@ -50,16 +61,43 @@
</template>
<script lang="ts" setup>
const route = useRoute()
const { data: event } = await useAsyncData('event-detail', () =>
queryCollection('events')
.path(`/events/${route.params.slug}`)
.first()
)
const route = useRoute();
const { data: event } = await useAsyncData("event-detail", () =>
queryCollection("events").path(`/events/${route.params.slug}`).first()
);
useHead({
title: event.value?.title
})
if (event.value) {
// 1. 确定图片:优先用 ogImage没有就用 cover
const shareImage = event.value.ogImage || event.value.cover;
// 2. 确定标题和描述:优先用 seoTitle没有就用 title
const shareTitle = event.value.seoTitle || event.value.title;
const shareDesc = event.value.seoDescription || event.value.description;
// 3. 注入 SEO
useSeoMeta({
// 基础
title: shareTitle,
description: shareDesc,
// Open Graph (Facebook / WhatsApp)
ogTitle: shareTitle,
ogDescription: shareDesc,
ogImage: shareImage,
ogType: "article",
// Twitter Card
twitterCard: "summary_large_image",
twitterTitle: shareTitle,
twitterDescription: shareDesc,
twitterImage: shareImage,
});
// 如果你用了 nuxt-og-image 模块生成动态图
if (shareImage) {
defineOgImage({ url: shareImage });
}
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,34 @@
<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")
.where("draft", "=", false)
.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,13 +1,9 @@
<template>
<div>
<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;
"
:class="heroClass"
:style="heroStyle"
title="连接校友 · 传承精神"
description="马来西亚柔佛永平中学校友会官方网站"
:links="heroCta"
@@ -20,19 +16,44 @@
<IndexEvents />
<IndexHallOfFame />
<IndexDonate />
<IndexAbout />
</div>
<!-- 捐赠模块 -->
<UPageCTA
class="bg-secondary-200 dark:bg-secondary-900"
title="支持与捐赠(功能暂未开放)"
description="您的捐赠将用于奖学金、校园建设及校友活动发展。感谢您对母校的支持!"
:links="donationLinks"
/>
</UPage>
</template>
<script lang="ts" setup>
import type { BlogPostProps, PageCardProps } from "@nuxt/ui";
import type { BlogPostProps } from "@nuxt/ui";
useSeoMeta({
title: "首页",
});
const colorMode = useColorMode()
const heroClass = 'bg-cover bg-center'
const heroStyle = computed(() => ({
backgroundImage: colorMode.value === 'dark'
? 'url("https://img.yphsalumni.org/i/2025/11/28/qzxrpq.png")'
: 'url("https://img.yphsalumni.org/i/2025/11/28/qk9fe8.png")',
backgroundPositionY: '-40px',
backgroundRepeat: 'no-repeat',
backgroundColor: colorMode.value === 'dark'
? 'rgba(0, 0, 0, 0.5)'
: 'rgba(255, 255, 255, 0.5)',
backgroundBlendMode: colorMode.value === 'dark' ? 'darken' : 'lighten'
}))
const heroCta = ref([
{
label: "立即加入我们",
to: "/join-us",
icon: "mdi:account-plus",
"data-umami-event": "Join Us Btn Click",
},
]);
@@ -56,11 +77,9 @@ const newsPost = computed<BlogPostProps[]>(() =>
);
// ========================================================
// 活动模块
// 捐赠模块
// ========================================================
const { data: events } = await useAsyncData("events", () =>
queryCollection("events").order("date", "DESC").limit(3).all()
);
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-400">
<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>

116
app/pages/members/index.vue Normal file
View File

@@ -0,0 +1,116 @@
<template>
<UPage>
<UContainer>
<UPageHeader title="会员总览" description="查询每个会员的信息" />
<UPageBody>
<UTable :data="members" :columns="columns" />
</UPageBody>
</UContainer>
</UPage>
</template>
<script lang="ts" setup>
import type { TableColumn } from "@nuxt/ui";
import { z } from "zod";
useSeoMeta({
title: "会员总览",
description:
"永平中学校友会会员总览,查询每位校友的毕业年份、届别、加入年份与现居国家。",
keywords: "永平中学校友会, 校友会员, 毕业届别, 会员名录, 校友查询",
ogTitle: "永平中学校友会会员总览",
ogDescription: "浏览永平中学校友会会员资料,了解各届校友的分布与加入年份。",
// ogImage: "/members/ogImage.png",
ogType: "website",
});
const MemberSchema = z.object({
memberId: z.string(),
chineseName: z.string(),
englishName: z.string(),
// ic: z.string(),
// mobile: z.string(),
// home: z.string(),
// email: z.string(),
graduateLevel: z.string(),
graduateYear: z.string(),
// marriageNtatus: z.string(),
livingCountry: z.string(),
// addressLine1: z.string(),
// addressLine2: z.string(),
// addressLine3: z.string(),
joinedYear: z.string(),
// receiptNumber: z.string(),
});
type Member = z.infer<typeof MemberSchema>;
const { data: members } = await useAsyncData("members", async () => {
const file = await queryCollection("members").first();
// ✅ 关键点:取 meta.body
return MemberSchema.array().parse(file?.meta.body);
});
const columns: TableColumn<Member>[] = [
{
accessorKey: "memberId",
header: "会员编号",
},
{
accessorKey: "chineseName",
header: "中文姓名",
},
{
accessorKey: "englishName",
header: "英文姓名",
},
{
accessorKey: "graduateYear",
header: "毕业/离校年份",
},
{
accessorKey: "graduateLevel",
header: "毕业/离校届别",
cell: ({ row }) => {
switch (row.original.graduateLevel) {
case "j":
// 初中毕业
// 如果 row.original.graduateYear 不能转换成数字,就写成初中毕业
// 否则计算届别
return isNaN(Number(row.original.graduateYear)) || row.original.graduateYear.trim() == ""
? "初中毕业"
: `初中第 ${Number(row.original.graduateYear) - 1958}`;
case "s":
// 高中毕业
return isNaN(Number(row.original.graduateYear)) || row.original.graduateYear.trim() == ""
? "高中毕业"
: `高中第 ${Number(row.original.graduateYear) - 1965}`;
case "dj1":
return "初一肆业";
case "dj2":
return "初二肆业";
case "dj3":
return "初三肆业";
case "ds1":
return "高一肆业";
case "ds2":
return "高二肆业";
case "ds3":
return "高三肆业";
default:
break;
}
},
},
{
accessorKey: "joinedYear",
header: "加入年份",
},
{
accessorKey: "livingCountry",
header: "现居国家",
},
];
</script>
<style></style>

View File

@@ -18,16 +18,43 @@
</template>
<script lang="ts" setup>
const route = useRoute()
const { data: n } = await useAsyncData('new-detail', () =>
queryCollection('news')
.path(`/news/${route.params.slug}`)
.first()
)
const route = useRoute();
const { data: n } = await useAsyncData("new-detail", () =>
queryCollection("news").path(`/news/${route.params.slug}`).first()
);
useHead({
title: n.value?.title
})
if (n.value) {
// 1. 确定图片:优先用 ogImage没有就用 cover
const shareImage = n.value.ogImage || n.value.cover;
// 2. 确定标题和描述:优先用 seoTitle没有就用 title
const shareTitle = n.value.seoTitle || n.value.title;
const shareDesc = n.value.seoDescription || n.value.description;
// 3. 注入 SEO
useSeoMeta({
// 基础
title: shareTitle,
description: shareDesc,
// Open Graph (Facebook / WhatsApp)
ogTitle: shareTitle,
ogDescription: shareDesc,
ogImage: shareImage,
ogType: "article",
// Twitter Card
twitterCard: "summary_large_image",
twitterTitle: shareTitle,
twitterDescription: shareDesc,
twitterImage: shareImage,
});
// 如果你用了 nuxt-og-image 模块生成动态图
if (shareImage) {
defineOgImage({ url: shareImage });
}
}
</script>
<style></style>
<style></style>

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

@@ -1,4 +1,5 @@
import { defineContentConfig, defineCollection, z } from "@nuxt/content";
import { asSeoCollection } from "@nuxtjs/seo/content";
export default defineContentConfig({
collections: {
@@ -12,31 +13,35 @@ export default defineContentConfig({
date: z.coerce.date(),
location: z.string(),
cover: z.string().url(),
draft: z.boolean().optional().default(false),
}),
}),
// 新闻集合
news: defineCollection({
type: "page",
source: "news/*.md",
schema: z.object({
title: z.string(),
date: z.coerce.date(),
updated: z.coerce.date().optional(),
author: z.string(),
description: z.string(),
cover: z.string().optional(),
tags: z.array(z.string()).optional(),
category: z.enum(["活动", "通知", "招聘", "博客"]).optional(),
highlight: z.boolean().optional(),
seoTitle: z.string().optional(),
seoDescription: z.string().optional(),
ogImage: z.string().optional(),
}),
}),
news: defineCollection(
asSeoCollection({
type: "page",
source: "news/*.md",
schema: z.object({
title: z.string(),
date: z.coerce.date(),
updated: z.coerce.date().optional(),
author: z.string(),
description: z.string(),
cover: z.string().optional(),
tags: z.array(z.string()).optional(),
category: z.enum(["活动", "通知", "招聘", "博客"]).optional(),
highlight: z.boolean().optional(),
seoTitle: z.string().optional(),
seoDescription: z.string().optional(),
ogImage: z.string().optional(),
draft: z.boolean().optional().default(false),
}),
})
),
// 名人堂
hallOfFames: defineCollection({
type: "page",
source: "hall-of-fames/*md",
source: "hall-of-fames/*.md",
schema: z.object({
name: z.string(),
photo: z.string().url(),
@@ -45,5 +50,28 @@ export default defineContentConfig({
gallery: z.array(z.string()),
}),
}),
// 会员名册
members: defineCollection({
type: "data",
source: "members/members.csv",
schema: z.object({
// id: z.number(),
// chinese_name: z.string(),
// english_name: z.string(),
// ic: z.string(),
// mobile: z.string(),
// home: z.string(),
// email: z.string(),
// graduate_level: z.string(),
// graduate_year: z.string(),
// marriage_status: z.string(),
// living_country: z.string(),
// address_line_1: z.string(),
// address_line_2: z.string(),
// address_line_3: z.string(),
// joined_year: z.string(),
// receipt_number: z.string(),
}),
}),
},
});

View File

@@ -3,16 +3,16 @@ title: "927 永中 • 钟意你"
subtitle: "永中校友会 39周年庆"
date: "2025-09-27"
location: "永平中学校园"
cover: "/events/20250927-return-to-school/event-photo-1.jpg"
cover: "https://img.yphsalumni.org/i/2025/11/28/qr1l7k.jpg"
---
# 永中校友会39周年会庆午宴 温馨欢聚350人
![来宾打卡照](/events/20250927-return-to-school/event-photo-1.jpg)
![来宾打卡照](https://img.yphsalumni.org/i/2025/11/28/qr1l7k.jpg)
来宾打卡照(星洲日报记者,邹智敏摄)
永平中学校友会于 9 月 27 日举办“927 永中·钟意你”39 周年会庆午宴,吸引约 350 位校友与社会贤达齐聚一堂,共度温馨时光,场面热闹盛大。
![嘉宾合照](/events/20250927-return-to-school/event-photo-2.jpg)
![嘉宾合照](https://img.yphsalumni.org/i/2025/11/28/qr1rrx.jpg)
与会嘉宾向永中校友会献上祝福,左起:许敏捷、王飞兴、林添顺、李煜斌、刘镇东、蓝宜宏、傅庆隆、张嘉群及郑凯聪(星洲日报记者,邹智敏摄)
本次会庆出席嘉宾包括:
@@ -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,81 @@
---
title: "柔联校友会 20 届理事就职典礼"
subtitle: "中学生写作比赛颁奖"
date: "2025-10-09"
location: "富华冷气酒家 2 楼"
cover: "https://img.yphsalumni.org/i/2025/11/28/qryfft.jpg"
---
# 柔联校友会 20 届理事就职典礼
![新届理事合照](https://img.yphsalumni.org/i/2025/11/28/qryfft.jpg)
柔佛州华校校友联合会日前举办第 20 届理事就职典礼与 2025 年 “林赛花教育基金” 柔佛州中学生现场写作比赛颁奖典礼,由马来西亚华校校友会联合总会会长萧成兴担任监誓人。
联合会主席陈星和在致词时强调,华教事业的发展离不开团结与传承,新一届理事会将继续致力于推动华文教育,弘扬中华文化。
## 柔佛州华校校友会联合会
### 第20届理事会
| 职位 | 姓名 |
| ---------- | ---------------------------------------------- |
| 主席 | 陈星和 |
| 副主席 | 陈月丽、陈重存、颜青积 |
| 总务 | 李秀琴 |
| 副总务 | 李煜斌 |
| 财政 | 陈保妤 |
| 副财政 | 吴沺成 |
| 文书 | 莫文豪 |
| 副文书 | 林道民 |
| 教育主任 | 姚理介 |
| 副教育主任 | 陈成祖 |
| 文娱主任 | 郑臻董 |
| 副文娱主任 | 梁德荣 |
| 联络主任 | 郑伟亮 |
| 副联络主任 | 郑清华 |
| 查账 | 涂馨尹、谢祥庆 |
| 理事 | 何国光、吕哲明、罗升隆、黄如龙、郑明裕、温维华 |
---
赞助金移交仪式上,妙妙机构执行董事荘坡政将赞助金移交给峇株文艺协会,以支持文艺出版与推广工作,随后颁发奖状与奖金给 26 名获奖学生。
## 🏆 2025年“林賽花教育基金”柔佛州中學生現場寫作比賽得獎名單
### 🥇 初中组
| 奖项 | 姓名 | 学校 |
| ------ | ------ | ------------------ |
| 特优奖 | 彭艺元 | 居銮中华中学 |
| 特优奖 | 黄守蒽 | 居銮中华中学 |
| 特优奖 | 方乐颖 | 居銮中华中学 |
| 优秀奖 | 余姿亿 | 永平中学 |
| 优秀奖 | 廖玮乐 | 永平中学 |
| 优秀奖 | 卓婧琳 | 峇株吧辖华仁中学 |
| 优秀奖 | 叶贯均 | 峇株吧辖华仁中学 |
| 优秀奖 | 董清清 | 峇株吧辖华仁中学 |
| 优秀奖 | 林婉仪 | 峇株吧辖华仁中学 |
| 优秀奖 | 陈惠敏 | 峇株吧辖华仁中学 |
| 优秀奖 | 曾愉峻 | 居銮中华中学 |
| 优秀奖 | 曾佳滢 | 利丰港培华独立中学 |
| 优秀奖 | 陆颖霏 | 新山宽柔中学 |
---
### 🥈 高中组
| 奖项 | 姓名 | 学校 |
| ------ | ------ | ---------------- |
| 特优奖 | 王祺齐 | 永平中学 |
| 特优奖 | 汤滢菲 | 居銮中华中学 |
| 特优奖 | 陈思宇 | 居銮中华中学 |
| 优秀奖 | 黄婉芯 | 拿督国中 |
| 优秀奖 | 林欣慧 | 拿督国中 |
| 优秀奖 | 蔡欣艳 | 拿督国中 |
| 优秀奖 | 许恩芮 | 永平中学 |
| 优秀奖 | 陈佳萱 | 宽柔中学古来分校 |
| 优秀奖 | 苏新致 | 永平中学 |
| 优秀奖 | 刘祈悦 | 峇株吧辖华仁中学 |
| 优秀奖 | 林淳希 | 峇株吧辖华仁中学 |
| 优秀奖 | 黄子宸 | 新山宽柔中学 |
| 优秀奖 | 苏祐萱 | 新山宽柔中学 |

View File

@@ -0,0 +1,113 @@
---
title: "永平中学第 60 届毕业典礼"
subtitle: "暨初中第 67 届毕业典礼"
date: "2025-11-15"
location: "永平中学邱廉书礼堂"
cover: "https://img.yphsalumni.org/i/2025/11/27/st6hzt.jpg"
draft: false
# SEO 主要字段
description: "永平中学高中第 60 届与初中第 67 届毕业典礼圆满举行,师生与家长齐聚邱廉书礼堂,共同见证学子的重要里程碑,并向荣休教师刘连升老师致以诚挚祝福。"
keywords:
- 永平中学
- 毕业典礼
- 高中第60届
- 初中第67届
- 校园活动
- 刘连升老师
- 永中校友会
# --- SEO 专用字段 (对应你的 Zod Schema) ---
# 对应 schema: seoTitle
# 如果不填,代码里会默认使用 title
seoTitle: "永平中学第 60 届毕业典礼|初中第 67 届毕业典礼"
# 对应 schema: seoDescription
# 如果不填,代码里会默认使用 description
seoDescription: "2025 年永平中学毕业典礼隆重举行,包含师长致辞、奖学金颁发、荣休老师欢送,以及学生精彩演出等精彩环节。"
# 对应 schema: ogImage
# 只有当你想要分享的图片和封面图不一样时才填,否则代码里会默认用 cover
ogImage: "https://img.yphsalumni.org/i/2025/11/27/st6hzt.jpg"
---
# 永平中学高中第 60 届、初中第 67 届毕业典礼圆满举行
![高三毕业生与嘉宾合照](https://img.yphsalumni.org/i/2025/11/27/st6hzt.jpg)
2025 年 11 月 15 日,永平中学邱廉书礼堂见证了一个充满祝福与感动的重要时刻:高中第 60 届与初中第 67 届毕业典礼隆重举行。礼堂内座无虚席,毕业生、家长、师长与嘉宾齐聚一堂,共同庆祝学子们学习旅程的重要里程碑。
经过六年的努力与成长,高三学生正式迎来人生的新阶段;初三毕业生亦迈向更高层次的挑战。典礼在庄严的国州歌声中启幕,掀开充满意义的一天。
## 董事长马彣清博士致词(由代表宣读)
![董事会秘书长黄仰力董事代表致辞](https://img.yphsalumni.org/i/2025/11/27/sndspp.jpg)
因公务繁忙未克出席,马彣清董事长由董事会秘书长黄仰力董事代表致辞。他转达董事长的寄语:“成功从来不是侥幸,而是毅力不断累积的成果。” 同时肯定毕业生在求学路上的坚持,并勉励他们继续以积极态度面对未来。
## 张嘉群校长三项叮咛:以品格与眼界开创未来
![张嘉群校长致辞](https://img.yphsalumni.org/i/2025/11/27/smvqcm.jpg)
张嘉群校长在致辞中寄语毕业生,并提出三项叮咛,期许他们在往后的道路上继续发光发热:
### 叮咛一:永怀初心
* 保持对学习的好奇与热情
* 无论未来走得多远,都不要忘记自己最初的信念
### 叮咛二:中正致远
* 既要正直谦和,也要坚守原则
* 不只追求速度,更追求稳健与长远
### 叮咛三:站在巨人肩膀上成为巨人
* 以董事、父母与师长为榜样
* 持续成长,努力成为对国家、社会与家庭有贡献的人才
## 柔佛州董联会主席兼校务顾问陈大锦先生致辞
![董联会主席兼校务顾问陈大锦先生致辞](https://img.yphsalumni.org/i/2025/11/27/smxf8t.jpg)
陈大锦先生分享了他访问上海中医药大学的见闻,特别是该校完善的奖助学金制度。他希望本校辅导处与升学资讯处能协助学生把握机会,踊跃申请明年 9 月开课的相关奖学金,为升学之路开拓更多可能。
## 欢送刘连升老师光荣荣休
![刘连升老师光荣荣休](https://img.yphsalumni.org/i/2025/11/27/spazik.jpg)
在典礼上,校方特别举行了“欢送刘连升老师荣休”环节,为这位服务永平中学 35 年、深受师生爱戴的资深教师献上诚挚祝福。
刘连升老师在任职期间,以其和蔼可亲的性格与渊博的历史知识,陪伴无数学生走过求学阶段。他不仅课堂上“聊古论今”,从古代帝王先烈谈到世界变迁,更在课余时与学生畅谈时事,从传统文化一路聊到新兴科技与 AI 技术,是学生眼中既严谨又风趣的良师益友。
许多毕业生回忆,与刘老师的关系早已超越师生,成为无话不谈的朋友。他的教学风格温和细腻,待人真诚,使无数学子在成长路上深受启发。
典礼现场气氛温馨感人,全体与会者向刘老师献上热烈掌声,感谢他 35 年来在校园里的付出与贡献。校方也祝愿刘老师退休生活悠然自得、健康安康,继续以他的人生智慧照亮身边的人。
## 颁发董事长设立的奖学金
典礼上同步进行董事长设立的各项奖学金颁发仪式,以表扬在学业、操行与综合表现上有卓越成绩的学生。
(详见[图集](https://drive.google.com/drive/folders/1n3aRxxBwn-k6jzAOogV2ChBHKDA0c0qG)
## 特别演出:永中之星冠军林妤桐献唱
![林妤桐学妹献唱](https://img.yphsalumni.org/i/2025/11/27/sr370l.jpg)
在典礼进入高潮之前,校方安排了一场温馨惊喜表演 —— 第一届“永中之星”冠军、来自初三诚的林妤桐学妹献唱两首歌曲《Forever Young》以及《凤凰花开的路口》。她以清亮而富感染力的歌声向高三学长姐献上最真挚的祝福也为典礼增添一份青春与深情的色彩。
## 高三教师团队呈献毕业回忆视频
随后,由高三各班班导师亲自策划的毕业回忆视频在礼堂大屏幕播出。画面记录了学生们在校园中的点点滴滴,从努力备考到日常生活,从欢笑到奋斗,皆在此刻化为珍贵回忆。
校方希望学生们在未来面对困难与挫折时,也能记起这份勇敢、乐观与纯真,继续向前迈进。
## 歌声中道别,在掌声中启航
![高三毕业生合唱](https://img.yphsalumni.org/i/2025/11/27/ss2wuz.jpg)
典礼尾声,高三毕业生深情演唱《再见》,而初三与高三学生共同合唱《毕业歌》,歌声回荡礼堂,为今年的毕业典礼写下温馨句点。现场气氛在歌声中变得格外动人,许多毕业生眼眶泛红,纷纷表示将铭记这一刻。
上午 11 时正,典礼圆满结束。为感谢师生与来宾的参与,马彣清董事长特别准备了供 600 人享用的自助餐,设宴于礼堂楼下。现场气氛融洽温馨。同时,本校友会也成功招收 14 名新会员,为未来的发展注入新的力量。
[2025 年毕业典礼图集](https://drive.google.com/drive/folders/1n3aRxxBwn-k6jzAOogV2ChBHKDA0c0qG)

View File

@@ -0,0 +1 @@
memberId,chineseName,englishName,ic,mobile,home,email,graduateLevel,,graduateYear,marriageStatus,livingCountry,addressLine1,addressLine2,addressLine3,joinedYear,receiptNumber
1 memberId chineseName englishName ic mobile home email graduateLevel graduateYear marriageStatus livingCountry addressLine1 addressLine2 addressLine3 joinedYear receiptNumber

View File

@@ -4,14 +4,13 @@ date: "2025-10-01"
updated: "2025-10-01"
author: "麦祖奕学长"
description: "永中校友会官网正式上线,为校友提供最新资讯、活动报名及互动交流平台。"
cover: "/news/20251001-official-web-launch/Screenshot.png"
cover: "https://img.yphsalumni.org/i/2025/11/28/qqc3ft.png"
tags: ["活动", "公告", "产品更新"]
category: "通知"
highlight: true
seoTitle: "永中校友会官网上线 | 最新活动与资讯平台"
seoDescription: "永中校友会官网正式上线,校友可在平台获取最新资讯、报名活动及参与互动。"
ogImage: "/images/og/news-launch.jpg"
slug: "/news/20251001-official-web-launch"
ogImage: "https://img.yphsalumni.org/i/2025/11/28/qqc3ft.png"
---
永中校友会官网正式上线啦!🎉

View File

@@ -0,0 +1,40 @@
---
title: "永平中学校友会与校友联络处的区别"
date: "2025-11-28"
updated: "2025-11-28"
author: "永平中学校友会"
description: "毕业校友常说“我都加入校友群组了”,实则误入了校友联络处。本文厘清其与永平中学校友会的区别与使命。"
cover: "https://img.yphsalumni.org/i/2025/11/28/sbk8ue.jpeg"
tags: ["永平中学", "校友会", "校友联络处", "区别", "公告"]
category: "公告"
highlight: true
seoTitle: "永平中学校友会与校友联络处的区别 | 官方说明"
seoDescription: "毕业校友常说“我都加入校友群组了”,实则误入了校友联络处。本文厘清其与永平中学校友会的区别与使命。"
ogImage: "https://img.yphsalumni.org/i/2025/11/28/sbk8ue.jpeg"
---
**永平中学校友会致广大校友的一封信**
亲爱的永平中学校友们:
在近期的毕业典礼上,我们欣喜地听到许多新校友分享“我已加入校友群组”的喜悦。这份对母校的深切情谊,令我们深深感动。
我们同时也注意到,部分校友可能产生了一个美丽的误会:大家加入的“校友联络处”群组,是由学校董事会设立的官方联络平台,主要负责校友信息的收集与传达。而我们所熟知的“永平中学校友会”,则是一个由历届毕业生**自发创建、独立运作的校友社区组织**。
自成立以来,永平中学校友会始终秉承以下宗旨,由校友共建、为校友服务:
- **联结校友情谊**:搭建纯粹、活跃的交流平台,让友谊跨越届别。
- **支持母校发展**:凝聚校友力量,回馈我们共同的永平中学。
- **推动教育、文化与社区活动**:举办丰富多彩的活动,丰富校友生活。
- **建立校友与在校生之间的桥梁**:传承经验与关怀,助力学弟学妹成长。
校友联络处与校友会,二者服务校友的初心一致,是相辅相成的伙伴。我们由衷欢迎每一位热心校友加入我们这个充满活力的毕业生社区,共同为母校的发展注入来自校友群体的独特力量!
**加入我们,共同书写校友故事:**
校友会官方平台https://yphsalumni.org/
<!-- 联系我们:[请在此处填入邮箱或具体负责人联系方式] -->
永平中学校友会<br/>
2025年11月28日

View File

@@ -0,0 +1,54 @@
下面给你一个**实用、可直接交给设计师落地的公告封面设计方案**,基于你们「校友会 vs 校友联络处」容易混淆这一情境,重点是**清晰、正式、易识别、避免再次误会**。
---
# 🎨 公告封面设计方案(适用于官网首页 / Banner / WhatsApp 分享图)
## **1. 视觉主题**
**关键词:清晰区隔、正式感、组织可信度**
色调保持学校主色系(如#fb9e3a#fcef91),并加上一条亮色做强调,提升官方感。
---
## **2. 版式构成**
### ### **A. 顶部:醒目的组织身份**
**「永平中学校友会」**
* 使用学校和校友会 logo + 标题文字(右)
* 字体使用较厚重、带权威感的 Serif / Sans Serif
* 加上小字「官方公告」标签
---
### **B. 中间:公告主标题(视觉核心)**
标题建议直接点出“澄清 & 提醒”的意图:
* **《校友会与校友联络处群组区分公告》**
字体加粗,居中,字号大,确保在手机上也能一眼看懂。
---
### **C. 配图 / 图标建议**
#### **徽章背景淡化**
校徽做 20% 透明大背景,体现官方权威。
---
## **3. 尺寸 & 格式(设计师直接可用)**
* Website banner: **1920×1080**
---
## **4. 文案建议(印在封面下方的小字)**
> “近期部分校友误将校友联络处群组误认为校友会官方群组,特此澄清。”
---

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

@@ -0,0 +1,56 @@
**主题:欢迎加入永平中学校友会!**
亲爱的校友,您好!
感谢您加入 **永平中学校友会Yong Peng High School Alumni Association**
自您成为会员的这一刻起,我们正式迎来了一位新的伙伴,也多了一份共同守护母校与社区的力量。
---
## 🎉 **我们是谁?**
永平中学校友会成立以来,一直致力于:
* **联结校友情谊**
* **支持母校发展**
* **推动教育、文化与社区活动**
* **建立校友与在校生之间的桥梁**
您的加入,让我们更有力量继续前进。
---
## 📌 **作为会员,您可享有:**
* 活动、讲座、聚会的**优先参与权**
* 校友资讯、重要公告的**第一时间通知**
* 参与校友会项目、社区计划的**贡献机会**
* 与来自各领域校友建立联系的**交流平台**
---
## 🗓️ **接下来能做什么?**
为帮助您更快融入校友会生态,我们建议:
1. **保存本会的官方联系方式**WhatsApp / 社媒)
2. **访问我们的官网**
3. **留意即将发布的活动资讯**
4. 若愿意参与志工 / 筹委,请随时回复我们
---
## ❤️ **我们欢迎您与我们同行**
无论您身处何地,校友会的大门永远为您敞开。
让我们一同继续延续永中的精神,传承、回馈、成长。
若您有任何疑问,欢迎随时与我们联系。
谢谢您再次加入我们,
**欢迎回家!**
**永平中学校友会**
Yong Peng High School Alumni Association
https://yphsalumni.org

89
docs/PPT Designs v1.md Normal file
View File

@@ -0,0 +1,89 @@
目标时长
- 57 分钟68 页),节奏轻快,现场可插入 6090 秒演示
推荐结构68 页)
- 封面:项目名称、口号、日期与主办单位
- 为什么做:痛点与目标
- 核心功能:面向校友和公众的主要板块
- 设计与技术:品牌视觉与技术栈亮点
- 运营与上线:内容节奏、参与方式与上线计划
- 路线图:后续可期与合作方向
- 现场演示3090 秒 Demo 流程说明
- 行动号召与致谢:访问方式、参与方式、鸣谢
每页文案示例(可直接放入 PPT
- 封面
- 标题:永平中学校友会官网上线发布
- 口号:连接校友 · 传承精神
- 时间地点2025.10 永平中学
- 主办:永平中学校友会 | 技术支持Tootaio Studio
- 右侧放二维码yphsalumni.org+ public/Logo.svg
- 为什么做(愿景与价值)
- 信息更集中:新闻公告、活动安排、校友故事统一发布
- 连接更紧密:线上平台增强校友互联与参与感
- 传承更长久:记录校史、人事与精神资产
- 数字化基建:为后续报名、征文、捐赠等打基础
- 核心功能(首发)
- 新闻与公告:重要信息一处直达
- 校友活动:预告、报名与回顾
- 名人堂:优秀校友故事与图集
- 关于校友会:宗旨、会徽、校歌
- 入会申请:表单已就绪(当前为占位,逐步开通)
- 后台脚手架:内容与会员管理后续接通
- 设计与技术(可信与可持续)
- 视觉基因:主色 #fb9e3a、副色 #fcef91(温暖、亲和、纪念感)
- 易读排版:专属 Markdown 样式,移动优先
- 技术栈Nuxt 4、TypeScript、Tailwind CSS、@nuxt/ui@nuxt/content
- SEO 就绪Sitemap、Robots、Meta/OG/Twitter 卡片
- 部署灵活:可静态托管,也可 SSR 托管
- 运营与上线(如何用起来)
- 内容节奏每周12篇新闻/活动;名人堂按策划发布
- 投稿机制:校友/老师/班级提供图文,统一编审发布
- 渠道联动:官网首发,同步 Facebook/TikTok
- 数据分析:尊重隐私的访问统计,用于优化内容结构
- 路线图Roadmap
- Q4入会表单接通审核流程、内容 CRUD 后台
- Q1活动报名/签到、相册;捐赠模块调研
- Q2校友企业与招聘、专题档案与时间轴
- 长期:多语言、校友地图、移动端优化
- 现场演示3090 秒)
- 首页开场 → 打开一条新闻 → 切到活动详情 → 看名人堂图集
- 快速展示入会表单(目前“功能未开放”提示)
- 如需:展示后台仪表盘框架(结构就绪,等接入)
- 行动号召与致谢
- 访问yphsalumni.org扫码关注
- 参与:投稿、提供史料、报名志愿者、提供赞助与合作
- 关注我们Facebook「永平中学校友会」、TikTok「@yphs.alumni」
- 致谢:校董会/校方/校友与志愿者、Tootaio Studio 技术支持
视觉与排版建议
- 版式16:9黑/深灰背景或浅色质感底,突出橙色点缀
- 字体:中文优先思源黑体/Noto Sans SC标题粗体、正文中等
- 统一元素:使用 public/Logo.svg 与 public/hero-image-2.jpg 作封面/过渡图
- 图片素材:新闻/活动封面取自 public/news/*、public/events/*、public/hall-of-fame/*
- 最少字多图:每页 35 条要点,每条不超过一行半
演示备选方案(网络不稳时)
- 准备 3060 秒无声/配乐屏录视频(首页→新闻→活动→名人堂→入会)
- 关键页面截图备选:主页、新闻详情、活动详情、名人堂人物页、后台仪表盘
- 本地二维码图片(指向官网),避免临场生成失败
可选加页(视场合增减)
- 用户故事:校友/老师/在校生各 1 个使用场景
- 隐私与安全:不采集敏感信息、分析工具与停用开关
- 合作与赞助:纪念册/活动共创与鸣谢方式
- FAQ如何投稿、如何加入志愿者、如何反馈问题
素材清单(制作 PPT 前先备齐)
- Logo 与主视觉public/Logo.svg、public/hero-image-2.jpg
- 二维码yphsalumni.org建议白底黑码配校色边框
- 页面截图:主页、新闻、活动、名人堂、入会、后台
- 文案确认:宗旨口号、愿景 1 句话、路线图 35 条
- 联系方式:官方邮箱/表单链接、官方社媒链接

109
docs/PPT Designs v2.md Normal file
View File

@@ -0,0 +1,109 @@
呈现原则
- 低调而清晰:把“报效 + 价值 + 开源”讲清楚,不喧宾夺主。
- 一致口径PPT、主持人口播、新闻通稿用同一措辞。
- 可验证与可持续:展示交付范围和开源地址,强调长期可维护。
推荐版式79 页)
- 封面:项目名、口号、日期、主办单位、技术支持
- 背景与目标:为什么要做官网
- 亮点与模块:新闻/活动/名人堂/关于/入会(占位)/后台脚手架
- 交付与价值:高定项目、价值、交付清单
- 开源与授权:开源协议、仓库地址、版权说明
- 运营与上线:内容节奏、参与方式、渠道联动
- 路线图:后续工作与可期
- 现场演示流程提示3090 秒)
- 鸣谢与行动号召:访问方式、参与方式、致谢
关键页完整文案(可直接放入 PPT
- 封面
- 标题:永平中学校友会官网上线发布
- 口号:连接校友 · 传承精神
- 时间地点2025.10 永平中学
- 主办单位:永平中学校友会
- 技术支持赞助Tootaio Studio
- 右侧放站点二维码yphsalumni.org与 public/Logo.svg
- 背景与目标
- 信息更集中:新闻公告、活动、校友故事统一发布
- 连接更紧密:线上平台增强凝聚力与参与感
- 长期可持续:标准化内容、可复用组件、开源治理
- 数字化基建:为报名、征文、捐赠等后续功能打基础
- 亮点与模块
- 新闻/公告首发权威信息SEO 友好
- 校友活动:预告、回顾与多媒体内容
- 名人堂:人物故事与图集
- 关于校友会:宗旨、会徽、校歌
- 入会申请:表单就绪(将逐步开通流程)
- 管理后台:内容与会员管理脚手架,随时对接后端
- 交付与价值(高定与报效)
- 本项目由 Tootaio Studio 报效永平中学校友会
- 价值RM 30,000高定项目
- 交付清单(概览)
- 产品与信息架构:栏目与内容模型设计
- 视觉与 UI主题风格、导航、Markdown 样式与组件库适配
- 前端开发Nuxt 4、TypeScript、Tailwind、@nuxt/ui@nuxt/content 集成
- 内容迁移与示例:新闻/活动/名人堂样例、封面与图集
- SEO 与分析Sitemap/Robots/OG 元信息、可选 Umami 统计接入
- 构建与部署:静态导出/SSR 双方案、上线脚本与文档
- 说明:以上价值不含域名、服务器及第三方服务费用
- 开源与授权(透明与共建)
- 代码开源:项目源代码公开,便于校友与开发者共建
- 开源协议MIT/Apache-2.0(二选一,建议在仓库添加 LICENSE
- 仓库地址Github 仓库链接与二维码(上线后填入)
- 版权说明:代码遵循开源协议;文字与图片等内容版权归原作者/校友会所有
- 贡献方式:提交 Issue/PR按 content.config.ts 字段规范撰写内容
- 运营与上线
- 内容节奏:每周 12 篇新闻/活动;名人堂按策划发布
- 参与方式:投稿、提供史料、报名志愿者、技术共建
- 渠道联动:官网首发,同步 Facebook/TikTok
- 数据与隐私:尊重用户隐私,分析仅用于内容优化
- 路线图(示例)
- Q4入会表单接通审核流程、内容后台 CRUD
- Q1活动报名/签到、相册;捐赠模块调研
- Q2校友企业与招聘、专题档案与时间轴
- 长期:多语言、校友地图、移动端优化
- 现场演示3090 秒)
- 首页 → 新闻详情 → 活动详情 → 名人堂图集 → 入会表单(现提示“功能未开放”)
- 若网络不稳:准备 45 秒屏录视频与关键页面截图备用
- 鸣谢与行动号召
- 访问yphsalumni.org扫码
- 参与:投稿/志愿者/合作/赞助(留邮箱或表单链接)
- 鸣谢:校董会/校方/校友与志愿者、Tootaio Studio 技术支持
- 结束语:连接校友 · 传承精神
金额与赞助信息的呈现技巧
- 将“报效/价值/开源”分散露出,避免单页过度“商业化”:封面页署名(技术支持)、中段“交付与价值”页清晰展现、高尾页鸣谢再次确认。
- “RM30,000”用一行大数字配副标题“高定项目报效旁边放交付清单强调价值而不过度营销。
- 视觉上弱化工作室 Logo相对校友会 Logo 约 1/21/3 宽度),保持庄重。
主持人口播1520 秒)
- “本次官网由 Tootaio Studio 报效搭建,项目价值 RM 三万,为高定定制并全面开源。感谢工作室团队的专业支持,也欢迎校友与开发者共同参与建设。”
新闻通稿段落(可复用)
- “永平中学校友会官网今日正式上线。该项目由 Tootaio Studio 报效支持,项目价值 RM30,000为高定定制网站并以开源方式发布。平台将持续发布新闻活动、名人堂故事等内容欢迎校友访问
yphsalumni.org 并参与共建。”
素材清单
- 官网二维码yphsalumni.org
- 仓库二维码(开源后补充)
- Logopublic/Logo.svg、主视觉 public/hero-image-2.jpg
- 页面截图:主页、新闻、活动、名人堂、入会(与后台仪表盘)
- 一页式“交付清单与价值”图(可用饼图/图标矩阵)
合规与落地建议
- 确认开源协议(建议 MIT并在仓库根目录添加 LICENSE
- 在 README.md 保留“报效/开源”说明与致谢PPT 与通稿用同一表述
- 金额声明附说明:不含域名/服务器/第三方服务费
- 现场背景板与屏幕页脚统一“技术支持Tootaio Studio”
需要的话,我可以:
- 直接为你生成一份可放映的 Marp/Reveal.js Markdown 幻灯,或输出 PPTX 模板(含上述文案与占位图)。
- 创建“交付与价值”信息图一页AI 矢量),用于 PPT 与对外物料。

View File

@@ -0,0 +1,57 @@
### 🎓 校友群体类(展示规模)
* **注册会员人数**(基础指标)
* **校友分布地区数**(如「分布于 12 个国家」)
* **历届毕业生总数**
* **校友企业数**(若可统计,代表社会影响力)
* **理事人数 / 活跃志工人数**
---
### 💰 贡献与资源类(展示凝聚力)
* **教育基金总额**(例如「林赛花教育基金已累计 RM XXX,XXX」
* **奖助学金受惠人数**
* **年度捐款总额 / 参与人数**
* **历年活动赞助商数量**
---
### 🏛️ 历史与传承类(展示深度)
* **成立年份 / 周年数**(基础指标)
* **举办活动次数**(历届聚会 / 座谈 / 校庆)
* **出版刊物 / 纪念册数量**
* **历届理事会届数**
---
### 🌏 影响与传播类(展示影响面)
* **官方网站访问量 / 月均访问数**
* **社交媒体关注人数 / 互动量**
* **媒体报道次数**(可简化为“媒体曝光数”)
---
### 💡 进阶玩法(让页面更有活力)
* **“活跃率”**:例如「本年度活动参与率 72%」
* **“成长曲线”**:每年会员人数变化趋势(用小图表示)
* **“校友情谊值”**:趣味指标,比如根据活动签到 / 捐赠 / 投稿自动算出的综合分数(可 Gamify
---
### ✅ 推荐组合(简洁又有格调)
| 分类 | 指标 | 示例展示 |
| -------- | ------------- | ---------------- |
| 校友群体 | 会员总数 | 1,237 位注册会员 |
| 成立历程 | 成立时间 | 创立于 1985 年 |
| 贡献力量 | 教育基金累计 | RM 245,000 |
| 活动热度 | 年度活动次数 | 12 场活动 |
| 社群影响 | Facebook 关注 | 3.4k Followers |
---
要我帮你把这五个指标设计成一个统一风格的 **Statistic Card UI**(适合放在 Nuxt + @nuxt/ui 项目里)吗?我可以直接给出结构和样式建议 🔥

View File

@@ -5,14 +5,15 @@ export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
devtools: { enabled: true },
modules: [
"@nuxt/ui",
"@nuxtjs/seo",
"@nuxt/content",
"@nuxt/image",
"@nuxt/ui",
"reka-ui/nuxt",
"@nuxtjs/robots",
"@nuxtjs/seo",
"@nuxtjs/sitemap",
],
ssr: true,
css: ["~/assets/css/main.css"],
vite: {
plugins: [tailwindcss()],
@@ -29,28 +30,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://img.yphsalumni.org/i/2025/11/28/qk9fe8.png",
}, // ✅ 上传一张封面图
// 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://img.yphsalumni.org/i/2025/11/28/qk9fe8.png",
},
],
},
},
site: {
url: "https://yphsalumni.com",
name: "永中校友会 YPHS Alumni"
}
});
url: "https://yphsalumni.org",
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",

3978
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

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: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

42
repomix.config.json Normal file
View File

@@ -0,0 +1,42 @@
{
"$schema": "https://repomix.com/schemas/latest/schema.json",
"input": {
"maxFileSize": 52428800
},
"output": {
"filePath": "repomix-output.xml",
"style": "xml",
"parsableStyle": false,
"fileSummary": true,
"directoryStructure": true,
"files": true,
"removeComments": false,
"removeEmptyLines": false,
"compress": false,
"topFilesLength": 5,
"showLineNumbers": false,
"truncateBase64": false,
"copyToClipboard": false,
"includeFullDirectoryStructure": false,
"tokenCountTree": false,
"git": {
"sortByChanges": true,
"sortByChangesMaxCommits": 100,
"includeDiffs": false,
"includeLogs": false,
"includeLogsCount": 50
}
},
"include": [],
"ignore": {
"useGitignore": true,
"useDefaultPatterns": true,
"customPatterns": []
},
"security": {
"enableSecurityCheck": true
},
"tokenCount": {
"encoding": "o200k_base"
}
}