feat(wanfa): 新增路线详情参考价格配置及展示功能

- 新增数据库迁移脚本,为详情表添加四个价格相关字段
- 完善后端schema校验、序列化逻辑,增加价格相关的数据清理与验证规则
- 新增价格配置校验逻辑,设置起售价时必须选择对应的计价单位
- 在Admin UI详情编辑器中新增价格配置模块,支持配置起售价、计价单位、适用人数范围和价格说明
- 更新小程序端详情页面,支持展示配置的参考价格信息
- 补充相关测试用例,更新API文档与类型定义
This commit is contained in:
duanshuwen committed 2026-08-27 22:02:51 +08:00
1 parent f18971a430
commit feec6ab80f
20 files changed
+351 -17

No files matched your search

@@ -31,6 +31,10 @@ function createRouteFallback(route: PlayRoute): DetailPresentation {
"最终行程以出行日期、人数和资源确认结果为准。",
],
gallery: Array.from(new Set([route.image, ...sharedGallery])).slice(0, 5),
priceStartingValue: null,
priceUnit: null,
pricePeopleRange: "",
priceDescription: "",
conciergeAdvisor: null,
};
}
@@ -61,6 +65,19 @@ function cleanGallery(value: unknown) {
);
}
function cleanPriceStartingValue(value: unknown, fallback: number | null) {
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return fallback;
return Number(value.toFixed(2));
}
function cleanPriceUnit(value: unknown, fallback: PublicDetail["priceUnit"]): PublicDetail["priceUnit"] {
return value === "person" || value === "day" || value === "group" ? value : fallback;
}
function cleanOptionalText(value: unknown, fallback: string) {
return cleanText(value) || fallback;
}
export function findWanfaRouteDetailFallback(routeId: string) {
return routeFallbacks.get(routeId) ?? null;
}
@@ -78,6 +95,10 @@ export function normalizeDetailPresentation(
const intro = cleanText(source.intro) || fallback?.intro || "";
const gallery = cleanGallery(source.gallery);
const conciergeAdvisor = normalizeConciergeAdvisor(source.conciergeAdvisor);
const priceStartingValue = cleanPriceStartingValue(source.priceStartingValue, fallback?.priceStartingValue ?? null);
const priceUnit = cleanPriceUnit(source.priceUnit, fallback?.priceUnit ?? null);
const pricePeopleRange = cleanOptionalText(source.pricePeopleRange, fallback?.pricePeopleRange ?? "");
const priceDescription = cleanOptionalText(source.priceDescription, fallback?.priceDescription ?? "");
if (!key || !eyebrow || !duration || !title || !subtitle || !intro) {
return fallback;
@@ -95,6 +116,10 @@ export function normalizeDetailPresentation(
excluded: cleanList(source.excluded).length ? cleanList(source.excluded) : fallback?.excluded ?? [],
notes: cleanList(source.notes).length ? cleanList(source.notes) : fallback?.notes ?? [],
gallery: gallery.length ? gallery : fallback?.gallery ?? [],
priceStartingValue,
priceUnit,
pricePeopleRange,
priceDescription,
conciergeAdvisor,
};
}