This commit introduces a new mobile-first version of the sponsor list page. Key features: - A responsive layout optimized for mobile devices. - A statistics panel showing total sponsors, contribution amounts, and type distribution. - Category filtering via a bottom navigation bar. Additionally, the special sponsor message is now loaded from `sponsorList.json` to centralize data for both desktop and mobile views.
370 lines
13 KiB
HTML
370 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>赞助名单 | Sponsor List - 永平新港汕河二周年联欢晚宴</title>
|
||
|
||
<script src="/analysis.js"></script>
|
||
|
||
<!-- ✅ 基本 SEO 元信息 -->
|
||
<meta
|
||
name="description"
|
||
content="汕河活动赞助名单与特别鸣谢。感谢所有支持汕河文化与社区发展的赞助商,让我们携手共建更美好的明天。"
|
||
/>
|
||
<meta
|
||
name="keywords"
|
||
content="汕河, Sponsor List, 赞助名单, 活动赞助, 马来西亚汕头同乡会, 捐款名单, 汕河活动"
|
||
/>
|
||
<meta name="author" content="汕河活动组委会" />
|
||
<meta name="robots" content="index, follow" />
|
||
|
||
<!-- ✅ 结构化数据(Google Rich Snippet) -->
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "Event",
|
||
"name": "汕河赞助名单",
|
||
"description": "汕河文化活动赞助名单与鸣谢,包含现金赞助、物品赞助与特别支持单位。",
|
||
"startDate": "2025-11-12",
|
||
"eventAttendanceMode": "https://schema.org/OnlineEventAttendanceMode",
|
||
"eventStatus": "https://schema.org/EventScheduled",
|
||
"organizer": {
|
||
"@type": "Organization",
|
||
"name": "汕河活动组委会",
|
||
"url": "https://dinner.tootaio.com"
|
||
},
|
||
"image": "https://dinner.tootaio.com/assets/og-cover.jpg",
|
||
"url": "https://dinner.tootaio.com/sponsor-list"
|
||
}
|
||
</script>
|
||
|
||
<!-- ✅ Open Graph for Facebook / 微信 / LINE 分享 -->
|
||
<meta property="og:type" content="website" />
|
||
<meta property="og:title" content="汕河赞助名单 | Sponsor List" />
|
||
<meta
|
||
property="og:description"
|
||
content="感谢所有支持汕河文化活动的赞助单位与个人,让爱心与文化同行。"
|
||
/>
|
||
<meta
|
||
property="og:image"
|
||
content="https://dinner.tootaio.com/assets/og-cover.jpg"
|
||
/>
|
||
<meta property="og:url" content="https://dinner.tootaio.com/sponsor-list" />
|
||
<meta property="og:site_name" content="汕河活动" />
|
||
|
||
<!-- ✅ Twitter Card -->
|
||
<meta name="twitter:card" content="summary_large_image" />
|
||
<meta name="twitter:title" content="汕河赞助名单 | Sponsor List" />
|
||
<meta
|
||
name="twitter:description"
|
||
content="汕河活动赞助名单与特别鸣谢。感谢所有支持汕河文化的伙伴!"
|
||
/>
|
||
<meta
|
||
name="twitter:image"
|
||
content="https://dinner.tootaio.com/assets/og-cover.jpg"
|
||
/>
|
||
|
||
<!-- ✅ Favicon -->
|
||
<link rel="icon" href="/favicon.ico" />
|
||
|
||
<!-- ✅ Canonical URL(避免重复收录) -->
|
||
<link rel="canonical" href="https://dinner.tootaio.com/sponsor-list" />
|
||
|
||
<!-- ✅ PWA / Mobile 优化 -->
|
||
<meta name="theme-color" content="#B91C1C" />
|
||
<meta name="apple-mobile-web-app-title" content="汕河赞助名单" />
|
||
<link rel="apple-touch-icon" href="/assets/og-cover.jpg" />
|
||
|
||
<script src="/analysis.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||
|
||
<style type="text/tailwindcss">
|
||
@theme {
|
||
/* 🎨 定义自定义字体族变量 */
|
||
--font-bai-ge: "BaiGeTianXing", sans-serif;
|
||
--font-gu-huang: "ShangShouGuHuang", sans-serif;
|
||
--font-tang-ying: "YeZiTangYingHei", sans-serif;
|
||
}
|
||
|
||
@font-face {
|
||
font-family: "BaiGeTianXing";
|
||
src: url("/fonts/字魂白鸽天行体.ttf") format("truetype");
|
||
font-display: swap;
|
||
}
|
||
|
||
@font-face {
|
||
font-family: "ShangShouGuHuang";
|
||
src: url("/fonts/ShangShouGuHuangTi-2.ttf") format("truetype");
|
||
font-display: swap;
|
||
}
|
||
|
||
@font-face {
|
||
font-family: "YeZiTangYingHei";
|
||
src: url("/fonts/YeZiGongChangTangYingHei-2.ttf") format("truetype");
|
||
font-display: swap;
|
||
}
|
||
|
||
.poem {
|
||
@apply flex-1 [writing-mode:vertical-rl] flex items-center justify-center text-6xl font-bai-ge tracking-[0.5em] text-white ring-amber-300 text-shadow-lg text-shadow-black;
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
.sponsor-marquee-track {
|
||
animation: sponsor-marquee-move-text 120s linear infinite;
|
||
}
|
||
|
||
.special-marquee-track {
|
||
animation: special-marquee-move-text 30s linear infinite;
|
||
}
|
||
|
||
@keyframes sponsor-marquee-move-text {
|
||
to {
|
||
transform: translateY(-50%);
|
||
}
|
||
}
|
||
|
||
@keyframes special-marquee-move-text {
|
||
to {
|
||
transform: translateX(-50%);
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app" class="h-screen flex flex-col select-none">
|
||
<div class="text-center text-6xl font-bold p-8 bg-red-700 text-white">
|
||
{{eventTitle}}
|
||
</div>
|
||
<!-- 特别赞助 -->
|
||
<div class="py-6 bg-yellow-300 text-red-500 shadow-yellow-500 shadow-lg">
|
||
<div class="overflow-clip mask-x-from-95% mask-x-to-100%">
|
||
<div class="flex pl-4 gap-4 w-max special-marquee-track text-4xl">
|
||
<div v-for="sponsor in specialSponsorDouble" :key="sponsor">
|
||
{{sponsor}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 下半部分二八分,左侧放 image,右侧走马灯 -->
|
||
<div
|
||
class="flex-1 flex min-h-0 overflow-clip bg-linear-to-b from-red-500 to-red-900"
|
||
>
|
||
<!-- <div class="flex-2 flex flex-col items-center justify-around">
|
||
<img
|
||
v-for="logo in logos"
|
||
:src="`../assets/${logo.imgSrc}`"
|
||
:alt="logo.imgSrc"
|
||
class="w-[80%] drop-shadow-2xl"
|
||
/>
|
||
</div> -->
|
||
<div class="poem">汕水流长通四海</div>
|
||
<div class="flex-8 inset-red-lg">
|
||
<div class="flex flex-col pt-4 gap-4 px-4 sponsor-marquee-track">
|
||
<div
|
||
v-for="sponsor in sponsorListDouble"
|
||
:key="sponsor"
|
||
class="bg-linear-to-br from-white/20 to-white/10 px-16 py-4 rounded-2xl border-2 border-white/40"
|
||
>
|
||
<div v-if="sponsor.type == 'cash'" class="text-center py-8">
|
||
<div class="text-7xl text-yellow-400 font-bold">
|
||
RM {{sponsor.displayAmount}}
|
||
</div>
|
||
<div
|
||
class="text-7xl text-white text-shadow-amber-400 text-shadow-lg"
|
||
>
|
||
{{sponsor.name}}
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-else-if="sponsor.type == 'cash-group'"
|
||
class="text-center py-8"
|
||
>
|
||
<div class="text-7xl text-yellow-400 font-bold mb-8">
|
||
RM {{sponsor.amount}}
|
||
</div>
|
||
<div class="flex flex-wrap gap-4">
|
||
<div
|
||
v-for="child in sponsor.children"
|
||
class="text-3xl px-4 py-2 bg-white/40 rounded-2xl"
|
||
>
|
||
{{child}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
v-else-if="sponsor.type == 'table'"
|
||
class="flex items-center"
|
||
>
|
||
<div
|
||
class="text-3xl bg-red-500 text-white font-bold px-4 py-2 border-2 border-white rounded-full"
|
||
>
|
||
{{tableToSeats(sponsor.amount)}}
|
||
</div>
|
||
<div class="text-4xl font-bold flex-1 ml-4 text-white">
|
||
{{sponsor.name}}
|
||
</div>
|
||
</div>
|
||
<div v-else-if="sponsor.type == 'item'" class="flex items-center">
|
||
<div
|
||
class="text-3xl bg-red-500 text-white font-bold px-4 py-2 border-2 border-white rounded-full"
|
||
>
|
||
{{sponsor.detail}}
|
||
</div>
|
||
<div class="text-4xl font-bold flex-1 ml-4 text-white">
|
||
{{sponsor.name}}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="poem">河川万里泽邦家</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const { createApp, ref, computed, onMounted } = Vue;
|
||
|
||
createApp({
|
||
setup() {
|
||
const eventTitle = ref("");
|
||
const logos = ref([]);
|
||
const sponsorList = ref([]); // Load from JSON
|
||
const specialSponsor = ref("");
|
||
|
||
const typePriority = {
|
||
cash: 4,
|
||
"cash-group": 3,
|
||
table: 2,
|
||
item: 1,
|
||
};
|
||
|
||
const groupByCashLessThan = ref(0);
|
||
|
||
function sortSponsors(list) {
|
||
return list.sort((a, b) => {
|
||
// 先按 type 优先级排序
|
||
const typeDiff = typePriority[b.type] - typePriority[a.type];
|
||
if (typeDiff !== 0) return typeDiff;
|
||
|
||
// 若 type 相同,再按 amount 从大到小
|
||
if (b.amount !== a.amount) return b.amount - a.amount;
|
||
|
||
// 若 amount 相同,最后按名称排序(可选)
|
||
return a.name.localeCompare(b.name, "zh");
|
||
});
|
||
}
|
||
|
||
const loadData = async () => {
|
||
try {
|
||
const [sponsorListResult] = await Promise.all([
|
||
fetch("../sponsorList.json"),
|
||
]);
|
||
if (!sponsorListResult.ok) {
|
||
throw new Error(
|
||
"Error while loading sponsorList: " + sponsorListResult.status
|
||
);
|
||
}
|
||
const sponsorListJsonData = await sponsorListResult.json();
|
||
eventTitle.value = sponsorListJsonData.eventTitle || "活动名称";
|
||
specialSponsor.value = sponsorListJsonData.specialSponsor || "";
|
||
logos.value = sponsorListJsonData.logos || [];
|
||
|
||
sponsorList.value = (
|
||
sponsorListJsonData.sponsorList || []
|
||
).reduce((acc, s, idx) => {
|
||
const sponsor = {
|
||
...s,
|
||
displayAmount:
|
||
s.type == "cash"
|
||
? s.amount.toLocaleString("en-MY")
|
||
: s.amount,
|
||
_uid: `s-${idx}`,
|
||
};
|
||
|
||
// 如果是现金赞助且金额小于阈值
|
||
if (s.type === "cash" && s.amount < groupByCashLessThan.value) {
|
||
// 查找是否已存在该金额的分组
|
||
const groupId = `group-${s.amount}`;
|
||
let amountGroup = acc.find((item) => item._uid === groupId);
|
||
|
||
if (!amountGroup) {
|
||
// 创建新的金额分组
|
||
amountGroup = {
|
||
name: "其他赞助商",
|
||
type: "cash-group",
|
||
amount: s.amount, // 保持原始数字,不格式化
|
||
children: [],
|
||
_uid: groupId,
|
||
};
|
||
acc.push(amountGroup);
|
||
}
|
||
|
||
// 将赞助商名称添加到分组的children中
|
||
amountGroup.children.push(s.name);
|
||
} else {
|
||
// 其他赞助商直接添加到结果中
|
||
acc.push(sponsor);
|
||
}
|
||
|
||
return acc;
|
||
}, []);
|
||
// Sort SponsorList by type and amount
|
||
sponsorList.value = sortSponsors(sponsorList.value);
|
||
console.log(sponsorList.value);
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
};
|
||
|
||
onMounted(async () => {
|
||
await loadData();
|
||
});
|
||
|
||
const sponsorListDouble = computed(() => {
|
||
const a = sponsorList.value.map((s) => ({
|
||
...s,
|
||
_uid: s._uid + "-a",
|
||
}));
|
||
const b = sponsorList.value.map((s) => ({
|
||
...s,
|
||
_uid: s._uid + "-b",
|
||
}));
|
||
return [...a, ...b];
|
||
});
|
||
|
||
const specialSponsorDouble = computed(() => [
|
||
specialSponsor.value,
|
||
specialSponsor.value,
|
||
specialSponsor.value,
|
||
specialSponsor.value,
|
||
]);
|
||
|
||
const tableToSeats = (amount) => {
|
||
switch (amount) {
|
||
case 2:
|
||
return "两席";
|
||
case 1:
|
||
return "一席";
|
||
case 0.5:
|
||
return "半席";
|
||
default:
|
||
console.error("Error while converting table amount: ", amount);
|
||
}
|
||
};
|
||
|
||
return {
|
||
eventTitle,
|
||
logos,
|
||
sponsorListDouble,
|
||
specialSponsorDouble,
|
||
tableToSeats,
|
||
};
|
||
},
|
||
}).mount("#app");
|
||
</script>
|
||
</body>
|
||
</html>
|