refactor: 清理废弃模块并重构管理端与全端代码

- 移除所有废弃的旧首页配置模块,包括destinationHero、demand相关、HomeExperience、vehicleService等表结构与代码,新增数据库迁移删除遗留表
- 重构管理端后台布局为标准RuoYi风格,新增Sidebar、Navbar、SettingsDrawer等组件,替换旧的过渡菜单实现
- 调整移动端响应断点为768px,更新布局相关测试用例,新增布局组件测试
- 删除OperationsTools发布/重置页面,移除/tools菜单入口,清理对应的API调用与测试代码
- 重构玩法模块编辑器:替换旧的图片手动输入框为图片上传组件,移除冗余的取消按钮
- 新增线索查询offset参数支持,完善查询参数处理逻辑
- 优化rbac菜单构建逻辑,新增可见性控制参数
- 修正管家编辑器提示文案,优化系统资源编辑器表单初始化逻辑
- 清理小程序端废弃的体验推荐组件与相关测试代码
- 更新文档与种子脚本,匹配新的API契约与使用说明
This commit is contained in:
duanshuwen
2026-08-26 23:24:28 +08:00
parent f6c5ad589f
commit fbf2c772a8
63 changed files with 2272 additions and 1991 deletions

View File

@@ -4,15 +4,9 @@ import {
} from "@/content";
import type {
AppData,
DemandFeatureCard,
DemandFormConfig,
DemandHero,
DestinationHero,
PublicHomeCard,
PublicSiteConfig,
PublicVehicleService,
PublicVehicleServiceSection,
PublicVehicleServiceStep,
VehicleOption,
} from "./types";
@@ -37,131 +31,40 @@ export function normalizeHomeCards<T extends { id?: string; title: string; image
});
}
function normalizeDestinationHero(items: PublicSiteConfig["destinationHero"], fallback: DestinationHero): DestinationHero {
const item = normalizeActiveItems(items).find((entry) => entry.title?.trim());
return item
? { id: item.id, title: item.title.trim(), kicker: item.kicker?.trim() || fallback.kicker, image: item.image?.trim() || fallback.image }
: fallback;
}
function normalizeDemandHero(items: PublicSiteConfig["demandHero"], fallback: DemandHero): DemandHero {
const item = normalizeActiveItems(items).find((entry) => entry.title?.trim());
if (!item) return fallback;
const steps = (item.steps ?? []).map((step) => step.trim()).filter(Boolean);
return { id: item.id, kicker: item.kicker?.trim() || fallback.kicker, title: item.title.trim(), description: item.description?.trim() || fallback.description, steps: steps.length ? steps : fallback.steps };
}
function normalizeDemandFeatureCards(items: PublicSiteConfig["demandFeatureCards"], fallback: DemandFeatureCard[]): DemandFeatureCard[] {
const activeItems = normalizeActiveItems(items).filter((item) => item.title?.trim());
return activeItems.length
? activeItems.map((item, index) => ({ id: item.id, title: item.title.trim(), description: item.description?.trim() || fallback[index]?.description || "" }))
: fallback;
}
function normalizeDemandForm(items: PublicSiteConfig["demandForm"], fallback: DemandFormConfig): DemandFormConfig {
const item = (items ?? []).find((entry) => entry.isActive !== false);
if (!item) return fallback;
const chips = (item.chips ?? []).map((chip) => chip.trim()).filter(Boolean);
return {
id: item.id,
destinationLabel: item.destinationLabel?.trim() || fallback.destinationLabel,
destinationPlaceholder: item.destinationPlaceholder?.trim() || fallback.destinationPlaceholder,
phoneLabel: item.phoneLabel?.trim() || fallback.phoneLabel,
phonePlaceholder: item.phonePlaceholder?.trim() || fallback.phonePlaceholder,
noteLabel: item.noteLabel?.trim() || fallback.noteLabel,
notePlaceholder: item.notePlaceholder?.trim() || fallback.notePlaceholder,
submitLabel: item.submitLabel?.trim() || fallback.submitLabel,
chips: chips.length ? chips : fallback.chips,
};
}
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,
destinationHero: normalizeDestinationHero(siteConfig.destinationHero, fallback.destinationHero),
demandHero: normalizeDemandHero(siteConfig.demandHero, fallback.demandHero),
demandFeatureCards: normalizeDemandFeatureCards(siteConfig.demandFeatureCards, fallback.demandFeatureCards),
demandForm: normalizeDemandForm(siteConfig.demandForm, fallback.demandForm),
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,
vehicleOptions: normalizeHomeCards<VehicleOption>(siteConfig.vehicleOptions, fallback.vehicleOptions),
vehicleService: normalizeVehicleService(siteConfig.vehicleService, fallback.vehicleService),
vehicleService: fallback.vehicleService,
};
}
const fallbackDestinationHero: DestinationHero = {
image: "/assets/guizhou/huangguoshu-waterfall.jpg",
kicker: "贵州小包团目的地",
title: "山水、苗寨、古城与野咖同程安排",
};
const fallbackDemandHero: DemandHero = { kicker: "3步定制", title: "告诉我们日期、人数和想法", description: "管家会按同行人、预算和体力强度,重新组合酒店、用车和景点节奏。", steps: ["提交需求", "管家沟通", "确认方案"] };
const fallbackDemandFeatureCards: DemandFeatureCard[] = [
{ title: "动线", description: "按天数顺路排" },
{ title: "住宿", description: "城市与山野组合" },
{ title: "用车", description: "人数行李匹配" },
];
const fallbackDemandForm: DemandFormConfig = {
destinationLabel: "目的地/玩法",
destinationPlaceholder: "例如:贵州、黄果树、西江苗寨",
phoneLabel: "联系方式",
phonePlaceholder: "手机号 / 微信号",
noteLabel: "补充说明",
notePlaceholder: "出行日期、人数、酒店偏好、预算范围",
submitLabel: "提交出行需求",
chips: ["贵州", "黄果树", "荔波小七孔", "西江苗寨", "梵净山", "万峰林"],
};
const fallbackVehicleService: PublicVehicleService = {
introTitle: "旅游出行专属用车服务",
intro: "覆盖景区接送、短途包车、旅途代步和多人出行场景。提交需求后由管家联系确认车型与服务安排。",
intro: "提交用车需求后由管家联系确认车型与服务安排。",
serviceSections: [
{ title: "旅游专属包车", description: "适配家庭出游、朋友结伴和小团体旅行,支持半天或全天包车,行程可按需求沟通调整。" },
{ title: "多车型灵活选择", description: "舒适轿车、SUV、商务车等车型按人数和行李情况匹配,让不同规模的旅行都能舒适出行。" },
],
advantages: [
"专为旅游定制:贴合出游场景,行程灵活",
"车型齐全:按人数和行李匹配出行方案",
"省心便捷:线上提交需求,管家快速跟进",
"出行安心:服务安排清晰,行程有人跟进",
{ title: "旅游专属包车", description: "适配家庭出游、朋友结伴和小团体旅行,行程可按需求沟通调整。" },
{ title: "多车型灵活选择", description: "按人数和行李情况匹配舒适轿车、SUV 或商务车。" },
],
advantages: ["专为旅游定制", "按人数和行李匹配", "管家快速跟进"],
processSteps: [
{ title: "提交需求", description: "填写日期、地点、人数和车型偏好。" },
{ title: "管家联系", description: "管家根据需求沟通车辆和服务安排。" },
{ title: "确认服务", description: "确认行程细节与报价后按约定出行。" },
{ title: "安心出行", description: "按确认方案完成接送或包车服务。" },
{ title: "管家联系", description: "沟通车辆和服务安排。" },
{ title: "确认服务", description: "确认行程细节后按约定出行。" },
],
};
export const fallbackData: AppData = {
heroSlides: fallbackHeroSlides,
destinationHero: fallbackDestinationHero,
demandHero: fallbackDemandHero,
demandFeatureCards: fallbackDemandFeatureCards,
demandForm: fallbackDemandForm,
vehicleOptions: fallbackVehicleOptions.map((option) => ({ ...option })),
vehicleService: fallbackVehicleService,
};