diff --git a/20251115/sponsorList.json b/20251115/sponsorList.json
index 063b6f4..d5cb5e0 100644
--- a/20251115/sponsorList.json
+++ b/20251115/sponsorList.json
@@ -1,5 +1,6 @@
{
"eventTitle": "永平新港汕河体育协会 2 周年庆联欢晚宴",
+ "specialSponsors": "感谢 V World2.0 的特别赞助,现在下载 APP 并进行实名认证即可获得 RM50 的登录奖励!",
"logos": [{ "imgSrc": "SamHor-HighRes.png" }, { "imgSrc": "关公文化-HighRes.png" }, { "imgSrc": "VWorld2 Logo.png" }],
"poems": ["汕水流长通四海", "河川万里泽邦家"],
"sponsorList": [
diff --git a/20251115/sponsorList/index.html b/20251115/sponsorList/index.html
index 76dc2fb..b0a76cf 100644
--- a/20251115/sponsorList/index.html
+++ b/20251115/sponsorList/index.html
@@ -232,9 +232,7 @@
const eventTitle = ref("");
const logos = ref([]);
const sponsorList = ref([]); // Load from JSON
- const specialSponsor = ref(
- "感谢 V World2.0 的特别赞助,现在下载 APP 并进行实名认证即可获得 RM50 的登录奖励!"
- );
+ const specialSponsor = ref("");
const typePriority = {
cash: 4,
@@ -271,6 +269,7 @@
}
const sponsorListJsonData = await sponsorListResult.json();
eventTitle.value = sponsorListJsonData.eventTitle || "活动名称";
+ specialSponsor.value = sponsorListJsonData.specialSponsor || "";
logos.value = sponsorListJsonData.logos || [];
sponsorList.value = (
diff --git a/20251115/sponsorList/mobile/index.html b/20251115/sponsorList/mobile/index.html
new file mode 100644
index 0000000..45a8c57
--- /dev/null
+++ b/20251115/sponsorList/mobile/index.html
@@ -0,0 +1,239 @@
+
+
+
+
+
+ 永平新港汕河体育协会 2 周年庆联欢晚宴|电子征信录与赞助名单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ eventTitle }}
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
{{ specialSponsor }}
+
+
+
+
+
+
+ 赞助统计
+
+
+
+
+
赞助商总数
+
{{ stats.totalSponsors }}
+
+
+
现金赞助总额
+
+ RM {{ stats.totalCash.toLocaleString() }}
+
+
+
+
标品赞助数量
+
{{ stats.totalItems }}
+
+
+
赞助席位总数
+
+ {{ tableToSeats(stats.totalTables) }}
+
+
+
+
+
+
赞助类型分布
+
+
+
{{ type.name }}
+
+
{{ type.count }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/20251115/sponsorList/mobile/script.js b/20251115/sponsorList/mobile/script.js
new file mode 100644
index 0000000..144ac90
--- /dev/null
+++ b/20251115/sponsorList/mobile/script.js
@@ -0,0 +1,295 @@
+const { createApp, ref, computed, onMounted } = Vue;
+
+const SponsorCard = {
+ props: {
+ sponsor: { type: Object, required: true },
+ },
+ template: "#sponsor-card-template",
+};
+
+const app = createApp({
+ setup() {
+ const eventTitle = ref("");
+ const logos = ref([]);
+ const sponsorList = ref([]); // Load from JSON
+ const specialSponsor = ref("");
+ const showStats = ref(false);
+ const activeCategory = ref("all");
+
+ const categories = [
+ {
+ type: "all",
+ name: "全部",
+ icon: "fas fa-list",
+ color: "text-blue-500",
+ },
+ {
+ type: "cash",
+ name: "现金",
+ icon: "fas fa-money-bill-wave",
+ color: "text-green-500",
+ pillBg: "bg-green-100",
+ pillColor: "text-green-800",
+ },
+ {
+ type: "item",
+ name: "标品",
+ icon: "fas fa-gift",
+ color: "text-purple-500",
+ pillBg: "bg-purple-100",
+ pillColor: "text-purple-800",
+ },
+ {
+ type: "table",
+ name: "桌位",
+ icon: "fas fa-utensils",
+ color: "text-orange-500",
+ pillBg: "bg-orange-100",
+ pillColor: "text-orange-800",
+ },
+ ];
+
+ 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 stats = computed(() => {
+ const result = {
+ totalSponsors: 0,
+ totalCash: 0,
+ totalItems: 0,
+ totalTables: 0,
+ typeDistribution: [],
+ };
+
+ // 计算各类赞助商数量
+ const typeCounts = {
+ cash: 0,
+ "cash-group": 0,
+ item: 0,
+ table: 0,
+ };
+
+ // 计算总额
+ sponsorList.value.forEach((sponsor) => {
+ if (sponsor.type === "cash") {
+ result.totalCash += sponsor.amount;
+ typeCounts.cash++;
+ } else if (sponsor.type === "cash-group") {
+ result.totalCash += sponsor.amount * sponsor.children.length;
+ typeCounts["cash-group"]++;
+ } else if (sponsor.type === "item") {
+ result.totalItems++;
+ typeCounts.item++;
+ } else if (sponsor.type === "table") {
+ result.totalTables += sponsor.amount;
+ typeCounts.table++;
+ }
+ });
+
+ // 计算总赞助商数量
+ result.totalSponsors = sponsorList.value.length;
+
+ // 计算类型分布
+ const total = result.totalSponsors;
+ result.typeDistribution = [
+ {
+ name: "现金赞助",
+ count: typeCounts.cash + typeCounts["cash-group"],
+ percentage: Math.round(
+ ((typeCounts.cash + typeCounts["cash-group"]) / total) * 100
+ ),
+ color: "bg-green-500",
+ },
+ {
+ name: "标品赞助",
+ count: typeCounts.item,
+ percentage: Math.round((typeCounts.item / total) * 100),
+ color: "bg-purple-500",
+ },
+ {
+ name: "桌位赞助",
+ count: typeCounts.table,
+ percentage: Math.round((typeCounts.table / total) * 100),
+ color: "bg-orange-500",
+ },
+ ];
+
+ return result;
+ });
+
+ // 过滤赞助商
+ const filteredSponsors = computed(() => {
+ if (activeCategory.value === "all") {
+ return sponsorList.value;
+ }
+ return sponsorList.value.filter(
+ (sponsor) => sponsor.type === activeCategory.value
+ );
+ });
+
+ 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.specialSponsors || "";
+ logos.value = sponsorListJsonData.logos || [];
+
+ sponsorList.value = (sponsorListJsonData.sponsorList || []).reduce(
+ (acc, s, idx) => {
+ const category = categories.find((c) => c.type === s.type);
+
+ const sponsor = {
+ ...s,
+ displayAmount:
+ s.type == "cash" ? s.amount.toLocaleString("en-MY") : s.amount,
+ seats: s.type == "table" ? tableToSeats(s.amount) : "",
+ pillBg: category?.pillBg,
+ pillColor: category?.pillColor,
+ _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",
+ displayAmount:
+ s.type == "cash"
+ ? s.amount.toLocaleString("en-MY")
+ : s.amount,
+ 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 tableToSeats = (input) => {
+ if (input === null || input === undefined || input === "") return "";
+
+ if (input === 0.5) return "半席";
+
+ const n = Number(input);
+ if (Number.isNaN(n)) throw new TypeError("输入不是有效数字: " + input);
+
+ const eps = 1e-9;
+ const sign = n < 0 ? "负" : "";
+ const abs = Math.abs(n);
+
+ // 把数值四舍五入到最接近的 0.5(保证容错)
+ const rounded = Math.round(abs * 2) / 2;
+
+ // 分离整数和小数部分
+ let intPart = Math.floor(rounded + eps);
+ const rem = rounded - intPart; // 只可能是 0 或 0.5
+
+ // 如果 rounded 恰好是 X.0 的情况下 rem ~ 0;如果是 X.5 则 rem ~ 0.5
+ // 特殊:如果 rem === 1(极罕见)就进位(上面已用 round 避免)
+ // 小范围容错处理
+ const isHalf = Math.abs(rem - 0.5) < eps;
+ const isInteger = Math.abs(rem) < eps;
+
+ // 数字到小范围中文(只对 0/1/2 做汉字)
+ const smallChinese = (num) => {
+ if (num === 0) return "零";
+ if (num === 1) return "一";
+ if (num === 2) return "两";
+ return null;
+ };
+
+ const numLabel = (() => {
+ const c = smallChinese(intPart);
+ return c !== null ? c : String(intPart);
+ })();
+
+ // 判断 numLabel 是阿拉伯数字还是汉字(用于决定是否在数字与“席”之间加空格)
+ const isDigits = /^\d+$/.test(numLabel);
+
+ if (isInteger) {
+ // 整数
+ return isDigits ? `${sign}${numLabel} 席` : `${sign}${numLabel}席`;
+ } else if (isHalf) {
+ // 半席
+ return isDigits ? `${sign}${numLabel} 席半` : `${sign}${numLabel}席半`;
+ } else {
+ // 理论上不会到这里(因为已 round 到 0.5),但兜底返回字符串
+ return `${sign}${numLabel} 席`;
+ }
+ };
+
+ return {
+ eventTitle,
+ logos,
+ sponsorList,
+ specialSponsor,
+ showStats,
+ activeCategory,
+ categories,
+ stats,
+ filteredSponsors,
+ tableToSeats,
+ };
+ },
+});
+
+app.component("sponsor-card", SponsorCard); // 全局注册
+app.mount("#app");