Files
WonderQ-Project/WonderQ-MiniAPP/src/lib/data.ts
duanshuwen 4e04feda5b feat(admin, miniapp): 优化数据库迁移逻辑与小程序图片资源处理
- 为后端Admin新增migration_compat工具,支持离线迁移时的基线资源检查,重构全部alembic迁移脚本适配离线模式,避免重复创建已存在的数据库表和字段
- 完善初始数据库迁移脚本,添加默认部门种子数据,新增ConciergeAdvisor详情字段从JSON到JSONB的类型转换迁移
- 小程序端新增图片资源解析工具resolveNetworkImage,实现本地静态资源到CDN的自动映射,添加图片加载容错降级机制
- 完善HeroSlide类型定义,新增actionLabel字段,优化首页轮播组件的文本渲染与错误处理逻辑
- 新增对应测试用例验证迁移路径正确性与前端数据处理逻辑
- 新增临时 brainstorm UI对比草稿与开发临时状态文件
2026-08-27 11:37:03 +08:00

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";
}