/** 报名者项(mock 用 name,DB 用 nickname) */ export type JoinerItem = { name?: string; nickname?: string; avatar?: string; xiaohongshu_url?: string; }; /** 去重 key:小红书 url 相同视为 1 人,无 url 时用 avatar,再 fallback nickname */ function dedupeKey(j: JoinerItem, useAvatarForMock: boolean): string { const xh = (j.xiaohongshu_url || "").trim(); if (xh && !useAvatarForMock) return `xh:${xh}`; const avatar = (j.avatar || "").trim(); if (avatar) return `av:${avatar}`; return `_${(j.nickname || j.name || "").trim()}`; } /** * 合并 mock 与数据库报名者:mock 固定 3 个在左,DB 在右 * 小红书 url 相同视为 1 人,avatar 相同也只显示 1 个 * 最多显示 8 个,保证 PC/H5 排版合理 */ export function mergeJoiners( mock: JoinerItem[], db: JoinerItem[] | undefined ): JoinerItem[] { const seen = new Set(); const result: JoinerItem[] = []; mock.forEach((j) => { const key = dedupeKey(j, true); if (!seen.has(key)) { seen.add(key); result.push(j); } }); (db || []).forEach((j) => { const key = dedupeKey(j, false); if (!seen.has(key)) { seen.add(key); result.push(j); } }); return result.slice(0, 8); } /** 数据库报名者按小红书 url 去重后的数量(用于显示报名人数) */ export function getUniqueDbCount(db: JoinerItem[] | undefined): number { if (!db?.length) return 0; const seen = new Set(); for (const j of db) { const key = dedupeKey(j, false); seen.add(key); } return seen.size; }