export type StoreCategoryLike = { id?: string; name?: string; parentId?: string | null; parent?: { name?: string } | null; }; export type StoreCategoryTreeNode = { id: string; name: string; children?: { id: string; name: string }[]; }; export function storeStarCount(rating?: number | string | null): number { const n = Number(rating); if (!Number.isFinite(n) || n <= 0) return 5; return Math.min(5, Math.max(1, Math.round(n))); } export function storeCategoryTags( store: { tags?: unknown; categoryId?: string | null; category?: StoreCategoryLike | null; categories?: StoreCategoryLike[] | null; }, tree: StoreCategoryTreeNode[] = [], ): string[] { const fromJson = Array.isArray(store.tags) ? store.tags.map((t) => String(t).trim()).filter(Boolean) : []; if (fromJson.length) return fromJson; const multi = Array.isArray(store.categories) ? store.categories : []; if (multi.length) { return groupCategoryTags(multi, tree); } if (store.category || store.categoryId) { const grouped = groupCategoryTags( store.category ? [store.category] : [], tree, store.categoryId, ); if (grouped.length) return grouped; } const storeCatId = String(store.categoryId || ''); if (storeCatId) { for (const root of tree) { for (const child of root.children ?? []) { if (child.id === storeCatId) { return [`${root.name}|${child.name}`]; } } } } return []; } /** 按大类分组:餐饮|火锅·江浙菜、娱乐|KTV */ function groupCategoryTags( categories: StoreCategoryLike[], tree: StoreCategoryTreeNode[] = [], fallbackCategoryId?: string | null, ): string[] { if (!categories.length && fallbackCategoryId) { return groupCategoryTags([{ id: String(fallbackCategoryId) }], tree); } const groups = new Map(); const order: string[] = []; for (const cat of categories) { const resolved = resolveCategoryParts(cat, tree); if (!resolved) continue; const { parentKey, parentName, childName } = resolved; if (!groups.has(parentKey)) { groups.set(parentKey, { parentName, children: [] }); order.push(parentKey); } const group = groups.get(parentKey)!; if (childName && childName !== parentName && !group.children.includes(childName)) { group.children.push(childName); } } return order .map((key) => { const group = groups.get(key)!; if (group.parentName && group.children.length) { return `${group.parentName}|${group.children.join('·')}`; } if (group.children.length) return group.children.join('·'); return group.parentName; }) .filter(Boolean); } function resolveCategoryParts( cat: StoreCategoryLike, tree: StoreCategoryTreeNode[] = [], ): { parentKey: string; parentName: string; childName: string } | null { const childName = String(cat.name || '').trim(); let parentName = String(cat.parent?.name || '').trim(); const catId = String(cat.id || ''); const parentId = String(cat.parentId || ''); if (!parentName && (parentId || catId)) { for (const root of tree) { if (parentId && root.id === parentId) { parentName = root.name; break; } for (const child of root.children ?? []) { if (catId && child.id === catId) { parentName = root.name; if (!childName) return { parentKey: root.id, parentName, childName: child.name }; break; } } } } if (!childName && !parentName) return null; const parentKey = parentId || parentName || catId; if (!parentName && catId) { for (const root of tree) { for (const child of root.children ?? []) { if (child.id === catId) { return { parentKey: root.id, parentName: root.name, childName: child.name }; } } } } return { parentKey, parentName: parentName || childName, childName: childName || parentName, }; } export function storeLeafCategoryIds(store: { categoryId?: string | null; category?: StoreCategoryLike | null; categories?: StoreCategoryLike[] | null; }): string[] { if (Array.isArray(store.categories) && store.categories.length) { return store.categories.map((c) => String(c.id || '')).filter(Boolean); } const id = String(store.categoryId || store.category?.id || ''); return id ? [id] : []; }