feat: 新增用车需求服务及线索管理全功能

- 新增后台用车服务配置模块,支持维护服务简介、优势与使用流程
- 新增需求线索管理页面,支持筛选、查看与更新用车线索状态
- 新增小程序用车需求页面,优化登录路径与回跳逻辑
- 优化车型卡片跳转与用车需求提交功能
- 新增服务端API与数据处理逻辑,完善权限校验
- 更新全站配置与API文档,新增相关测试用例
This commit is contained in:
duanshuwen
2026-08-22 21:48:08 +08:00
parent b7a81964c9
commit d42f69bd47
36 changed files with 1290 additions and 33 deletions

View File

@@ -10,6 +10,9 @@ import type {
DestinationHero,
PublicHomeCard,
PublicSiteConfig,
PublicVehicleService,
PublicVehicleServiceSection,
PublicVehicleServiceStep,
VehicleOption,
} from "./types";
@@ -72,6 +75,33 @@ function normalizeDemandForm(items: PublicSiteConfig["demandForm"], fallback: De
};
}
function normalizeVehicleServiceSection(item: unknown): PublicVehicleServiceSection | null {
if (!item || typeof item !== "object") return null;
const record = item as Record<string, unknown>;
const title = typeof record.title === "string" ? record.title.trim() : "";
const description = typeof record.description === "string" ? record.description.trim() : "";
return title && description ? { title, description } : null;
}
function normalizeVehicleServiceStep(item: unknown): PublicVehicleServiceStep | null {
return normalizeVehicleServiceSection(item);
}
function normalizeVehicleService(item: PublicSiteConfig["vehicleService"], fallback: PublicVehicleService): PublicVehicleService {
if (!item || item.isActive === false) return fallback;
const serviceSections = (item.serviceSections ?? []).map(normalizeVehicleServiceSection).filter(Boolean) as PublicVehicleServiceSection[];
const processSteps = (item.processSteps ?? []).map(normalizeVehicleServiceStep).filter(Boolean) as PublicVehicleServiceStep[];
const advantages = (item.advantages ?? []).filter((value): value is string => typeof value === "string").map((value) => value.trim()).filter(Boolean);
return {
id: item.id,
introTitle: item.intro?.trim() && item.introTitle?.trim() ? item.introTitle.trim() : fallback.introTitle,
intro: item.intro?.trim() || fallback.intro,
serviceSections: serviceSections.length ? serviceSections : fallback.serviceSections,
advantages: advantages.length ? advantages : fallback.advantages,
processSteps: processSteps.length ? processSteps : fallback.processSteps,
};
}
export function normalizeSiteConfig(siteConfig: PublicSiteConfig, fallback: AppData): AppData {
return {
heroSlides: siteConfig.heroSlides?.length ? siteConfig.heroSlides.map((slide) => ({ id: slide.id, image: slide.image, kicker: slide.kicker, title: slide.title, targetType: slide.targetType, targetValue: slide.targetValue })) : fallback.heroSlides,
@@ -80,6 +110,7 @@ export function normalizeSiteConfig(siteConfig: PublicSiteConfig, fallback: AppD
demandFeatureCards: normalizeDemandFeatureCards(siteConfig.demandFeatureCards, fallback.demandFeatureCards),
demandForm: normalizeDemandForm(siteConfig.demandForm, fallback.demandForm),
vehicleOptions: normalizeHomeCards<VehicleOption>(siteConfig.vehicleOptions, fallback.vehicleOptions),
vehicleService: normalizeVehicleService(siteConfig.vehicleService, fallback.vehicleService),
};
}
@@ -104,6 +135,26 @@ const fallbackDemandForm: DemandFormConfig = {
submitLabel: "提交出行需求",
chips: ["贵州", "黄果树", "荔波小七孔", "西江苗寨", "梵净山", "万峰林"],
};
const fallbackVehicleService: PublicVehicleService = {
introTitle: "旅游出行专属用车服务",
intro: "覆盖景区接送、短途包车、旅途代步和多人出行场景。提交需求后由管家联系确认车型与服务安排。",
serviceSections: [
{ title: "旅游专属包车", description: "适配家庭出游、朋友结伴和小团体旅行,支持半天或全天包车,行程可按需求沟通调整。" },
{ title: "多车型灵活选择", description: "舒适轿车、SUV、商务车等车型按人数和行李情况匹配,让不同规模的旅行都能舒适出行。" },
],
advantages: [
"专为旅游定制:贴合出游场景,行程灵活",
"车型齐全:按人数和行李匹配出行方案",
"省心便捷:线上提交需求,管家快速跟进",
"出行安心:服务安排清晰,行程有人跟进",
],
processSteps: [
{ title: "提交需求", description: "填写日期、地点、人数和车型偏好。" },
{ title: "管家联系", description: "管家根据需求沟通车辆和服务安排。" },
{ title: "确认服务", description: "确认行程细节与报价后按约定出行。" },
{ title: "安心出行", description: "按确认方案完成接送或包车服务。" },
],
};
export const fallbackData: AppData = {
heroSlides: fallbackHeroSlides,
@@ -112,6 +163,7 @@ export const fallbackData: AppData = {
demandFeatureCards: fallbackDemandFeatureCards,
demandForm: fallbackDemandForm,
vehicleOptions: fallbackVehicleOptions.map((option) => ({ ...option })),
vehicleService: fallbackVehicleService,
};
export function getSubmitErrorMessage(error: unknown) {