- 为后端Admin新增migration_compat工具,支持离线迁移时的基线资源检查,重构全部alembic迁移脚本适配离线模式,避免重复创建已存在的数据库表和字段 - 完善初始数据库迁移脚本,添加默认部门种子数据,新增ConciergeAdvisor详情字段从JSON到JSONB的类型转换迁移 - 小程序端新增图片资源解析工具resolveNetworkImage,实现本地静态资源到CDN的自动映射,添加图片加载容错降级机制 - 完善HeroSlide类型定义,新增actionLabel字段,优化首页轮播组件的文本渲染与错误处理逻辑 - 新增对应测试用例验证迁移路径正确性与前端数据处理逻辑 - 新增临时 brainstorm UI对比草稿与开发临时状态文件
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import {
|
|
heroSlides as fallbackHeroSlides,
|
|
resolveNetworkImage,
|
|
vehicleOptions as fallbackVehicleOptions,
|
|
} from "@/content";
|
|
import type {
|
|
AppData,
|
|
PublicHomeCard,
|
|
PublicSiteConfig,
|
|
PublicVehicleService,
|
|
VehicleOption,
|
|
} from "./types";
|
|
|
|
function normalizeActiveItems<T extends { isActive?: boolean; sortOrder?: number }>(items: T[] | undefined) {
|
|
return (items ?? [])
|
|
.filter((item) => item.isActive !== false)
|
|
.sort((left, right) => (left.sortOrder ?? 0) - (right.sortOrder ?? 0));
|
|
}
|
|
|
|
export function normalizeHomeCards<T extends { id?: string; title: string; image: string; description: string }>(items: PublicHomeCard[] | undefined, fallback: T[]): T[] {
|
|
const activeItems = normalizeActiveItems(items).filter((item) => item.title?.trim());
|
|
if (!activeItems.length) return fallback;
|
|
return activeItems.map((item, index) => {
|
|
const fallbackItem = fallback[index] ?? fallback[0];
|
|
return {
|
|
...fallbackItem,
|
|
id: item.id,
|
|
title: item.title.trim(),
|
|
description: item.description?.trim() || fallbackItem.description,
|
|
image: resolveNetworkImage(item.image, fallbackItem.image),
|
|
} as T;
|
|
});
|
|
}
|
|
|
|
export function normalizeSiteConfig(siteConfig: PublicSiteConfig, fallback: AppData): AppData {
|
|
return {
|
|
heroSlides: siteConfig.heroSlides?.length
|
|
? siteConfig.heroSlides.map((slide, index) => ({
|
|
...slide,
|
|
image: resolveNetworkImage(
|
|
slide.image,
|
|
fallback.heroSlides[index]?.image ?? fallback.heroSlides[0]?.image ?? "",
|
|
),
|
|
id: slide.id,
|
|
kicker: slide.kicker,
|
|
title: slide.title,
|
|
actionLabel: slide.actionLabel,
|
|
targetType: slide.targetType,
|
|
targetValue: slide.targetValue,
|
|
}))
|
|
: fallback.heroSlides,
|
|
vehicleOptions: normalizeHomeCards<VehicleOption>(siteConfig.vehicleOptions, fallback.vehicleOptions),
|
|
vehicleService: fallback.vehicleService,
|
|
};
|
|
}
|
|
|
|
const fallbackVehicleService: PublicVehicleService = {
|
|
introTitle: "旅游出行专属用车服务",
|
|
intro: "提交用车需求后由管家联系确认车型与服务安排。",
|
|
serviceSections: [
|
|
{ title: "旅游专属包车", description: "适配家庭出游、朋友结伴和小团体旅行,行程可按需求沟通调整。" },
|
|
{ title: "多车型灵活选择", description: "按人数和行李情况匹配舒适轿车、SUV 或商务车。" },
|
|
],
|
|
advantages: ["专为旅游定制", "按人数和行李匹配", "管家快速跟进"],
|
|
processSteps: [
|
|
{ title: "提交需求", description: "填写日期、地点、人数和车型偏好。" },
|
|
{ title: "管家联系", description: "沟通车辆和服务安排。" },
|
|
{ title: "确认服务", description: "确认行程细节后按约定出行。" },
|
|
],
|
|
};
|
|
|
|
export const fallbackData: AppData = {
|
|
heroSlides: fallbackHeroSlides,
|
|
vehicleOptions: fallbackVehicleOptions.map((option) => ({ ...option })),
|
|
vehicleService: fallbackVehicleService,
|
|
};
|
|
|
|
export function getSubmitErrorMessage(error: unknown) {
|
|
return error instanceof Error && error.message ? error.message : "提交失败,请稍后再试。";
|
|
}
|
|
|
|
export function formatWan(value: number) {
|
|
return Number.isFinite(value) ? value.toFixed(2).replace(/\.00$/, "") : "0";
|
|
}
|