187 lines
5.4 KiB
TypeScript
187 lines
5.4 KiB
TypeScript
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 type StoreAddressParts = {
|
|
province?: string | null;
|
|
cityName?: string | null;
|
|
city?: string | null;
|
|
district?: string | null;
|
|
address?: string | null;
|
|
};
|
|
|
|
function addressPart(value: unknown): string {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
/** 省 + 市 + 区 + 详细地址原样拼接;详细地址已含省市区时不去重 */
|
|
export function fullStoreAddress(store: StoreAddressParts, fallback = '地址待完善'): string {
|
|
const province = addressPart(store.province);
|
|
const city = addressPart(store.cityName) || addressPart(store.city);
|
|
const district = addressPart(store.district);
|
|
const detail = addressPart(store.address);
|
|
const text = `${province}${city}${district}${detail}`;
|
|
return text || fallback;
|
|
}
|
|
|
|
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<string, { parentName: string; children: string[] }>();
|
|
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] : [];
|
|
}
|
|
|
|
/** C 端是否渲染「核销N次」;开关关闭或次数为 0 时不展示 */
|
|
export function shouldShowStoreRedeemCount(
|
|
enabled: boolean | undefined,
|
|
count?: number | null,
|
|
): boolean {
|
|
if (enabled === false) return false;
|
|
return Number(count) > 0;
|
|
}
|