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.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -23,4 +23,6 @@ logs
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|
||||||
repomix-output.xml
|
repomix-output.xml
|
||||||
|
|
||||||
|
content/members/members.csv
|
||||||
@@ -105,6 +105,11 @@ const bannerActions = ref<ButtonProps[]>([
|
|||||||
const items = computed<NavigationMenuItem[]>(() => [
|
const items = computed<NavigationMenuItem[]>(() => [
|
||||||
{ label: "首页", to: "/" },
|
{ label: "首页", to: "/" },
|
||||||
{ label: "新闻", to: "/news", active: route.path.startsWith("/news") },
|
{ label: "新闻", to: "/news", active: route.path.startsWith("/news") },
|
||||||
|
{
|
||||||
|
label: "会员",
|
||||||
|
to: "/members",
|
||||||
|
active: route.path.startsWith("/members"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "活动",
|
label: "活动",
|
||||||
to: "/events",
|
to: "/events",
|
||||||
@@ -122,7 +127,7 @@ const items = computed<NavigationMenuItem[]>(() => [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "关于校友会",
|
label: "关于",
|
||||||
to: "/about",
|
to: "/about",
|
||||||
active: route.path.startsWith("/about"),
|
active: route.path.startsWith("/about"),
|
||||||
children: [
|
children: [
|
||||||
@@ -143,12 +148,12 @@ const items = computed<NavigationMenuItem[]>(() => [
|
|||||||
description: "永平中学补习班(1956年):一封迟来的贴文",
|
description: "永平中学补习班(1956年):一封迟来的贴文",
|
||||||
to: "/about/middle-highschool-tuition-class",
|
to: "/about/middle-highschool-tuition-class",
|
||||||
active: route.path.startsWith("/about/middle-highschool-tuition-class"),
|
active: route.path.startsWith("/about/middle-highschool-tuition-class"),
|
||||||
icon: "mdi:mail"
|
icon: "mdi:mail",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "友情链接",
|
label: "链接",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: "永平中学官网",
|
label: "永平中学官网",
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ const generation = route.params.slug;
|
|||||||
|
|
||||||
const categories = ref(["领导团队", "职能部门", "专项部门"]);
|
const categories = ref(["领导团队", "职能部门", "专项部门"]);
|
||||||
|
|
||||||
|
// TODO: Fetch from api
|
||||||
const orgStructure = ref([
|
const orgStructure = ref([
|
||||||
{
|
{
|
||||||
name: "李煜斌",
|
name: "李煜斌",
|
||||||
@@ -132,14 +133,14 @@ const orgStructure = ref([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "胡少菲",
|
name: "胡少菲",
|
||||||
position: "总务",
|
position: "康乐",
|
||||||
category: "职能部门",
|
category: "职能部门",
|
||||||
photo: "/org-structure/胡少菲.png",
|
photo: "/org-structure/胡少菲.png",
|
||||||
description: "文化活动策划、康乐项目组织与会员联谊活动。",
|
description: "文化活动策划、康乐项目组织与会员联谊活动。",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "林剑宝",
|
name: "林剑宝",
|
||||||
position: "副总务",
|
position: "副康乐",
|
||||||
category: "职能部门",
|
category: "职能部门",
|
||||||
photo: "/org-structure/林剑宝.png",
|
photo: "/org-structure/林剑宝.png",
|
||||||
description: "协助文康组织文体活动、兴趣小组与社交聚会。",
|
description: "协助文康组织文体活动、兴趣小组与社交聚会。",
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
<UPageHero
|
<UPageHero
|
||||||
class="bg-cover bg-center"
|
class="bg-cover bg-center"
|
||||||
style="
|
style="
|
||||||
background-image: url("/hero-image.jpg");
|
background-image: url("/hero-image-2.jpg");
|
||||||
|
background-position-y: -40px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
background-color: rgba(255, 255, 255, 0.5); /* Semi-transparent black */
|
background-color: rgba(255, 255, 255, 0.5); /* Semi-transparent black */
|
||||||
background-blend-mode: lighten;
|
background-blend-mode: lighten;
|
||||||
"
|
"
|
||||||
|
|||||||
111
app/pages/members/index.vue
Normal file
111
app/pages/members/index.vue
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<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({
|
||||||
|
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: "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))
|
||||||
|
? "初中毕业"
|
||||||
|
: `初中第 ${Number(row.original.graduateYear) - 1958} 届`;
|
||||||
|
case "s":
|
||||||
|
// 高中毕业
|
||||||
|
return isNaN(Number(row.original.graduateYear))
|
||||||
|
? "高中毕业"
|
||||||
|
: `高中第 ${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>
|
||||||
@@ -36,7 +36,7 @@ export default defineContentConfig({
|
|||||||
// 名人堂
|
// 名人堂
|
||||||
hallOfFames: defineCollection({
|
hallOfFames: defineCollection({
|
||||||
type: "page",
|
type: "page",
|
||||||
source: "hall-of-fames/*md",
|
source: "hall-of-fames/*.md",
|
||||||
schema: z.object({
|
schema: z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
photo: z.string().url(),
|
photo: z.string().url(),
|
||||||
@@ -45,5 +45,27 @@ export default defineContentConfig({
|
|||||||
gallery: z.array(z.string()),
|
gallery: z.array(z.string()),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
// 会员名册
|
||||||
|
members: defineCollection({
|
||||||
|
type: "data",
|
||||||
|
source: "members/members.csv",
|
||||||
|
schema: z.object({
|
||||||
|
// 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(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
1
content/members/members-sample.csv
Normal file
1
content/members/members-sample.csv
Normal file
@@ -0,0 +1 @@
|
|||||||
|
chineseName,englishName,ic,mobile,home,email,graduateLevel,,graduateYear,marriageStatus,livingCountry,addressLine1,addressLine2,addressLine3,joinedYear,receiptNumber
|
||||||
|
@@ -63,7 +63,7 @@
|
|||||||
|
|
||||||
- 版式:16:9,黑/深灰背景或浅色质感底,突出橙色点缀
|
- 版式:16:9,黑/深灰背景或浅色质感底,突出橙色点缀
|
||||||
- 字体:中文优先思源黑体/Noto Sans SC;标题粗体、正文中等
|
- 字体:中文优先思源黑体/Noto Sans SC;标题粗体、正文中等
|
||||||
- 统一元素:使用 public/Logo.svg 与 public/hero-image.jpg 作封面/过渡图
|
- 统一元素:使用 public/Logo.svg 与 public/hero-image-2.jpg 作封面/过渡图
|
||||||
- 图片素材:新闻/活动封面取自 public/news/*、public/events/*、public/hall-of-fame/*
|
- 图片素材:新闻/活动封面取自 public/news/*、public/events/*、public/hall-of-fame/*
|
||||||
- 最少字多图:每页 3–5 条要点,每条不超过一行半
|
- 最少字多图:每页 3–5 条要点,每条不超过一行半
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
|
|
||||||
素材清单(制作 PPT 前先备齐)
|
素材清单(制作 PPT 前先备齐)
|
||||||
|
|
||||||
- Logo 与主视觉:public/Logo.svg、public/hero-image.jpg
|
- Logo 与主视觉:public/Logo.svg、public/hero-image-2.jpg
|
||||||
- 二维码:yphsalumni.org(建议白底黑码,配校色边框)
|
- 二维码:yphsalumni.org(建议白底黑码,配校色边框)
|
||||||
- 页面截图:主页、新闻、活动、名人堂、入会、后台
|
- 页面截图:主页、新闻、活动、名人堂、入会、后台
|
||||||
- 文案确认:宗旨口号、愿景 1 句话、路线图 3–5 条
|
- 文案确认:宗旨口号、愿景 1 句话、路线图 3–5 条
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ yphsalumni.org 并参与共建。”
|
|||||||
|
|
||||||
- 官网二维码(yphsalumni.org)
|
- 官网二维码(yphsalumni.org)
|
||||||
- 仓库二维码(开源后补充)
|
- 仓库二维码(开源后补充)
|
||||||
- Logo:public/Logo.svg、主视觉 public/hero-image.jpg
|
- Logo:public/Logo.svg、主视觉 public/hero-image-2.jpg
|
||||||
- 页面截图:主页、新闻、活动、名人堂、入会(与后台仪表盘)
|
- 页面截图:主页、新闻、活动、名人堂、入会(与后台仪表盘)
|
||||||
- 一页式“交付清单与价值”图(可用饼图/图标矩阵)
|
- 一页式“交付清单与价值”图(可用饼图/图标矩阵)
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default defineNuxtConfig({
|
|||||||
{ property: "og:url", content: "https://yphsalumni.org" }, // ✅ 换成你网站的真实域名
|
{ property: "og:url", content: "https://yphsalumni.org" }, // ✅ 换成你网站的真实域名
|
||||||
{
|
{
|
||||||
property: "og:image",
|
property: "og:image",
|
||||||
content: "https://yphsalumni.org/hero-image.jpg",
|
content: "https://yphsalumni.org/hero-image-2.jpg",
|
||||||
}, // ✅ 上传一张封面图
|
}, // ✅ 上传一张封面图
|
||||||
|
|
||||||
// Twitter Card
|
// Twitter Card
|
||||||
@@ -62,7 +62,7 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "twitter:image",
|
name: "twitter:image",
|
||||||
content: "https://yphsalumni.org/hero-image.jpg",
|
content: "https://yphsalumni.org/hero-image-2.jpg",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
BIN
public/hero-image-2.jpg
Normal file
BIN
public/hero-image-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
Reference in New Issue
Block a user